不知道ZSet(有序集合)的看官们,可以翻阅我的上一篇文章:
小白也能看懂的REDIS教学基础篇——朋友面试被SKIPLIST跳跃表拦住了
书接上回,话说我朋友小A童鞋,终于面世通过加入了一家公司。这个公司待遇比较丰厚,而且离小A住的地方也比较近,最让小A中意还是有个肯带他的大佬。小A对这份工作非常满意。时间一天一天过去,某个周末,小A来找我家吃蹭饭.在饭桌上小A给我分享了他上星期的一次事故经历。
上个星期,他们公司出了比较严重的一个事故,一个导出报表的后台服务拖垮了报表数据服务,导致很多查询该服务的业务都受到了牵连。主要原因是因为导出的报表数据比较多,导致导出时间比较漫长。前端也未针对导出按钮做防重试限制。多个运营人员多次点击了导出按钮。加上后台服务配置的重试机制把这个流量放大了好几倍,最后拖垮了整个报表数据服务。老大让小A出个集群限流的方案防止下次在出现这类问题。
这下小A慌了,单机限流好搞,使用 Hystrix 框架注解一加就完事。或者使用 Sentinel 在 Sentinel Dashboard 后台配置一下就完事。集群限流要怎么弄?小A苦思冥想了一个上午也没整出来,最后只能求助大佬帮助。
小A:大佬,老大让我出一个集群限流的方案,我以前对这个不熟,网站找的一堆,都是重复相同的感觉不靠谱,能教教我怎么弄吗?
大佬:莫慌莫慌,这件事情其实不难。我先考考你,限流的算法有哪些?
小A:... 我想想
小A:有了,常见的限流算法有以下三种:滑动窗口算法,令牌桶算法,漏桶算法。
大佬:对头,那你觉得单机限流和集群限流有什么区别呢?
小A:emmm...
大佬:你可以从集群和单机程序本身的区别去想想。
小A:我知道了,单机限流限流数据存在单机上,只能一个机器用。而集群是分布式多机器的,要让多个机器共享同一份限流数据,才能保证多机器的限流。
大佬:很好,那你还记得面试时候我问你的Zset(Sorted Sets)吗?用它就能很简单的实现一个滑动时间窗哦。
小A:大佬快教我!
大佬:那话不多说,现在进入正题。
如果不知道Zset数据结构的,可以先去看看我的这篇文章
小白也能看懂的REDIS教学基础篇——朋友面试被SKIPLIST跳跃表拦住了
首先来看一段实现滑动时间窗的lua代码(这是实现Redis滑动时间窗限流的核心代码)
-- 参数:
-- nowTime 当前时间
-- windowTime窗口时间
-- maxCount最大次数
-- expiredWindowTime 已经过期的窗口时间
-- value 请求标记
local nowTime = tonumber(ARGV[1]);
local windowTime = tonumber(ARGV[2]);
local maxCount = tonumber(ARGV[3]);
local expiredWindowTime = tonumber(ARGV[4])
local value = ARGV[5];
local key = KEYS[1];
-- 获取当前窗口的请求标志个数
local count = redis.call('ZCARD', key)
-- 比较当前已经请求的数量是否大于窗口最大请求数
if count >= maxCount then
-- 如果大于最大请求数
-- 删除过期的请求标志 释放窗口空间 等同于滑动时间窗口向前滑动
redis.call('ZREMRANGEBYSCORE', key, 0, expiredWindowTime)
-- 再次获取当前窗口的请求标志个数
local count = redis.call('ZCARD', key)
-- 延长过期时间
redis.call('PEXPIRE', key, windowTime + 1000)
-- 比较释放后的大小 是否小于窗口最大请求数
if count < maxCount then
-- 返回200代表成功
return 200
else
-- 返回500代表失败
return 500
end
else
-- 插入当前访问的访问标记
redis.call('ZADD', key, nowTime, value)
-- 延长过期时间
redis.call('PEXPIRE', key, windowTime + 1000)
-- 返回200代表成功
return 200
end