简介
市面上很多介绍redis如何实现限流的,但是大部分都有一个缺点,就是只能实现单一的限流,比如1分钟访问1次或者60分钟访问10次这种,但是如果想一个接口两种规则都需要满足呢,我们的项目又是分布式项目,应该如何解决,下面就介绍一下redis实现分布式多规则限流的方式。
思考
- 如何一分钟只能发送一次验证码,一小时只能发送10次验证码等等多种规则的限流
- 如何防止接口被恶意打击(短时间内大量请求)
- 如何限制接口规定时间内访问次数
解决方法
记录某IP访问次数
使用 String结构 记录固定时间段内某用户IP访问某接口的次数
- RedisKey = prefix : className : methodName
- RedisVlue = 访问次数
拦截请求:
- 初次访问时设置 「[RedisKey] [RedisValue=1] [规定的过期时间]」
- 获取 RedisValue 是否超过规定次数,超过则拦截,未超过则对 RedisKey 进行加1
分析: 规则是每分钟访问 1000 次
- 考虑并发问题
假设目前 RedisKey => RedisValue 为 999
目前大量请求进行到第一步( 获取Redis请求次数 ),那么所有线程都获取到了值为999,进行判断都未超过限定次数则不拦截,导致实际次数超过 1000 次
「解决办法:」 保证方法执行原子性(加锁、lua)
- 考虑在临界值进行访问
思考下图
图片
代码实现: 比较简单
参考:https://gitee.com/y_project/RuoYi-Vue/blob/master/ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java。
Zset解决临界值问题
使用 Zset 进行存储,解决临界值访问问题
图片
网上几乎都有实现,这里就不过多介绍
实现多规则限流
先确定最终需要的效果
- 能实现多种限流规则
- 能实现防重复提交
通过以上要求设计注解(先想象出最终实现效果)
编写注解(RateLimiter,RateRule)
编写 RateLimiter 注解。
编写RateRule注解
拦截注解 RateLimiter
- 确定redis存储方式
RedisKey = prefix : className : methodName
RedisScore = 时间戳
RedisValue = 任意分布式不重复的值即可
- 编写生成 RedisKey 的方法
编写lua脚本
编写lua脚本 (两种将时间添加到Redis的方法)。
Zset的UUID value值
UUID(可用其他有相同的特性的值)为Zset中的value值
- 参数介绍
KEYS[1] = prefix : ? : className : methodName
KEYS[2] = 唯一ID
KEYS[3] = 当前时间
ARGV = [次数,单位时间,次数,单位时间, 次数, 单位时间 ...]
- 由java传入分布式不重复的 value 值
根据时间戳作为Zset中的value值
- 参数介绍
KEYS[1] = prefix : ? : className : methodName
KEYS[2] = 当前时间
ARGV = [次数,单位时间,次数,单位时间, 次数, 单位时间 ...]
- 根据时间进行生成value值,考虑同一毫秒添加相同时间值问题
以下为第二种实现方式,在并发高的情况下效率低,value是通过时间戳进行添加,但是访问量大的话会使得一直在调用 redis.call('ZADD', key, currentTime, currentTime),但是在不冲突value的情况下,会比生成 UUID 好
编写 AOP 拦截
以上,欢迎大家提出意见。