Spring Gateway 网关 限流

Spring Gateway 网关 限流

Spring Gateway 提供了很多开箱即用的 Filter, 今天我们来介绍一下他的限流 RequestRateLimiter , 由于网关是所有请求的入口所以网关统一做限流是最合适的。

首先什么是限流? 为什么需要限流? 限流有两种一种是限制总的QPS, 比如10000 QPS 每秒处理10000个请求,多的就拒绝防止系统过载。 还有一种是结合业务关键字的限流, 想象一下你开了个餐馆,一天能做100个包子,你希望更多的客人品尝到你的包子,可是经常有客人一次购买50个包子,其他排队的客人就没有机会了,于是你限制每人最多购买5个包子。 第一种限流只是限制总的请求量,防止系统过载崩溃,但是想象一下,有人用脚本攻击你的系统,发出大量的请求,流量被占满触发限流,这时候其他正常用户的请求会被拒绝,你的服务虽然没有宕机,但是也无法提供服务,服务不可用和宕机没睡吗区别。 第二种他回根据一些用户标示,位每个不同的用户设置限流,比人一个用户 30个请求/秒,这样防止系统被个别用户请求占满而导致其他正常用户不可用。 spring gateway RequestRateLimiter 就可以实现对用户维度的限流。 使用 RequestRateLimiter 需要我们实现一个接口 :

public interface KeyResolver {

    Mono<String> resolve(ServerWebExchange exchange);

}

 

这个接口很简单就是需要我们返回一个key, 以这个key 作为维度限流, 以前的文章讲过spring gateway 统一制作 jwt 用户身份鉴权,每次请求头都会携带 Authorization token,每个用户登录后都会签发一个token,可以用token 作为一个用户的唯一标示

实现如下:

@Component
public class TokenKeyResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        
        String authorization = exchange.getRequest().getHeaders().getFirst(AUTHHEADER);
        if(org.apache.commons.lang.StringUtils.isNotBlank(authorization)){
            return Mono.just(authorization);
        }
        return Mono.empty();
        
    }

 

spring gateway 的限流需要用到 redis,具体实现原理可以参考 https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d ,

使用的request_rate_limiter.lua脚本实现的令牌桶算法:

local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]

local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)

local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
  last_tokens = capacity
end

local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
  last_refreshed = 0
end

local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
  new_tokens = filled_tokens - requested
end

redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)

return { allowed, new_tokens }

我们需要添加依赖来连接redis

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

后面就只需要添加配置就可以了

 

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace:
        ip:

    gateway:
      redis-rate-limiter:
        includeHeaders: false
      filter:
        request-rate-limiter:
          deny-empty-key: false
#          empty-key-status-code: 200
      default-filters:
        - name: RequestRateLimiter
          args:
            # 令牌桶每秒填充平均速率
            redis-rate-limiter.replenishRate: 15
            # 令牌桶的上限
            redis-rate-limiter.burstCapacity: 30
            redis-rate-limiter.requestedTokens: 1
            # 使用SpEL表达式从Spring容器中获取Bean对象
            key-resolver: "#{@tokenKeyResolver}"

 

启动测试 可以用wrk 来测试,超过请求限制,就会返回 HTTP 429 - Too Many Requests

posted @ 2023-01-13 15:54  然_默  阅读(571)  评论(0编辑  收藏  举报