zoukankan      html  css  js  c++  java
  • ⑥SpringCloud 实战:引入gateway组件,开启网关路由功能

    这是SpringCloud实战系列中第4篇文章,了解前面第两篇文章更有助于更好理解本文内容:
    ①SpringCloud 实战:引入Eureka组件,完善服务治理
    ②SpringCloud 实战:引入Feign组件,发起服务间调用
    ③SpringCloud 实战:使用 Ribbon 客户端负载均衡
    ④SpringCloud 实战:引入Hystrix组件,分布式系统容错
    ⑤SpringCloud 实战:引入Zuul组件,开启网关路由

    简介

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

    Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

    特性

    Spring Cloud Gateway 具有如下特性:

    • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
    • 动态路由:能够匹配任何请求属性;
    • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
    • 集成Hystrix的断路器功能;
    • 集成 Spring Cloud 服务发现功能;
    • 易于编写的 Predicate(断言)和 Filter(过滤器);
    • 请求限流功能;
    • 支持路径重写。

    实战 Gateway

    引入 gateway

    1. 新建jlw-gateway项目

    2. 引入gateway依赖

      <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-gateway</artifactId>
      </dependency>
      
    3. 加入Eureka注册中心

    4. 开启服务注册和发现

      # gateway 服务端口
      server:
          port: 9000
      
      spring:
          cloud:
              gateway:
                  discovery:
                      locator:
                          # 启用DiscoveryClient网关集成的标志
                          enabled: true
                          # 服务小写匹配
                          lower-case-service-id: true
      

      配置完上面,Gateway 就可以自动根据服务发现为每个服务创建router了,然后将已服务名开头的请求路径转发到对应的服务。

    Gateway Actuator API

    pom中引入spring-boot-starter-actuator相关依赖,然后配置文件添加如下代码,开启gateway相关的端点。

    management:
        endpoints:
            web:
                exposure:
                    #应该包含的端点ID,全部:*
                    include:  'gateway'
    

    重启项目,访问http://localhost:9000/actuator/gateway/routes就可以查看到配置的路由信息了

    更多gateway路由信息接口展示如下图:

    相关概念

    • Route(路由):
      路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;
    • Predicate(断言):
      指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由;
    • Filter(过滤器):
      指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。

    路由基本配置

    Gateway 提供了两种不同的方式用于配置路由:一种是通过yml文件来配置,另一种是通过Java Bean来配置

    使用配置文件

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      filters:
                        - StripPrefix=2
    

    字段含义解释:

    • id
      我们自定义的路由 ID,保持唯一
    • uri
      目标服务地址,大部分场景我们是转发到某个服务上,配置lb://eureka-provider意思是请求要转发到注册中心的eureka-provider服务上。
    • predicates
      路由条件,接受一个参数,返回一个布尔结果决定是否匹配。Gateway 为我们内置了多种路由条件,包括 Path、Cookie、Param、Header、Before、After 等等,开箱即用,当然我们也可以自己实现 predicates
    • filters
      过滤规则,当请求经过 predicate 匹配成功后,执行 filter,我们可以使用它修改请求和响应,示例表示目标服务收到的 path 将会忽略2位路径path。

    ②使用Java Bean配置

    配置RouteLocator对象,代码示例如下:

    @Bean
    public RouteLocator customRoutes(RouteLocatorBuilder builder){
        return builder.routes()
                // 请求网关路径包含 /api/ec/** 的都会被路由到eureka-client
                .route("eureka-client",r->r.path("/api/ec/**")
                        .filters(f->f.stripPrefix(2))
                        .uri("lb://eureka-client"))
                // 可以配置多个route
                .route("eureka-client2",r->r.path("/api/ec2/**")
                        .filters(f->f.stripPrefix(2))
                        .uri("lb://eureka-client"))
                .build();
    }
    

    以上配置后,通过http://localhost:9000/api/ec/sayHello 或者http://localhost:9000/api/ec2/sayHello 都会被路由到eureka-client服务。

    路由规则:Predicate

    Spring Cloud Gateway 内置了很多 Predicates 工厂(可以通过访问路径/actuator/gateway/routepredicates查看),这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。

    按照其功能可以大致分为以下几种不同 Predicate

    1.通过请求时间匹配路由

    1. 指定时间之后的请求会匹配该路由:AfterRoutePredicateFactory

      spring:
          cloud:
              gateway:
                  routes:
                      - id: ribbon-client
                        uri: lb://ribbon-client
                        predicates:
                            - After=2019-10-10T00:00:00+08:00[Asia/Shanghai]
      
    2. 指定时间之前的请求会匹配该路由:BeforeRoutePredicateFactory

      spring:
          cloud:
              gateway:
                  routes:
                      - id: ribbon-client
                        uri: lb://ribbon-client
                        predicates:
                            - Before=2019-10-10T00:00:00+08:00[Asia/Shanghai]
      
    3. 指定时间区间内的请求会匹配该路由:BetweenRoutePredicateFactory

      spring:
          cloud:
              gateway:
                  routes:
                      - id: ribbon-client
                        uri: lb://ribbon-client
                        predicates:
                            - Between=2019-10-01T00:00:00+08:00[Asia/Shanghai], 2019-10-10T00:00:00+08:00[Asia/Shanghai]
      

    2.通过Cookie匹配路由

    Cookie Route Predicate 可以接收两个参数,一个是 Cookie name,一个是正则表达式。示例如下:

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates: 
                          - Cookie=name, jinglingwang.cn
    

    3.通过 Header匹配路由

    Header Route Predicate 和 Cookie Route Predicate 一样,也是接收 2 个参数,一个 header 中属性名称和一个正则表达式,示例如下:

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates:
                          - Header=name, jinglingwang.cn
    

    4.通过 Host 匹配路由

    该模式接收一个参数:主机列表,示例如下:

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates:
                          - Host=**.jinglingwang.cn,**.jinglingwang.com
    

    5.通过 Request Method 匹配路由

    可以通过是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由,示例代码如下:

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates:
                          - Method=GET,POST
    

    6.通过请求路径匹配路由

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates: 
                          - Path=/api/rc/**
    

    7.通过请求参数匹配路由

    该模式有两个参数:一个必需的param和一个可选的regexp(Java正则表达式),示例如下

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates:
                          - QUERY=name,jingling*
    

    8.通过指定远程地址匹配路由

    spring:
        cloud:
            gateway:
                routes:
                    - id: ribbon-client
                      uri: lb://ribbon-client
                      predicates:
                          - RemoteAddr=192.168.1.1/24 #192.168.1.1是IP  24是子网掩码
    

    如果请求的远程地址是192.168.1.10,则此路由匹配。

    9.通过权重来匹配路由

    该模式有两个参数:group和Weight(一个int值),示例如下:

    spring:
      cloud:
        gateway:
          routes:
          - id: weight_high
            uri: http://localhost:8201
            predicates:
            - Weight=group1, 8
          - id: weight_low
            uri: http://localhost:8202
            predicates:
            - Weight=group1, 2
    

    以上表示有80%的请求会被路由到localhost:8201,20%会被路由到localhost:8202

    网关过滤器

    路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器只能指定路由进行使用。Spring Cloud Gateway 内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。

    1. 添加请求Header过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: ribbon-client
                        uri: lb://ribbon-client
                        filters:
                          - AddRequestHeader=source, jinglingwang.cn
      

      上面的示例会为所有匹配的请求向下游请求时在Header中添加source=jinglingwang.cn

    2. 添加请求参数过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: ribbon-client
                        uri: lb://ribbon-client
                        filters:
                          - AddRequestParameter=source, jinglingwang.cn
      

      上面的示例会把source=jinglingwang.cn添加到下游的请求参数中

    3. 添加响应头过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: ribbon-client
                        uri: lb://ribbon-client
                        filters:
                          - AddResponseHeader=source, jinglingwang.cn
      

      上面的示例会把source=jinglingwang.cn添加到所有匹配请求的下游响应头中。

    4. 剔除重复的响应头过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: ribbon-client
                        uri: lb://ribbon-client
                        filters:
                          - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
      

      DedupeResponseHeader过滤器也可接收可选策略参数,可接收参数值包括:RETAIN_FIRST (默认值,保留第一个值), RETAIN_LAST(保留最后一个值), and RETAIN_UNIQUE(保留所有唯一值,以它们第一次出现的顺序保留)。

    5. 开启Hystrix断路器功能的过滤器

      要开启断路器功能,我们需要在pom.xml中添加Hystrix的相关依赖:spring-cloud-starter-netflix-hystrix

      然后添加相关服务降级的处理类:

      @RestController
      public class FallbackController{
          @GetMapping("/fallback")
          public Object fallback() {
              
              Map<String,Object> result = new HashMap<>(3);
              result.put("data","jinglingwang.cn");
              result.put("message","Get request fallback!");
              result.put("code",500);
              return result;
          }
      }
      

      添加配置

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - StripPrefix=2
                          - name: Hystrix
                            args:
                              name: fallbackcmd
                              fallbackUri: forward:/fallback
      
    6. 启用resilience4j断路器的过滤器

      要启用Spring Cloud断路器过滤器,需要引入依赖spring-cloud-starter-circuitbreaker-reactor-resilience4j

      spring:
      	  cloud:
      		gateway:
      			 routes:
      				- id: ribbon-client
      				  uri: lb://ribbon-client
      				  filters:
      				   - CircuitBreaker=myCircuitBreaker
      

      还可以接受一个可选的fallbackUri参数:

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - StripPrefix=2
                          - name: CircuitBreaker
                            args:
                              name: myCircuitBreaker
                              fallbackUri: forward:/fallback
      

      关闭eureka-provider服务,访问http://localhost:9000/api/ep/hello接口,出现降级处理信息

      上面的配置也可以用JAVA Bean的方式配置:

      @Bean
      public RouteLocator customRoutes(RouteLocatorBuilder builder){
          return builder.routes()
                  .route("eureka-provider", r -> r.path("/api/ep/**")
                          .filters(f->f.stripPrefix(2).circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback")))
                          .uri("lb://eureka-provider"))
                  .build();
      }
      

      6.1 根据状态码使断路器跳闸

      根据返回的状态码,决定断路器是否要跳闸

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - StripPrefix=2
                          - name: CircuitBreaker
                            args:
                              name: myCircuitBreaker
                              fallbackUri: forward:/fallback
                              statusCodes:
                                - 500
                                - 'NOT_FOUND'
      

      或者JAVA Bean配置:

      @Bean
      public RouteLocator customRoutes(RouteLocatorBuilder builder){
          return builder.routes()
                  .route("eureka-provider", r -> r.path("/api/ep/**")
                          .filters(f->f.stripPrefix(2).circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback").addStatusCode("500")))
                          .uri("lb://eureka-provider"))
                  .build();
      }
      

      其中NOT_FOUND是HttpStatus枚举的String表示形式

    7. 增加路径的过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - PrefixPath=/mypath
      

      /mypath作为所有匹配请求路径的前缀

    8. 去掉路径前缀的过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - StripPrefix=2
      

      以上配置会忽略两位路径path,当访问网关API /api/ep/hello 时,向eureka-provider发起/hello请求

    9. 用于限流的过滤器

      RequestRateLimiter 过滤器可以用于限流,RateLimiter实现来确定是否允许继续当前请求。 如果不是,则返回HTTP 429—太多请求(默认)的状态。

      该过滤器采用可选的keyResolver参数和速率限制器特定的参数,keyResolver是一个实现KeyResolver接口的bean。在配置中,使用SpEL按名称引用bean。#{@myKeyResolver}是引用名为myKeyResolver的bean的SpEL表达式。

      使用 Redis RateLimiter

      1. 引入redis依赖,配置好redis。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        
      2. 添加配置,使用的算法是令牌桶算法。

        spring:
            cloud:
                gateway:
                    routes:
                        - id: eureka-provider
                          uri: lb://eureka-provider
                          predicates:
                            - Path=/api/ep/**
                          filters:
                            - name: RequestRateLimiter
                              args:
                                redis-rate-limiter.replenishRate: 10  #允许用户每秒处理多少个请求,而不丢弃任何请求。这是令牌桶的填充速率
                                redis-rate-limiter.burstCapacity: 20  #一个用户在一秒钟内允许做的最大请求数。这是令牌桶可以容纳的令牌数。将该值设置为零会阻止所有请求。
                                redis-rate-limiter.requestedTokens: 1 #一个请求花费多少令牌
        

        通过在“replenishRate”和“burstCapacity”中设置相同的值来实现稳定的速率。通过将burstCapacity设置为高于replenishRate,可以允许临时爆发。

      3. 配置KeyResolver

        JAVA 代码:

        @Configuration
        public class RedisRateLimiterConfig{
            @Bean
            KeyResolver userKeyResolver() {
                // 根据请求参数中的phone进行限流
                return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("phone"));
            }
            @Bean
            @Primary
            public KeyResolver ipKeyResolver() {
                // 根据访问IP进行限流
                return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
            }
        }
        
      4. 配置RequestRateLimiter

        spring:
            cloud:
                gateway:
                    routes:
                        - id: eureka-provider
                          uri: lb://eureka-provider
                          predicates:
                            - Path=/api/ep/**
                          filters:
                            - name: RequestRateLimiter
                              args:
                                redis-rate-limiter.replenishRate: 1  #允许用户每秒处理多少个请求,而不丢弃任何请求。这是令牌桶的填充速率
                                redis-rate-limiter.burstCapacity: 2  #一个用户在一秒钟内允许做的最大请求数。这是令牌桶可以容纳的令牌数。将该值设置为零会阻止所有请求。
                                redis-rate-limiter.requestedTokens: 1 #一个请求花费多少令牌
                                key-resolver: "#{@ipKeyResolver}"
        

        多次请求,会返回状态码为429的错误

    10. 用于重定向的过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - StripPrefix=2
                          - RedirectTo=302, https://jinglingwang.cn  #302 重定向到https://jinglingwang.cn
      

      效果图如下:

    11. 用于重试的过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - StripPrefix=2
                          - name: Retry
      		  args:
      		    retries: 3
      		    statuses: BAD_GATEWAY
      		    methods: GET,POST
      		    backoff:
      			 firstBackoff: 10ms
      			 maxBackoff: 50ms
      			 factor: 2
      			 basedOnPreviousValue: false
      

      参数解释:

      • retries:应该尝试的重试次数。
      • statuses:应该重试的HTTP状态代码,HttpStatus。
      • methods:应该重试的HTTP方法,HttpMethod。
      • series:要重试的状态码,Series。
      • exceptions:应该重试的引发异常的列表。
      • backoff:为重试配置的指数补偿
    12. 用于限制请求大小的过滤器

      spring:
          cloud:
              gateway:
                  routes:
                      - id: eureka-provider
                        uri: lb://eureka-provider
                        predicates:
                          - Path=/api/ep/**
                        filters:
                          - StripPrefix=2
                         - name: RequestSize
                           args:
      			     maxSize: 5MB
      

      当请求大小大于允许的限制时,网关会限制请求到达下游服务。maxSize参数后跟一个可选的数据单位,如“KB节”或“MB”,默认是“B”。

      上面的配置如果超过限制会出现以下提示:

      errorMessage:Request size is larger than permissible limit. Request size is 6.0 MB where permissible limit is 5.0 MB

    配置http超时时间

    我们可以为所有路由配置Http超时(响应和连接),并且为每个特定路由配置单独的超时时间

    全局的超时时间配置:

    spring:
      cloud:
        gateway:
          httpclient:
            connect-timeout: 1000
            response-timeout: 5s
    

    特定路由配置超时时间

    spring:
        cloud:
            gateway:
                routes:
                    - id: eureka-provider
                      uri: lb://eureka-provider
                      predicates:
                        - Path=/api/ep/**
                      metadata:
                         response-timeout: 2000
                         connect-timeout: 1000
    

    或者使用JAVA Bean的方式配置:

    @Bean
    public RouteLocator customRoutes(RouteLocatorBuilder builder){
        return builder.routes()
                .route("eureka-provider", r -> r.path("/api/ep/**")
                        .filters(f->f.stripPrefix(2)
                                .requestRateLimiter(rate->rate.setKeyResolver(ipKeyResolver))
                                .circuitBreaker(c->c.setName("myCircuitBreaker").setFallbackUri("forward:/fallback").addStatusCode("500").addStatusCode("NOT_FOUND")))
                        .uri("lb://eureka-provider")
                        .metadata(RESPONSE_TIMEOUT_ATTR, 200)
                        .metadata(CONNECT_TIMEOUT_ATTR, 200))
                .build();
    }
    

    跨域配置

    spring:
        cloud:
            gateway:
                globalcors: # 全局跨域配置
                  cors-configurations:
                    '[/**]':
                      allowedOrigins: "jinglingwang.cn"
                      allowedMethods:
                        - GET
                  add-to-simple-url-handler-mapping: true
    

    上面的配置,对于所有Get请求,允许来自jinglingwang.cn的跨域请求。

    自定义 Route Predicate Factories

    通过一个名为AbstractRoutePredicateFactory的抽象类来进行扩展,示例代码:

    public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {
    
        public MyRoutePredicateFactory(){
            super(Config.class);
        }
    
        @Override
        public Predicate<ServerWebExchange> apply(Config config){
            return new GatewayPredicate() {
                @Override
                public boolean test(ServerWebExchange exchange) {
                    String host = exchange.getRequest().getHeaders().getFirst("Host");
                    return "jinglingwang.cn".equalsIgnoreCase(host);
                }
    
                @Override
                public String toString() {
                    return String.format("host: name=%s ", config.host);
                }
            };
        }
    
        public static class Config {
            //自定义过滤器的配置属性
            @NotEmpty
            private String host;
        }
    
    }
    

    自定义 GatewayFilter Factories

    要写GatewayFilter,必须实现GatewayFilterFactory,可以通过扩展一个名为AbstractGatewayFilterFactory的抽象类来进行。

    @Component
    public class AddHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<AddHeaderGatewayFilterFactory.Config>{
    
        public AddHeaderGatewayFilterFactory() {
            super(Config.class);
        }
        @Override
        public GatewayFilter apply(Config config){
            return (exchange, chain) -> {
                // 如果要构建“前置”过滤器,则需要在调用chain.filter之前处理
                ServerHttpRequest request = exchange.getRequest().mutate()
                        .header("source", "jinglingwang.cn").build();
                //使用构建器来处理请求
                return chain.filter(exchange.mutate().request(request).build());
            };
        }
    
        public static class Config {
            //Put the configuration properties for your filter here
        }
    }
    

    配置属性

    要查看所有与 Spring Cloud 网关相关的配置属性列表,请参见附录

  • 相关阅读:
    线程(中)
    线程
    生产者消费者模型
    进程的常用方法
    HTML中head与body标签
    HTTP协议
    mysql:视图,触发器,事务,存储过程,函数。
    关于MySQL中pymysql安装的问题。
    MySQL多表查询,pymysql模块。
    MySQL之单表查询
  • 原文地址:https://www.cnblogs.com/admol/p/14133985.html
Copyright © 2011-2022 走看看