一、前言

1、SpringCloudGateway是SpringCloud新推出的网关框架,比较于上一代Zuul,功能和性能有很大的提升。Zuul1.x采用的是阻塞多线程方式,也就是一个线程处理一个连接请求,高并发情况下性能较差,即使是Zuul2.x虽然做到了非阻塞,但是面对连续跳票,看起来Zuul要被抛弃了。取而代之的是SpringCloudGateway,SpringCloudGateway是基于Webflux,是一个非阻塞异步的框架,性能上有很大提升,而且包含了Zuul的所有功能,可以从Zuul无缝切换到SpringCloudGateway

2、SpringCloud环境版本:Greenwich.RELEASE

3、SpringBoot环境版本:2.1.3.RELEASE

二、环境搭建

1、在父工程下新建一个网关模块

2、引入SpringCloudGateway需要的POM,记得引入actuator组件,否则服务发现中心会认为服务不在线,导致网关无法路由到服务,并且加入熔断组件Hystrix

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 健康检查 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Hystrix -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

3、主函数很简单,加入必要的注解,比如服务发现等

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayCarfacApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(GatewayCarfacApplication.class, args);
    }
}

4、进行SpringCloudGateway的配置,配置可以使用Java代码进行配置或者yml配置,这里使用yml进行配置

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 设置可以路由到其他服务
      routes: # 可以配置多个路由
        - id: service-media-1 # 路由id唯一
          uri: lb://service-media # 可以直接跳转到具体的地址,如果要跳转到其他服务,则填写lb://<服务id>
          predicates:
            - Path=/media/** # 路由规则
          filters:
            - StripPrefix=1 # 不填则无法路由到其他服务
            - AddRequestHeader=X-Request-Foo, Bar
            - name: Hystrix # 添加熔断
              args:
                name: fallbackcmd
                fallbackUri: forward:/test/fallback # 熔断跳转地址

# 熔断超时时间
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 3000

5、上面我们配置了熔断的配置,一旦发生熔断就会跳转到/test/fallback这个地址,下面我们实现一下这个接口,这里简单的返回了error,我们可以自定义处理熔断的逻辑

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @RequestMapping(value = "/fallback", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<String> fallback()
    {
        return new ResponseEntity<>("error.", HttpStatus.OK);
    }
}

6、网关配置好后,再启动一个其他的服务模块,记得添加引入actuator,否则无法被路由到

<!-- Web -->
<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>

7、编写一个HTTP接口,来测试网关的调用,为了检测熔断的效果,这里通过参数来控制接口的响应时间

@Controller
@RequestMapping(value = "/test")
public class TestController
{
    @Value("${server.port}")
    private String port;

    @RequestMapping(value = "/get/{time}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public ResponseEntity<String> get(@PathVariable("time") long time)
    {
        try
        {
            Thread.sleep(time);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        return new ResponseEntity<>(port + " get ok.", HttpStatus.OK);
    }
}

8、在同一注册中心下启动网关和服务,根据我们配置的路由规则/media/**,我们可以这样调用http://127.0.0.1:8501/media/get/100,可以看到正确的路由到了服务

SpringCloud-Greenwich版本新特性探索(1)---SpringCloudGateway-LMLPHP

9、我们将响应时间改成5秒http://127.0.0.1:8501/media/get/5000,超高熔断的检测时间,可以发现接口熔断,并且跳转到了指定的链接

SpringCloud-Greenwich版本新特性探索(1)---SpringCloudGateway-LMLPHP

三、高级特性---断言

1、以上我们实现了SpringCloudGateway的基本使用办法,可以应付大部分应用场景了,我们同时可以细粒度的去改造路由,就使用到了断言Predict,如果不满足我们设置的断言条件,则无法被路由

2、设置断言,我们只需要在predicates下面加入配置即可,要注意如果设置了多个断言,则请求必须满足所有断言才可以被正确路由到

spring:
  cloud:
    gateway:
      routes: # 可以配置多个路由
        - id: service-media-1 # 路由id唯一
          uri: lb://service-media # 可以直接跳转到具体的地址,如果要跳转到其他服务,则填写lb://<服务id>
          predicates:
            # 加入断言

3、常用的一些断言,还有很多其他断言就不一一列举了,当然最常用的可能是Path断言,我们需要通过路径来指定路由

(1)- Header=<key>, <value>(必须有指定的HTTP Header才能路由)

(2)- Cookie=<key>, <value>(必须有指定的Cookie才能路由)

(3)- Host=aaa.bbb.com(请求域名必须是aaa.bbb.com才能路由)

(4)- Method=GET(请求方式必须是Get请求才能路由)

四、高级特性---过滤器

1、通过设置网关的过滤器,我们可以在用户访问的入口增加一些处理,比如鉴权、接口监控等,下面介绍一些常用的过滤器

2、常用的过滤器

(1)- AddRequestHeader=<key>, <value>(增加自定义HTTP请求头)

(2)- SetStatus=401(设置响应的HTTP错误码)

(3)- RedirectTo=302, http://acme.org(重定向到指定链接)

3、自定义过滤器,可以通过代码定制更加灵活的过滤器,下面实现一个简单的接口耗时统计的过滤器,思路:设置两个过滤器,一个前置过滤器用来收集接口开始调用的时间,一个后置过滤器来将结束时间减去开始时间得到接口的耗时时间,并打印出来

(1)前置过滤器工厂,将开始时间写到attributes里面

public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> {

    public PreGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            exchange.getAttributes().put("requestTime", System.currentTimeMillis());
            return chain.filter(exchange);
        };
    }

    public static class Config {

    }

}

(2)后置过滤器工厂,获取attribute里面的开始时间,并用当前时间减去,得到耗时时间,打印

public class PostGatewayFilterFactory extends AbstractGatewayFilterFactory<PostGatewayFilterFactory.Config>
{
    public PostGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
                Long startTime = exchange.getAttribute("requestTime");
                long time = System.currentTimeMillis() - startTime;
                System.out.println("接口耗时时间(ms):"+time);
            }));
        };
    }

    public static class Config {

    }

}

(3)接下来将两个过滤器注入到Spring里面

@Configuration
public class FilterConfig
{
    @Bean
    public PreGatewayFilterFactory preGatewayFilterFactory() {
        return new PreGatewayFilterFactory();
    }

    @Bean
    public PostGatewayFilterFactory postGatewayFilterFactory() {
        return new PostGatewayFilterFactory();
    }
}

(4)前往application.yml进行过滤器的配置,在filter属性下配置上我们的自定义过滤器,根据框架的约定引用过滤器:比如我们的过滤器名称是PreGatewayFilterFactory,那我们引用的名称就是去掉GatewayFilterFactory这个后缀,也就是Pre,具体配置如下

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 设置可以路由到其他服务
      routes: # 可以配置多个路由
        - id: service-media-1 # 路由id唯一
          uri: lb://service-media # 可以直接跳转到具体的地址,如果要跳转到其他服务,则填写lb://<服务id>
          predicates:
            - Path=/media/** # 路由规则
          filters:
            - StripPrefix=1 # 不填则无法路由到其他服务
            - Pre # 自定义过滤器
            - Post # 自定义过滤器

(5)接下来我们调用接口,可以看到耗时时间打在公屏上

五、网关限流

1、我们可以在网关层面做限流的功能,防止高并发时把服务器搞崩,或者应对一些网络攻击等情况

2、SpringCloudGateway为我们提供了一个很方便使用的令牌桶限流,思路:我们设置一个固定大小的令牌桶,如果令牌桶不满,则根据一定的频率向桶里放入令牌,每当有客户端的请求发来,会先从令牌桶里面取令牌,取到了则继续执行,取不到请求则会被拒绝,这样就达到了限流的作用,我们可以灵活地调整令牌补充速率和令牌桶大小,来细粒度的控制限流,我们使用redis来存储令牌,下面是一个简单的demo

(1)引入redis依赖

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

(2)接下来自定义限流的维度,我们可以从域名、uri等维度进行限流

public class HostAddrKeyResolver implements KeyResolver
{
    @Override
    public Mono<String> resolve(ServerWebExchange exchange)
    {
        // 域名维度限流
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
        // uri维度限流
        // return Mono.just(exchange.getRequest().getURI().getPath());
    }
}

(3)将解析器注入到Spring

@Configuration
public class TokenLimitConfig
{
    @Bean
    public HostAddrKeyResolver hostAddrKeyResolver() {
        return new HostAddrKeyResolver();
    }
}

(4)接下来配置限流器的配置,将我们编写的解析器引用,并且配置令牌桶的属性,这里我配置的是,每秒补充一个令牌,令牌桶的大小为3,最后配置redis的参数,用起来非常简单

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 设置可以路由到其他服务
      routes: # 可以配置多个路由
        - id: service-media-1 # 路由id唯一
          uri: lb://service-media # 可以直接跳转到具体的地址,如果要跳转到其他服务,则填写lb://<服务id>
          predicates:
            - Path=/media/** # 路由规则
          filters:
            - StripPrefix=1 # 不填则无法路由到其他服务
            - name: RequestRateLimiter # 配置限流器
              args:
                key-resolver: '#{@hostAddrKeyResolver}' # 自定义限流过滤器
                redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率
                redis-rate-limiter.burstCapacity: 3 # 令牌桶总容量
  redis:
    host: 127.0.0.1
    port: 4000
    password: 123456

(5)配置好后,使用JMeter来压测一下接口,可以发现某些请求返回429错误码,被限流

SpringCloud-Greenwich版本新特性探索(1)---SpringCloudGateway-LMLPHP

04-03 17:15