1、当我们需要处理诸如接口限流或监控告警等功能时,会使用滑动窗口来使我们的统计会比较平滑,在单机应用下可以有很多办法实现,而分布式应用就无法使用前述办法了,不过也不必太担心,分布式下通过redis的zset数据结构能非常方便的实现滑动窗口。众所周知,zset存在分数score的概念,并可通过score进行一系列的操作,比如排序,区间查询等。因此我们可以将接口的请求时间戳作为zset的score,然后对zset进行操作。

2、为了让一系列针对zset的操作满足原子性,我们需要使用lua脚本进行操作,具体如下:

//定义一个数组接收返回结果,注意lua中数组下标从1开始
local result = {}
//往zset中插入一个元素,ARGV[1]为时间戳,ARGV[2]为一个随机值或其他值,千万不能重复
//否则会覆盖其他值
redis.call('zadd',KEYS[1],ARGV[1],ARGV[2])
//获取当前zset中的最大score
local max = redis.call('zrange',KEYS[1],-1,-1,'withscores')
//通过上一步获取到的最大的score来删除zset中一个滑动窗口之外的所有元素
//保证当前zset中只存在一个周期的数据,这就是能实现滑动窗口的原理,
//ARGV[3]是一个周期的大小
redis.call('zremrangebyscore',KEYS[1],max[2] - 3*ARGV[3],max[2] - ARGV[3] - 1)
//返回score最小的元素,也即是当前周期内最早调用接口的记录,这里会返回两个值
//一个是value,一个score,因此res1是一个数组
local min = redis.call('zrange',KEYS[1],0,0,'withscores')
//设置zset的过期时间,最短一个周期的长度,建议稍微长一点,比如两个周期
redis.call('expire',KEYS[1],ARGV[4])
//获取当前zset的元素个数,也即是当前周期的调用量,我们便可以用该值进行一些计算
//比如判断是否超出接口限流的限制大小
local res2 = redis.call('zcard',KEYS[1])
//将上面获取到的zset中最小的score保存到数组中
result[1] = min[2]
//将上面获取到的zset中最大的score保存到数组中
result[2] = max[2]
//将zset的元素个数保存到数组中
result[3] = res2
//返回数组
return result

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