这里要介绍的是Spring出的spring-cloud-starter-gateway

开始

先了解SpringCloud Gateway的相关概念:

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

先看看路由的断言有哪些

  • The After Route Predicate Factory:匹配在指定日期时间之后发生的请求
  • The Before Route Predicate Factory:匹配在指定日期时间之前发生的请求
  • The Between Route Predicate Factory:匹配在指定日期时间端之内发生的请求
  • The Cookie Route Predicate Factory:cookie路由
  • The Header Route Predicate Factory:header路由
  • The Host Route Predicate Factory:主机名地址路由
  • The Method Route Predicate Factory:请求方法路由
  • The Path Route Predicate Factory:请求uri路由
  • The Query Route Predicate Factory:请求参数路由
  • The RemoteAddr Route Predicate Factory:ip请求路由
  • The Weight Route Predicate Factory:权重路由

路由

了解这些内容后直接看一个示例:

  1. pom.xml先引入依赖(2.2.6.RELEASEHoxton.SR3):

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
  2. 建议网关的配置都写配置在yml配置文件上,不用java代码写,这样可以更方便的通过配置中心动态修改网关配置

    spring:
      cloud:
        gateway:
          routes:
            - id: order-service
              uri: 'http://192.168.1.233:9300'
              predicates:
                - Path=/order-service/**
    
    • routers下面来配置所有的路由规则
    • id表示一个唯一的值
    • uri表示路由转发的地址
    • predicates断言的配置,就是上面那些

这里交代一下:我已经启动了一个order-service监听在9300端口了,这个服务有一个路由是order/create

这里使用的是Path断言,当请求的路径是/order-service/**(**表示任意内容)时,则把请求转发到http://192.168.1.233:9300即:

http://192.168.203.233:9500/order-service/order/create 
->
http://192.168.203.233:9300/order-service/order/create 

拦截器

SpringCloud Gateway的全部filter都在文档上有,想要概览一遍可以看看官网文档

前缀分离

现在你应该会发现一个问题,我们uri上的order-servidce只是用来区分请求不同的服务的,真正的请求uriorder/create,怎么转发请求的时候也给带上了这个前缀呢?那么想要解决这个问题的话一起来看看Spring网关自带的filter

想要实现上面的需求,把一个请求的几个前缀给去掉,可以使用The StripPrefix GatewayFilter Factory,这个拦截器很简单,用于剥离请求uri的层级,我们在上方的yml里新增一些配置,感受一下变化:

spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: 'http://192.168.1.233:9300'
          predicates:
            - Path=/order-service/**
          # 新增配置
          filters:
            - StripPrefix=1

我们新增了一个filters配置,这是一个数组

然后下面我们配置了一个叫StripPrefixfilter,这个就是上面说到的StripPrefix GatewayFilter。设置的值为1,则说明我们会剥离一层的uri,最终的请求转发情况如下:

http://192.168.203.233:9500/order-service/order/create 
->
http://192.168.203.233:9300/order/create 

断路器

这样就完美的达到了我们的要求,是不是很开心,但是这还并没有完哦。

好好想想,在分布式系统中,网关作为流量的入口,因此会有大量的请求进入网关,向其他服务发起调用,其他服务不可避免的会出现调用失败(超时、异常),超时会造成网关堆积的请求过多,而失败则需要快速返回失败给客户端,想要实现这个要求,就必须在网关上做熔断、降级操作

这时候该断路器上场了,同样的SpringCloud Gateway也提供了这么一个Spring Cloud CircuitBreaker GatewayFilter Factory拦截器。这个拦截器需要有断路器的实现(impl)来配合,这里用的是SpringCloud新的断路器Resilience4J Circuit Breakers,想要用这个断路器还需要一些新的依赖

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
</dependency>

这是circuitbreakerresilience4j实现

新增配置:

  • 自定义一个断路器(可不做):

    @Configuration
    public class MyCircuitBreakerConfig {
    
        private static final String SLOW_NAME = "SLOW";
    
        @Bean
        public Customizer<ReactiveResilience4JCircuitBreakerFactory> slowCusomtizer() {
            return factory -> {
                factory.configure(builder -> builder
                        .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofSeconds(2)).build())
                        .circuitBreakerConfig(CircuitBreakerConfig.ofDefaults()), SLOW_NAME);
            };
        }
    }
    

    可以看到我们这个自定义的断路器只是改了一下超时时间为2s(默认1s),并且给定idSLOW,其他的保持默认

  • 配置一下断路器的快速返回的内容是什么

    @RestController
    @RequestMapping("/fallback")
    public class FallbackController {
    
        @RequestMapping("/order")
        public String fallback() {
            return "this request is break";
        }
    
    }
    

    这是一个标准的Spring接口

  • 重新回到yml配置

spring:
 cloud:
   gateway:
     routes:
       - id: order-service
         uri: 'http://192.168.1.233:9300'
         predicates:
           - Path=/order-service/**
     filters:
       - StripPrefix=1
       # 新增配置
       - name: CircuitBreaker
         args:
           name: SLOW
           fallbackUri: 'forward:/fallback/order'
  • 第一个name表示的使用的filter是什么,这里当然是CircuitBreaker断路器
  • args.name表示使用的断路器的id是什么,我这里使用了上面配置的id。实测如果这个id的断路器配置不存在的话,会使用默认配置
  • args.fallbackUri则是当发生熔断时跳转的url请求地址,就是上面定义的地址

这样就配置好了断路器,接下来测试一下是否成功。

  • 先把order-service这个服务关掉
  • 访问网关的http://192.168.203.233:9500/order-service/order/create
  • 等待2s直接返回this request is break(或者你定义的响应)则表示成功

请求限流

限流使用到了redis,这里加入spring-boot-starter-data-redis-reactive

pom.xml新增:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

看官网介绍是需要一个key生成的方法,用于标识一个请求,后续的里程碑版本会提供一些可直接使用的key生成类,但是现在没有,我们尝试自己来写一个

java配置代码如下:

@Configuration
public class MyLimitConfig {

    @Bean
    KeyResolver limitKeyResolver() {
        return exchange -> Mono.just(
                Optional.ofNullable(exchange.getRequest().getRemoteAddress()).
                        orElse(InetSocketAddress.createUnresolved("127.0.0.1", 80)).
                        getHostName()
        );
    }

}

这里单纯的用一个ip地址来表示一个请求

然后是yml的配置:

spring:
  redis:
    host: 192.168.1.104
    port: 6379
    password: pw123456
    database: 0
    timeout: 10S
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: 'http://192.168.1.233:9300'
          predicates:
            - Path=/order-service/**
          filters:
            - StripPrefix=1
            - name: CircuitBreaker
              args:
                name: SLOW
                fallbackUri: 'forward:/fallback/order'
            # 新增
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 2
                redis-rate-limiter.requestedTokens: 1
                key-resolver: '#{@limitKeyResolver}'

可以看到增加了一个RequestRateLimiter拦截器,另外Spring这里的限流算法用的是令牌桶算法,不了解可以网络自己搜索了解一下

四个参数:

  • redis-rate-limiter.replenishRate:表示的是每秒生成的令牌数量
  • redis-rate-limiter.burstCapacity:表示的是令牌桶的大小
  • redis-rate-limiter.requestedTokens:表示一个请求消耗几个令牌
  • key-resolver:生成key的方法,使用的是SpringEL表达式

这样配置完成后,当超过请求限制,比如同一秒不同的ip请求3次的话,第三次会返回429的状态。

结束

Spring的网关在对比zuul的话,zuul1使用的是同步iozuul2则和springcloud gateway一样使用的是netty。另外spring的网关有很多开箱即用的router,filter。我觉得以后Spring的网关应该会成为主流。