SpringCloudGateWay

SpringCloudGateWay

概述

SpringCloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

简单来说:SpringCloud Gateway是使用WebFlux 中的reactor-netty 响应式编程组件,底层使用了Netty通讯框架,速度比Zuul 1.0更快。

主要功能

  • 反向代理
  • 权限控制
  • 流量控制
  • 熔断
  • 日志监控等

特点

  • 基于 Spring Framework 5、Project Reactor 和 Spring Boot 2.0
  • 能够匹配任何请求属性的routes
  • Predicatesfilter组合成为特定路由。
  • 断路器集成。
  • Spring Cloud DiscoveryClient 集成
  • 编写Predicatesfilter容易
  • 限流
  • 路径重写

filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

Predicate(断言): 参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

route(路由):是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由

原理

​ 客户端向Spring-Gateway发送请求,如果网关的处理程序映射(Gateway Handler Mapping)确定请求与路由匹配,则将其转发到Web网关处理程序(Gateway Web Handler),在通过特定的处理请求的过滤器链运行请求。过滤器被虚线分隔的原因是过滤器可以在发送代理请求之前和之后运行逻辑。执行所有“预”过滤器逻辑。然后进行代理请求。

GateWay 服务搭建

1、搭建SpringCloud服务中心 参考:SpringCloud 框架搭建

2、创建网关服务中心:在刚刚搭建的框架中搭建网关

  • 创建项目 SpringCloud-Gateway-9527

  • 导入Pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>SpringCloud-Way</artifactId>
            <groupId>com.wyx</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>SpringCloud-way-9527</artifactId>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.tar>8</maven.compiler.tar>
        </properties>
    
        <dependencies>
            <!--Spring-Way 启动依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
    
            <!--连接 consul 的依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-consul-discovery</artifactId>
            </dependency>
    
            <!--spring-boot-web 模块 常用的3个 网关不需要,否则启动不了-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    <!--        <dependency>-->
    <!--            <groupId>org.springframework.boot</groupId>-->
    <!--            <artifactId>spring-boot-starter-web</artifactId>-->
    <!--        </dependency>-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <!--热部署插件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </dependency>
            <!--测试插件-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--lombok 依赖-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    
    </project>
    
  • 创建application.yaml

    server:
      port: 9527
    spring:
      application:
        name: cloud-gateway
    
      cloud:
        consul:
          # 服务的主机
          host: localhost
          # 服务的端口号
          port: 8500
          discovery:
            # 注册到服务中心的名称
            service-name: ${spring.application.name}
    
  • 主启动类

    package com.wyx.cloud;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient
    public class SpringCloudWayMain9527 {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringCloudWayMain9527.class,args);
        }
    
    }
    
  • 启动服务时需要先启动Consul,因为使用的时Consul注册中心 如何启动参考

    Consul安装及其启动

启动后访问Consul的服务中心查看是否注册完成,访问:http://localhost:8500/

搭建服务提供者 8001 - 8002搭建流程参考:https://www.cnblogs.com/Rampant/p/14775726.html 的Consul服务治理,服务提供者注册到Consul。不在具体概述。

配置路由协议:在application.yaml中配置

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由

添加配置前:需要访问 http://localhost:8001//1

添加网关配置后:访问:http://localhost:9527//1

这里网关的作用:相当于,在访问的路径上面添加一层,方便管理多个项目。简单来说就是将多个项目提供的方法添加到网关的方法。

第二种配置方法:配置类编写

package com.wyx.cloud.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class WayConfig {
    
    /*
    *  route-8002 对应id
    *  //** 对应 -Path
    *   http://localhost:8002 对应 url
    */
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("route-8002",
                r -> r.path("//**")
                        .uri("http://localhost:8002")
        ).build();
        return routes.build();
    }
}

配置类的优先级高于 application.yaml 的优先级

动态路由配置

在上面的配置中,如下
spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
我们可以看出,url的值是固定写死的,但是在实际开发中,一个服务一般情况下都是集群的,写死的话不满足要写,于是我们可以使用动态路由配置来实现。

配置如下

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
# lb代表启用负载均衡,cloud-provider-way 代表提供的服务名,配置后当服务提供者集群时,就会轮巡的调用

断言

Predicate 的使用

After

在XXX时间后生效

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]

上面的时间是如何获取的?

import java.time.ZonedDateTime;

public class Time {
    public static void main(String[] args) {
        ZonedDateTime zbj = ZonedDateTime.now();
        System.out.println(zbj);
    }
}
// 2021-07-16T14:40:35.302+08:00[Asia/Shanghai]

Before

在XXX时间前生效

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]

Between

在XXX时间到XXX时间生效

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
            - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

在Cookie满足的条件下生效

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
			- Cookie=username, wyx

前一个是代表键名,后一个代表值,可以使用Java正则来替换值

在请求头包含的参数满足下生效

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
        	- Header=X-Request-Id, \d+

前一个是代表请求头的键名,后一个代表值,可以使用Java正则来替换值

Host

