springcloudgateway RequestRateLimiter Redis 限流

前提,已集成springcloudgateway相关pom,如lettuce-core、spring-cloud-starter-gateway等基础pom

1、从限流需要的基础pom开始:

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

我使用的springboot版本:2.1.4.RELEASE      springcloud版本:Greenwich.RELEASE

2、config编写,这个一般网上搜索有3种,path、host、请求参数如:user

@Configuration
public class LimitingConfig {
    /**
     * 使用请求 IP 作为限流键
     *
     * @return key
     */

    @Bean(name = "hostKeyResolver")
    KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

3、配置文件(以properties为例,可自行转换yml)

server.port=9001
spring.application.name=limitService

# redis配置
spring.redis.host=127.0.0.1
spring.redis.password=redis
spring.redis.port=6379
spring.redis.database=0

#routes[id] id数值以自身项目为准

# 使用请求来源 IP 作为限流键
spring.cloud.gateway.routes[4].id=limitRouter
# 如果是用注册中心可以直接写应用名,如果不是可写url地址如 http://127.0.0.1:9002
spring.cloud.gateway.routes[4].uri=lb://limitService
# 请求路径为/api/limit/get
spring.cloud.gateway.routes[4].predicates[0]=Path=/api/limit/get
# 通过截取1层,实际访问的controller接口地址为/limit/get
spring.cloud.gateway.routes[4].filters[0]=StripPrefix=1
# 不可更改
spring.cloud.gateway.routes[4].filters[1].name=RequestRateLimiter
# 使用SpEL表达式从Spring容器中获取Bean对象
spring.cloud.gateway.routes[4].filters[1].args.key-resolver=#{@hostKeyResolver}
# 令牌桶每秒填充平均速率
spring.cloud.gateway.routes[4].filters[1].args.redis-rate-limiter.replenishRate=1
# 令牌桶的上限   突发周期为 burstCapacity(2) / replenishRate(1)秒,如果周期内有2/1次请求突发的情况,则第2/1次会有部分请求丢失,返回HTTP 429 - Too Many Requests
spring.cloud.gateway.routes[4].filters[1].args.redis-rate-limiter.burstCapacity=2

4、如果使用单机redis可使用压测工具直接压测访问 http://127.0.0.1:9001/api/limit/get 接口,如使用上例中配置,超过2个请求,后面的将会返回429状态码

5、如果使用的是阿里云集群,默认request_rate_limiter.lua执行存在问题,

--在debug情况下,会有以下2个错误
-- NOSCRIPT No matching script. Please use EVAL.
-- ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, and KEYS should not be in expression

解决方法:需要在自身项目对应目录(META-INF/scripts/request_rate_limiter.lua,源码:GatewayRedisAutoConfiguration.java)下重写该文件,覆盖jar中的原始文件修改如下:

--踩坑参照 https://blog.csdn.net/qq_33996921/article/details/107204362
--错误
--NOSCRIPT No matching script. Please use EVAL.
--ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, and KEYS should not be in expression
--注释掉 tokens_key、timestamp_key 在引用的地址直接填写数组


--local tokens_key = KEYS[1]
--local timestamp_key = KEYS[2]
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)

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)

--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)

local last_tokens = tonumber(redis.call("get", KEYS[1]))
if last_tokens == nil then
  last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)

local last_refreshed = tonumber(redis.call("get", KEYS[2]))
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)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
  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)

redis.call("setex", KEYS[1], ttl, new_tokens)
redis.call("setex", KEYS[2], ttl, now)

return { allowed_num, new_tokens }

参考资料:

https://blog.csdn.net/qq_33996921/article/details/107204362

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值