写在前面

常用的更平滑的限流算法有两种:漏桶算法和令牌桶算法.
两种限流的算法确有其独到之处,其他实现比如滑动时间窗或者三色速率标记法,其实是“漏桶”与“令牌桶”的变种。要么将“漏桶”容积换成了单位时间,要么是按规则将请求标记颜色进行处理,底层还是“令牌”的思想。

漏桶算法和令牌桶算法的区别

A. 漏桶算法:水(请求)以任意的速度先进入到漏桶里,桶以固定的速度出水,当水流入桶的速度大于水流出的速度,就会直接溢出(拒绝服务),可以看出漏桶算法能强行限制数据的传输速率。

入桶:以任意速率往桶中放入水滴。

出桶:以固定速率从桶中流出水滴。

Spring Security 配置资源白名单 spring gateway ip白名单_redis

B. 令牌桶算法:系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入令牌,如果桶已经满了,令牌就溢出了;如果桶未满,令牌可以累加。当有新的请求时,会拿走一个TOKEN,如果没有TOKEN可以使用了,就会出现阻塞或者拒绝服务。

入桶:以固定速率往桶中放入水滴。

流出:以任意速率从桶中流出水滴。

Spring Security 配置资源白名单 spring gateway ip白名单_spring_02


令牌桶算法:用于保护服务端,主要用来对调用者频率进行限流,目的是不让服务端被压垮。如果服务端本身有处理能力的时候,如果流量突发(实际消费能力强于配置的流量限制=桶大小),那么实际处理速率可以超过配置的限制(桶大小)。

漏桶算法:用于保护调用方,也就是保护所调用的系统。如果调用的第三方系统本身没有保护机制,或者有流量限制的时候,调用速度不能超过他的限制,由于我们不能更改第三方系统,所以只有在主调方控制。这时即使流量突发也必须舍弃,因为消费能力是第三方决定的。

Spring Cloud Gateway的限流

Spring Cloud Gateway 官方提供了 RequestRateLimiterGatewayFilterFactory 过滤器工厂,使用 REDIS 和 LUA 脚本实现了令牌桶的方式。限流规则由KeyResolver接口的具体实现类来决定,常用的规则有:IP、URL、参数等等来进行限流。
官方文档

如何使用

① 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
② 修改/增加配置
在GATEWAY的.YML文件中进行如下配置(主要是REDIS和RequestRateLimiter):
spring:
  redis:
    host: localhost
    port: 6379
    password: 
  cloud:
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true
      routes:
        # 系统模块
        - id: servicex-system
          uri: lb://servicex-system
          predicates:
            - Path=/system/**
          filters:
            - StripPrefix=1
            # 稳定速率是通过把replenishRate(补充令牌速度)和burstCapacity(令牌桶容量)设置为相同的值来实现的;
            # 可通过设置burstCapacity高于replenishRate来允许临时突发流量。在这种情况下,限流器需要在两次突发请求之间留出一段时间(根据replenishRate),因为连续两次突发将导致请求丢失(HTTP 429 - Too Many Requests).
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1     # 令牌桶每秒填充速率, 指的是允许用户每秒执行多少请求,不丢弃任何请求;
                redis-rate-limiter.burstCapacity: 5     # 令牌桶总容量, 指的是用户在一秒钟内允许执行的最大请求数,也就是令牌桶可以保存的令牌数, 如果将此值设置为零将阻止所有请求;
                redis-rate-limiter.requestedTokens: 1   # 指的是每个请求消耗多少个令牌, 默认是1.
                key-resolver: "#{@pathKeyResolver}"     # 指的是限流的时候以什么维度来判断,使用SpEL表达式按名称引用BEAN(REDIS中限流相关的KEY和此处的配置相关).
        # 文件服务
        - id: servicex-file
          uri: lb://servicex-file
          predicates:
            - Path=/file/**
          filters:
            - StripPrefix=1
# 不校验白名单
ignore:
  whites:
    - /auth/logout
    - /auth/login
    - /*/v2/api-docs
    - /csrf
③ 定义限流维度
1. 多个限流维度, 需要使用@Primary指定;
2. 在配置文件中指定的限流维度的名字来自该文件,key-resolver: "#{@pathKeyResolver}"。

@Configuration
public class KeyResolverConfiguration {

    @Primary
    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
    }

    @Bean
    public KeyResolver parameterKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getHeaders().get("username").get(0));
    }

    @Bean
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getURI().getHost());
    }
}
④ 查看数据

在REDIS的DB0数据库中会生成两个KEY,它们的TTL都很短,分别为:
request_rate_limiter.{限流维度名字}.tokens
request_rate_limiter.{限流维度名字}.timestamp

⑤ 使用SWAGGER测试

Spring Security 配置资源白名单 spring gateway ip白名单_spring_03

如何封装429状态码?

问题:我们的代码如何捕获429状态码并进行封装呢?