满足请求主机的信息放行

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
            - Host=**.somehost.org,**.anotherhost.org

Method

请求方法满足放行

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
            - Method=,POST

Path

匹配请求路径,路径符合就匹配,前面很多例子都用到了Path不在说明

Query

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=green
        # 如果请求包含green(参数名)查询参数,则前面的路由匹配。
spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=red, gree.
        # 请求的参数名为red 且值满足正则表达式,这里可以匹配 gree或greet 都可以

RemoteAddr

这里运用了计算机网络中的构造超网CIDR ,具体如何匹配请参考计算机网络的书。

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: https://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24
        # 192.168.1.1/24 前面24号作为网络号,剩余8为做主机号,可以匹配例如192.168.1.10,具体如何匹配参考计算机网络书

Weight

权重路由匹配转发

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2
# 该路由会将约 80% 的流量转发到weighthigh.org,将约 20% 的流量转发到weightlow.org 同一服务的group相同

过滤器

Filter,这里又称路由过滤器(网关过滤器),即HTTP请求通过断言接收过来后在将请求中的参数进行一定的修改,从而达到我们请求的某服务器的参数配置要求。

过滤器主要分为(单一)网关过滤器和全局过滤器,也可以按照时间的先后顺序分为业务逻辑前,和业务逻辑后两种

网关过滤器(GatewayFilter)

网关过滤器,共有31种配置模板

由于在官网中书写例子较多,这里我们随便说明几个,具体使用请查看官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/index.html#gatewayfilter-factories

AddRequestHeader

添加请求头参数,即HTTP请求上添加一个请求头

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue
        # 请求头名字:X-Request-red 值为blue

SetPath

设置请求路径

spring:
  cloud:
    gateway:
      routes:
      - id: setpath_route
        uri: https://example.org
        predicates:
        - Path=/red/{segment}
        filters:
        - SetPath=/{segment}
        # 这里的效果是,如果请求为 https://example.org/red/1 ,设置请求路径后为 https://example.org/1

PrefixPath

添加请求前缀

spring:
  cloud:
    gateway:
      routes:
      - id: prefixpath_route
        uri: https://example.org
        filters:
        - PrefixPath=/mypath
        # 如果请求的路径为 https://example.org/ 会将其修改为 https://example.org/mypath/

更多方法请参考官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/index.html#gatewayfilter-factories

全局过滤器

和网关过滤器功能一样,但是这个是应用在全局,全局过滤器默认也给我们配了许多过滤器,比如ForwardRoutingFilter(转发)ReactiveLoadBalancerClientFilter(负载均衡过滤器) 等等,具体查看官网。

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/index.html#global-filters

转发过滤器:

配置如下:

spring:
  cloud:
    gateway:
      routes:
      - id: myRoute
        uri: foresrd:///service
        predicates:
        - Path=/service/**
        # 假设请求的是 localhost:8080/index 经过改变后请求的路径为 localhost:8080/service

负载均衡过滤器

spring:
  cloud:
    gateway:
      routes:
        - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: lb://cloud-provider-way   #匹配后提供服务的路由地址
          predicates:
            - Path=//**   #断言,路径相匹配的进行路由
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]
          # lb://接微服务名称 可以实现具体的微服务之间的跳转,假设有两个微服务提供者,那么他将轮询的调用微服务提供者

自定义过滤器

虽然在SpringCloudGateway中,存在许多已经配置号的过滤器,但是有时后也不能完全满足我们的需求,于是我们可以自定义过滤器,从而达到我们的需求。

自定义网关过滤器

那么如何才能实现我们自定义的网关过滤器呢?

  1. 编写过滤器类,需要实现GlobalFilterOrdered 接口

    package com.wyx.cloud.filter;
    
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.core.Ordered;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    public class CustomizeWayFilter implements Ordered, GatewayFilter {
    
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            System.out.println("进入了自定义网关过滤器");
            return chain.filter(exchange);
        }
    
    
        /*
        *   控制执行顺序,数值越小优先级越高
        *
        * */
        @Override
        public int Order() {
            return 0;
        }
    }
    
    
  2. 将编写的过滤器注入需要注入的过滤路由

    package com.wyx.cloud.config;
    
    import com.wyx.cloud.filter.CustomizeWayFilter;
    import org.springframework.cloud.gateway.route.RouteLocator;
    import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec;
    import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
    import org.springframework.cloud.gateway.route.builder.UriSpec;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.function.Function;
    
    @Configuration
    public class WayConfig {
        
        /*
        *  route-8002 对应id
        *  //** 对应 -Path
        *   http://localhost:8002 对应 url
        */
        @Bean
        public RouteLocator routeLocator(RouteLocatorBuilder routeLocatorBuilder){
            RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
    
            routes.route("route-8002",
                    r -> r.path("//**")
                            .filters(f -> f.filter(new CustomizeWayFilter())
                            )
                            .uri("http://localhost:8002")
            ).build();
            return routes.build();
        }
    }
    

自定义全局过滤器

