SpringCloud Gateway 熔断降级和分布式限流介绍

2021-10-08 0 By admin

SpringCloud Gateway 服务中熔断降级和分布式限流都是对用户请求量控制的手段,用于保护服务的稳定性。

一、熔断降级

1.1、为什么要实现熔断降级?

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

1.2、为什么在网关上请求失败需要快速返回给客户端?

因为当一个客户端请求发生故障的时候,这个请求会一直堆积在网关上。
当然只有一个这种请求,网关肯定没有问题(如果一个请求就能造成整个系统瘫痪,那这个系统可以下架了),但是网关上堆积多了就会给网关乃至整个服务都造成巨大的压力,甚至整个服务宕掉。
因此要对一些服务和页面进行有策略的降级,以此缓解服务器资源的的压力,以保证核心业务的正常运行,同时也保持了客户和大部分客户的得到正确的相应,所以需要网关上请求失败需要快速返回给客户端。

1.3、熔断降级配置示例

server.port: 8082

spring:
  application:
    name: gateway
  redis:
      host: localhost
      port: 6379
      password: 123456
  cloud:
    gateway:
      routes:
        - id: rateLimit_route
          uri: http://localhost:8000
          order: 0
          predicates:
            - Path=/test/**
          filters:
            - StripPrefix=1
            - name: Hystrix
              args:
                name: fallbackCmdA
                fallbackUri: forward:/fallbackA

  hystrix.command.fallbackCmdA.execution.isolation.thread.timeoutInMilliseconds: 5000

这里的配置,使用了两个过滤器:

1、过滤器StripPrefix,作用是去掉请求路径的最前面n个部分截取掉。

StripPrefix=1就代表截取路径的个数为1,比如前端过来请求/test/good/1/view,匹配成功后,路由到后端的请求路径就会变成http://localhost:8888/good/1/view。

2、过滤器Hystrix,作用是通过Hystrix进行熔断降级

当上游的请求,进入了Hystrix熔断降级机制时,就会调用fallbackUri配置的降级地址。需要注意的是,还需要单独设置Hystrix的commandKey的超时时间。

fallbackUri配置的降级地址的代码如下:

package org.gateway.controller;

import org.gateway.response.Response;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FallbackController {

    @GetMapping("/fallbackA")
    public Response fallbackA() {
        Response response = new Response();
        response.setCode("100");
        response.setMessage("服务暂时不可用");
        return response;
    }
}

二、分布式限流

从某种意义上讲,令牌桶算法是对漏桶算法的一种改进。
桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。
在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。
放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。
所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。

令牌桶限流流程

在Spring Cloud Gateway中,有Filter过滤器,因此可以在“pre”类型的Filter中自行实现上述三种过滤器。
但是限流作为网关最基本的功能,Spring Cloud Gateway 官方就提供了 RequestRateLimiterGatewayFilterFactory这个类,适用在 Redis 内的通过执行 Lua 脚本实现了令牌桶的方式。
具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中,lua脚本在如下图所示的文件夹中:

SpringCloud Gateway Redis Lua
SpringCloud Gateway Redis Lua

首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖,代码如下:

2.1、Gateway 分布式限流 Redis Lua 示例

1、配置如下

server:
  port: 8081
spring:
  cloud:
    gateway:
      routes:
      - id: limit_route
        uri: http://httpbin.org:80/get
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: '#{@userKeyResolver}'
            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 3
  application:
    name: cloud-gateway
  redis:
    host: localhost
    port: 6379
    database: 0

在上面的配置文件,指定程序的端口为8081,配置了 redis的信息,并配置了RequestRateLimiter的限流过滤器,该过滤器需要配置三个参数:

  1. burstCapacity,令牌桶总容量。
  2. replenishRate,令牌桶每秒填充平均速率。
  3. key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

2、这里根据用户ID限流,请求路径中必须携带userId参数

@Bean
KeyResolver userKeyResolver() {
  return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}

KeyResolver需要实现resolve方法,比如根据userid进行限流,则需要用userid去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。

3、如果需要根据IP限流,定义的获取限流Key的bean为:

@Bean
public KeyResolver ipKeyResolver() {
  return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}

通过exchange对象可以获取到请求信息,这边用了HostName,如果你想根据用户来做限流的话这边可以获取当前请求的用户ID或者用户名就可以了,比如:

4、如果需要根据接口的URI进行限流,则需要获取请求地址的uri作为限流key,定义的Bean对象为:

 
@Bean
KeyResolver apiKeyResolver() {
  return exchange -> Mono.just(exchange.getRequest().getPath().value());
}

通过exchange对象可以获取到请求信息,这边用了HostName,如果你想根据用户来做限流的话这边可以获取当前请求的用户ID或者用户名就可以了,比如:

5、如果需要根据接口的URI进行限流,则需要获取请求地址的uri作为限流key,定义的Bean对象为:

@Bean
KeyResolver apiKeyResolver() {
  return exchange -> Mono.just(exchange.getRequest().getPath().value());
}