限流系列文章——滑动窗口限流
1、需求
限定用户的某个行为在指定时间 T 内,只允许发生 N 次。假设 T 为 1 秒钟,N 为 1000 次。
2、常见的错误设计
程序员设计了一个在每分钟内只允许访问 1000 次的限流方案,如下图 01:00s-02:00s 之间只允许访问 1000 次,这种设计最大的问题在于,请求可能在 01:59s-02:00s 之间被请求 1000 次,02:00s-02:01s 之间被请求了 1000 次,这种情况下 01:59s-02:01s 间隔 0.02s 之间被请求 2000 次,很显然这种设计是错误的。
3、滑动窗口算法
3.1 解决方案
指定时间 T 内,只允许发生 N 次。我们可以将这个指定时间 T,看成一个滑动时间窗口(定宽)。我们采用 Redis 的 zset 基本数据类型的 score 来圈出这个滑动时间窗口。在实际操作 zset 的过程中,我们只需要保留在这个滑动时间窗口以内的数据,其他的数据不处理即可。
每个用户的行为采用一个 zset 存储,score 为毫秒时间戳,value 也使用毫秒时间戳(比 UUID 更加节省内存)
只保留滑动窗口时间内的行为记录,如果 zset 为空,则移除 zset,不再占用内存(节省内存)
3.2 pipeline 代码实现
代码的实现的逻辑是统计滑动窗口内 zset 中的行为数量,并且与阈值 maxCount 直接进行比较就可以判断当前行为是否被允许。这里涉及多个 redis 操作,因此使用 pipeline 可以大大提升效率
测试代码:
测试效果:
从测试输出的数据可以看出,起到了限流的效果,从第 11 次以后的请求操作都是失败的,但是这个和我们允许的 5 次误差还是比较大的。这个问题的原因是我们测试 System.currentTimeMillis()的毫秒可能相同,而且此时 value 也是 System.currentTimeMillis()也相同,会导致 zset 中元素覆盖!
修改代码测试:
在循环中睡眠 1 毫秒即可,测试结果符合预期!
3.3 lua 代码实现
我们在项目中使用原子性的 lua 脚步来实现限流的使用会更多,因此这里也提供一个基于操作 zset 的 lua 版本
测试代码不变,大家可以自行测试,记得还是要考虑我们测试的时候 System.currentTimeMillis()相等的问题,不信你输出 System.currentTimeMillis()就知道了!多思考问题,技术其实都在心里!
版权声明: 本文为 InfoQ 作者【李子捌】的原创文章。
原文链接:【http://xie.infoq.cn/article/a171932f9236ab38c578eb66c】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论