redis限流

限流是对系统的出入流量进行控制,防止大流量出入,导致资源不足,系统不稳定。

限流系统是对资源访问的控制组件,控制主要的两个功能:限流策略和熔断策略,对于熔断策略,不同的系统有不同的熔断策略诉求,有的系统希望直接拒绝、有的系统希望排队等待、有的系统希望服务降级、有的系统会定制自己的熔断策略,很难一一列举,所以本文只针对限流策略这个功能做详细的设计。

针对找出超出速率阈值的请求这个功能,限流系统中有两个基础概念:资源和策略。

资源 :或者叫稀缺资源,被流量控制的对象;比如写接口、外部商户接口、大流量下的读接口 策略 :限流策略由限流算法和可调节的参数两部分组成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def can_pass_fixed_window(user, action, time_zone=60, times=30):
"""
:param user: 用户唯一标识
:param action: 用户访问的接口标识(即用户在客户端进行的动作)
:param time_zone: 接口限制的时间段
:param time_zone: 限制的时间段内允许多少请求通过
"""
key = '{}:{}'.format(user, action)
# redis_conn 表示redis连接对象
count = redis_conn.get(key)
if not count:
count = 1
redis_conn.setex(key, time_zone, count)
if count < times:
redis_conn.incr(key)
return True

return False

这个方法虽然简单,但有个大问题是无法应对两个时间边界内的突发流量。如上图所示,如果在计数器清零的前1秒以及清零的后1秒都进来了100个请求,那么在短时间内服务器就接收到了两倍的(200个)请求,这样就有可能压垮系统。会导致上面的问题是因为我们的统计精度还不够,为了将临界问题的影响降低,我们可以使用滑动窗口法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def can_pass_slide_window(user, action, time_zone=60, times=30):
"""
:param user: 用户唯一标识
:param action: 用户访问的接口标识(即用户在客户端进行的动作)
:param time_zone: 接口限制的时间段
:param time_zone: 限制的时间段内允许多少请求通过
"""
key = '{}:{}'.format(user, action)
now_ts = time.time() * 1000
# value是什么在这里并不重要,只要保证value的唯一性即可,这里使用毫秒时间戳作为唯一值
value = now_ts
# 时间窗口左边界
old_ts = now_ts - (time_zone * 1000)
# 记录行为
redis_conn.zadd(key, value, now_ts)
# 删除时间窗口之前的数据
redis_conn.zremrangebyscore(key, 0, old_ts)
# 获取窗口内的行为数量
count = redis_conn.zcard(key)
# 设置一个过期时间免得占空间
redis_conn.expire(key, time_zone + 1)
if not count or count < times:
return True
return False