SpringBoot 项目使用 Redis 对方法进行接口限流

一、思考

  1. 如何防止接口被恶意打击(短时间内大量请求)
  2. 如何限制接口规定时间内访问次数

二、解决方法

1. 使用 String结构 记录固定时间段内某用户IP访问某接口的次数

  • RedisKey = prefix : className : methodName
  • RedisVlue = 访问次数

拦截请求:

  1. 初次访问时设置 RedisKey RedisValue=1 规定的过期时间
  2. 获取 RedisValue 是否超过规定次数,超过则拦截,未超过则对 RedisKey 进行加1

分析: 规则是每分钟访问 1000 次

  1. 考虑并发问题
    • 假设目前 RedisKey => RedisValue 为 999
    • 目前大量请求进行到第一步( 获取Redis请求次数 ),那么所有线程都获取到了值为999,进行判断都未超过限定次数则不拦截,导致实际次数超过 1000 次
    • 解决办法: 保证方法执行原子性(加锁、lua)
  2. 考虑在临界值进行访问
    • 思考下图

代码实现: 比较简单(可参考若依代码 gitee.com/y_project/R...

2. 使用 Zset 进行存储,解决临界值访问问题

  • RedisKey = prefix : className : methodName
  • RedisScore = 时间搓
  • RedisValue = 任意分布式不重复的值即可

如果开发者只能配置单个规则,可以使用

less 复制代码
// 删除 score 在 【min ~ max】 中的 member
Zremrangebyscore [key] [min] [max]
// 统计某个key的member总数
ZCARD [key]

如果开发者需要配置多个规则,则需要可能需要的命令

less 复制代码
// 统计在 score 在 【min ~ max】之前的个数 (用于判断多个规则)
ZCOUNT [key] [min] [max]
// 删除 score 在 【min ~ max】 中的 member (删除超过最长时间的数据)
Zremrangebyscore [key] [min] [max]

以下以 lua 代码进行编写

  • KEYS1 = prefix : ? : className : methodName
  • KEYS2 = 唯一ID
  • KEYS3 = 当前时间
  • ARGV = 次数,单位时间,次数,单位时间, 次数, 单位时间 ...
lua 复制代码
-- 1. 获取参数
local key = KEYS[1]
local uuid = KEYS[2]
local currentTime = tonumber(KEYS[3])
-- 2. 以数组最大值为 ttl 最大值
local expireTime = -1;
-- 3. 遍历数组查看是否越界
for i = 1, #ARGV, 2 do
    local rateRuleCount = tonumber(ARGV[i])
    local rateRuleTime = tonumber(ARGV[i + 1])
    -- 3.1 判断在单位时间内访问次数
    local count = redis.call('ZCOUNT', key, currentTime - rateRuleTime, currentTime)
    -- 3.2 判断是否超过规定次数
    if tonumber(count) >= rateRuleCount then
        return true
    end
    -- 3.3 判断元素最大值,设置为最终过期时间
    if rateRuleTime > expireTime then
        expireTime = rateRuleTime
    end
end
-- 4. redis 中添加当前时间
redis.call('ZADD', key, currentTime, uuid)
-- 5. 更新缓存过期时间
redis.call('PEXPIRE', key, expireTime)
-- 6. 删除最大时间限度之前的数据,防止数据过多
redis.call('ZREMRANGEBYSCORE', key, 0, currentTime - expireTime)
return false

以下为第二种lua脚本编写方式

  • KEYS1 = prefix : ? : className : methodName
  • KEYS2 = 当前时间
  • ARGV = 次数,单位时间,次数,单位时间, 次数, 单位时间 ...

以下为第二种实现方式,在并发高的情况下效率低,value是通过时间搓进行添加,但是访问量大的话会使得一直在调用 redis.call('ZADD', key, currentTime, currentTime),但是在不冲突value的情况下,会比生成 UUID 好

lua 复制代码
-- 1. 获取参数
local key = KEYS[1]
local currentTime = KEYS[2]
-- 2. 以数组最大值为 ttl 最大值
local expireTime = -1;
-- 3. 遍历数组查看是否越界
for i = 1, #ARGV, 2 do
    local rateRuleCount = tonumber(ARGV[i])
    local rateRuleTime = tonumber(ARGV[i + 1])
    -- 3.1 判断在单位时间内访问次数
    local count = redis.call('ZCOUNT', key, currentTime - rateRuleTime, currentTime)
    -- 3.2 判断是否超过规定次数
    if tonumber(count) >= rateRuleCount then
        return true
    end
    -- 3.3 判断元素最大值,设置为最终过期时间
    if rateRuleTime > expireTime then
        expireTime = rateRuleTime
    end
end
-- 4. 更新缓存过期时间
redis.call('PEXPIRE', key, expireTime)
-- 5. 删除最大时间限度之前的数据,防止数据过多
redis.call('ZREMRANGEBYSCORE', key, 0, currentTime - expireTime)
-- 6. redis 中添加当前时间  ( 解决多个线程在同一毫秒添加相同 value 导致 Redis 漏记的问题 )
-- 6.1 maxRetries 最大重试次数 retries 重试次数
local maxRetries = 5
local retries = 0
while true do
    local result = redis.call('ZADD', key, currentTime, currentTime)
    if result == 1 then
        -- 6.2 添加成功则跳出循环
        break
    else
        -- 6.3 未添加成功则 value + 1 再次进行尝试
        retries = retries + 1
        if retries >= maxRetries then
            -- 6.4 超过最大尝试次数 采用添加随机数策略
            local random_value = math.random(1, 1000)
            currentTime = currentTime + random_value
        else
            currentTime = currentTime + 1
        end
    end
end

return false

第一次编写文章,有很多写的不好的地方请大家指出,java代码👉 RateLimiterAspect.java

转载请附带本文链接

相关推荐
码兄科技20 小时前
实战:基于Spring Boot + UniApp的地理信息小程序开发
spring boot·后端·uni-app
腾讯云云开发1 天前
腾讯云 CloudBase 登上 WAIC:我们为 Agent 重新设计了云的生产线
后端
星栈1 天前
从装一堆工具到看懂 Node 工程化思维:我的项目复盘记录
后端·node.js
65岁退休Coder1 天前
LangChain v1.3.4 笔记 - 05 Agent 上下文记忆
后端
颜酱1 天前
04 | 召回前置准备:搭好召回所需的四个数据库
前端·人工智能·后端
JaneConan1 天前
鸿蒙 韶非 UI 系列:能力调用 startAbilityForResult,跳能力拿回参,鸿蒙能力路由入门
后端·harmonyos
晴空了无痕1 天前
从 Go 基础到 K8s:一条可落地的 Go 服务端成长路线
开发语言·后端·golang·kubernetes
用户8356290780511 天前
如何使用 Python 在 Excel 中添加、编辑和删除超链接
后端·python
花椒技术1 天前
原本要 2 天的服务端冒烟前置审查,为什么 3 分半就能出报告?|QA 质量交付实践(三)
后端·ai编程·测试
达达尼昂1 天前
AI 编程的工程化实践:Flutter AI Harness 的设计与落地
人工智能·后端·全栈