常见的限流策略:
访问时incr一个用户标识,给这个值设置一个过期时间,下一次先判断值是否达到限制,没有的话请求完成后incr同时更新过期时间。
这种方法有很多弊端,最明显的就是过期时间。比如限制5秒内只能访问2次,第一次访问后3秒后访问第二次,过期时间又更新成了5秒,这样实际效果就是限制8秒内访问3次,不符合要求。
游标式时间限流策略:
需要一个滑动时间窗口的概念,这个窗口大小是恒定的,跟着时间推移滑动,窗口阈值内请求不超过规定次数;
使用zset(有序集合)结构,key是用户标识,score和value是当前毫秒时间戳,移除窗口时间外的值,判断剩余的记录数是否超过限制。
用到的主要命令:
zadd : 将一个或多个成员元素及其分数值加入到有序集当中
zremrangebyscore : 移除有序集中,指定分数(score)区间内的所有成员
zcard: 计算集合中元素的数量
python实现:
def is_action_allowed(user_id, action_key, period, max_count):
key = 'hist:%s:%s' % (user_id, action_key)
now_ts = int(time.time() * 1000) # 毫秒时间戳
with client.pipeline() as pipe: # client 是 StrictRedis 实例
# 记录行为
pipe.zadd(key, now_ts, now_ts) # value 和 score 都使用毫秒时间戳
# 移除时间窗口之前的行为记录,剩下的都是时间窗口内的:(当前时间-阈值)之前的记录都删掉
pipe.zremrangebyscore(key, 0, now_ts - period * 1000)
# 获取窗口内的行为数量
pipe.zcard(key)
# 设置 zset 过期时间,避免冷用户持续占用内存
# 过期时间应该等于时间窗口的长度,再多宽限 1s
pipe.expire(key, period + 1)
# 批量执行
_, _, current_count, _ = pipe.execute()
# 比较数量是否超标
return current_count <= max_count
网友评论