自定义全局过滤器,和自定义网关过滤器都需要实现 GlobalFilterOrdered 接口,但是全局过滤器,只需要将实现的类当作组件加入SpringIOC容器中即可

如下 ,以下配置需要放到@Configuration下

@Bean
public GlobalFilter customFilter() {
    return new CustomGlobalFilter();
}

public static class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("进入了全局过滤器");
        return chain.filter(exchange);
    }

    @Override
    public int Order() {
        return -1;
    }
}

权限校验

    @Bean
    public GlobalFilter customFilter() {
        return new CustomGlobalFilter();
    }

    public static class CustomGlobalFilter implements GlobalFilter, Ordered {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String token = exchange.Request().QueryParams().First("token");
            if (token == null){
                System.out.println("token id null...");
                ServerHttpResponse response = exchange.Response();
                response.Headers().add("Content-Type","application/json; charset=utf-8");
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                String message = "用户未授权";
                DataBuffer wrap = response.bufferFactory().wrap(message.Bytes());
                return response.writeWith(Mono.just(wrap));
            }
            System.out.println("权限校验成功");
            return chain.filter(exchange);
        }
        @Override
        public int Order() {
            return -1;
        }
    }

网关限流

为什么要限流:

  • 用户增长过快

  • 因为某个热点

  • 恶意爬虫

  • 恶意请求

    简单理解就是防止突然间的高流量进入,导致服务器崩溃

限流算法

  1. 计数器算法

    简单来说就是在规定的时间内,限制访问次数,比如一分钟可以访问100次,但是如果有恶意访问在1秒钟就将100次用完,那么后面的其他时间访问也不提供服务,会造成资源浪费,其次就是会使得间接性服务不可用

  2. 漏桶算法

    就是将请求用队列存储起来,然后对请求的队列慢慢的处理,可以规定多长时间处理多少请求。缺点是当请求过多时,可能会造成队列溢出,或者Java虚拟机内存不够用,从而导致服务故障。

  3. 令牌漏桶算法

    就是在漏桶算法的基础上添加对输入流量的限制,下面我们来看看原理图

​ 大致运行流程如下,用恒定的速度生产令牌,并放入桶(队列)中,如果桶满了,生成的令牌不加入桶中。这时如果收到请求就从令牌桶中获取令牌,如果获取成功就处理请求,否则不处理请求,将请求丢弃或者报错。

Gateway结合redis实现令牌桶算法限流

Spring Cloud GateWay 官方提供了RequestRateLimiterGatewayFliterFactory 使用RedisLua脚本实现了令牌桶算法。

  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    
  2. 配置application.yaml

    server:
      port: 9527
    spring:
      application:
        name: cloud-gateway
    
      cloud:
        gateway:
          routes:
            - id: routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
              uri: lb://cloud-provider-getway   #匹配后提供服务的路由地址
              predicates:
                - Path=/get/**   #断言,路径相匹配的进行路由
              filters:
                - name: RequestRateLimiter
                  args:
                    redis-rate-limiter.replenishRate: 1   # 生成令牌速度 单位秒 1秒1个
                    redis-rate-limiter.burstCapacity: 2   # 桶容量
                    redis-rate-limiter.requestedTokens: 1 # 每个请求消耗的令牌数量
                    key-resolver: "#{@pathKeyResolver}"   # 使用的规则
    
      redis:
        timeout: 1000
        host: xxx.xxx.xxx.xxx
        port: 6379
        password: xxxx
        database: 0
        lettuce:
          pool:
            max-active: 1024
            max-wait: 10000
            max-idle: 200
            min-idle: 5
    
        consul:
          # 服务的主机
          host: localhost
          # 服务的端口号
          port: 8500
          discovery:
            # 注册到服务中心的名称
            service-name: ${spring.application.name}
    
  3. 限流规则

    package com.wyx.cloud.config;
    
    import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import reactor.core.publisher.Mono;
    
    @Configuration
    public class LimitConfig {
    /*
    一次只能使用一种限流规则,即只能注入一个bean 并且需要修改配置文件中的 
    key-resolver: "#{@pathKeyResolver}"   # 使用的规则 其中pathKeyResolver 对应bean的方法名
    */
        
        
    //    @Bean
        public KeyResolver pathKeyResolver(){
    //        return new KeyResolver() {
    //            @Override
    //            public Mono<String> resolve(ServerWebExchange exchange) {
    //                return Mono.just(exchange.getRequest().getURI().getPath().toString());
    //            }
    //        };
            // 1.8 新特性
            return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
        }
    
    //    参数限流
        //@Bean
        public KeyResolver parameterKeyResolver(){
    
            return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
        }
    
    //    IP限流
        @Bean
        public KeyResolver IPKeyResolver(){
            return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
        }
    
    }
    
    

高可用网关搭建

参考Nginx的高可用搭建,参考:https://www.cnblogs.com/Rampant/p/14785179.html 的反向代理集群高可用

posted @ 2021-07-17 19:48  橘子有点甜  阅读(230)  评论(0编辑  收藏  举报