SpringCloud Gateway RequestRateLimiter

本文介绍了如何在Spring Cloud Gateway中使用Redis和Lua脚本实现基于令牌桶算法的请求限流。配置包括了网关的默认过滤器、KeyResolver、Redis设置以及Lua脚本的详细解释,确保了分布式限流的正确性和一致性。限流响应头包含了相关信息,如剩余令牌数和补充速率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:
API Rate Limiting with Spring Cloud Gateway
Spring Microservices Security Best Practices

示例配置

按照key-resolver解析出的id(字符串key,用于唯一区分用户、IP等等)进行请求限流,
限流算法采用基于redis lua脚本实现的令牌桶算法,具体配置见如下说明:

server:
  port: 8088
spring:
  application:
    name: mx-gateway-opt
  cloud:
    # 网关配置
    gateway:
      # 默认过滤器(对所有route均生效)
      default-filters:
        # 请求限速配置
        - name: RequestRateLimiter
          args:
            # 如果keyResolver返回空key,则拒绝该请求403,默认true表示拒绝,false则表示允许访问
            deny-empty-key: false
            # 令牌桶算法每秒补充的token数量(每秒的请求数量)
            redis-rate-limiter.replenishRate: 10
            # 令牌桶算法token最大数量(每秒的最大请求数量)
            redis-rate-limiter.burstCapacity: 15
            # 单次请求消费的token数量
            redis-rate-limiter.requestedTokens: 1
            # 自定义的KeyResolver(从请求exchange解析id,用于区分限流的独立单元,如用户ID、remoteAddr、sessionId等)
            key-resolver: "#{@begRateLimiterKeyResolver}"
  # redis配置
  redis:
    database: 2
    host: localhost
    password: mypassw
    port: 6379
    timeout: 3000
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: -1
        min-idle: 0

keyresovler默认实现

org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver

package org.springframework.cloud.gateway.filter.ratelimit;
import reactor.core.publisher.Mono;
import org.springframework.web.server.ServerWebExchange;
public class PrincipalNameKeyResolver implements KeyResolver {

	/**
	 * {@link PrincipalNameKeyResolver} bean name.
	 */
	public static final String BEAN_NAME = "principalNameKeyResolver";

	@Override
	public Mono<String> resolve(ServerWebExchange exchange) {
		return exchange.getPrincipal().flatMap(p -> Mono.justOrEmpty(p.getName()));
	}

}

RequestRateLimiterGatewayFilterFactory

在这里插入图片描述

RequestRateLimiterGatewayFilterFactory调用链路
RequestRateLimiterGatewayFilterFactory
-> RedisRateLimiter -> META/scripts/request_rate_limter.lua

request_rate_limiter.lua
lua脚本保证redis单线程执行,解决分布式网关限流问题(避免多个网关同时调用redis导致数据混乱)

--keys参数:
--request_rate_limiter.{id}.tokens
--request_rate_limiter.{id}.timestamp
local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)

--对应配置:
--redis-rate-limiter.replenishRate: 每秒需要补充的token数量
--redis-rate-limiter.burstCapacity: 令牌桶中token的最大容量
--当前时间戳(秒)
--redis-rate-limiter.requestedTokens: 单次请求消耗的token数量
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
--redis缓存到期时间(2倍且向下取整),
--即超过ttl时间没人访问(则redis缓存tokens_key, timestamp_key失效不存在),
--则默认令牌桶为满的状态(capacity容量)
local ttl = math.floor(fill_time*2)

--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])
--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])
--redis.log(redis.LOG_WARNING, "now " .. ARGV[3])
--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])
--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)
--redis.log(redis.LOG_WARNING, "ttl " .. ttl)

--获取之前(上次访问后)剩余的token数量(默认capacity)
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
  last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)

--获取上次访问的时间戳(默认为0)
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
  last_refreshed = 0
end
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)

--距离上次访问已经过去的秒数
local delta = math.max(0, now-last_refreshed)
--当前时间点补充后的token数量(默认capacity,且最多不操作capacity)
--之前剩下的token数量+上次访问后到现在为止需要添加的token数量delta*rate
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
--是否允许访问(补充后的token数量 >= 当前单次请求消耗的token数量)
local allowed = filled_tokens >= requested
--补充后的token数量
local new_tokens = filled_tokens
--是否允许访问 
local allowed_num = 0
--若允许访问
if allowed then
  --当前请求过后剩余的token数量=补充后的token数量-当前请求消耗的token数量
  new_tokens = filled_tokens - requested
  --标记允许访问
  allowed_num = 1
end

--redis.log(redis.LOG_WARNING, "delta " .. delta)
--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)
--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)
--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)

--记录令牌桶tokens_key, timestamp_key
--超过ttl(需要几秒即可填充满令牌桶)后缓存失效,key不存在则默认为满的状态
--例如rate=100, capacity=150, 则ttl=Math.floor((150/100)*2)=3,
--即超过3秒没人访问,则令牌桶会自动被填充满(key失效为空则表示满状态)
if ttl > 0 then
  redis.call("setex", tokens_key, ttl, new_tokens)
  redis.call("setex", timestamp_key, ttl, now)
end

-- return { allowed_num, new_tokens, capacity, filled_tokens, requested, new_tokens }
return { allowed_num, new_tokens }

lua脚本调用参数debug截图
在这里插入图片描述

redis缓存截图
在这里插入图片描述

限流相关的响应header

在这里插入图片描述

# burst容量(令牌桶1秒时间内最大容量)
X-RateLimit-Burst-Capacity: 1
# 当前1秒内剩余token数据量
X-RateLimit-Remaining: 0
# 每秒补充的token数量
X-RateLimit-Replenish-Rate: 1
# 当前请求需要消费的token数量
X-RateLimit-Requested-Tokens: 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗小爬EX

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值