限流
断尾求生——简单限流
限定用户的某个行为在指定的时间里只能允许发生N次
解决方案
这个限流需求中存在一个滑动时间窗口,用一个zset结构记录用户的行为历史,每一个行为都会作为zset中的一个key保存下来
def is_aciton_allowed(user_id,action_key,period,max_count):
key = "his:%s:%s"%(user_id,action_key)
now_ts = int(time.time * 1000)
with client.pipline() as pipe:
pipe.zadd(key,now_ts,now_ts)
pipe.zremrangebyscore(key,0,now_ts-period*1000)
pipe.zcard(key)
pipe.expire(key,period+1)
_,_,current_count,_pipe.execute()
return current_count <= max_count
缺点
需要记录窗口内所有的行为记录,如果这个量很大,比如60s内操作不得超过100万次,不适合做这样的限流,需要消耗大量的存储空间
一毛不拔——漏斗限流
漏斗限流是最常用的限流方法之一,漏斗的容量是有限的,如果将漏嘴堵住,然后一直往里面灌水,它就会变满,直至装不进。如果漏嘴放开,水就会往下流,流走一部分之后,又可以继续往里面灌水。
所以,漏斗的剩余空间就代表当前行为可以持续进行的数量,漏嘴的流水速率代表着系统允许该行为的最大频率。
漏斗算法
class Funnel(object):
def __init__(self,capactiy,leaking_rate):
self.capactiy = capactiy
self.leaking_rate = leaking_rate
self.left_quota = capactiy
self.leaking_ts = time.time()
def make_space(self):
now_ts = time.time()
delta_ts = now_ts - self.leaking_ts
delta_quota = delta_ts * self.leaking_rate
if delta_quota < 1:
return
self.left_quota = self.left_quota + delta_quota
self.leaking_ts = now_ts
if self.left_quota > self.capactiy:
self.left_quota = self.capactiy
def watering(self,quota):
self.make_space()
if self.left_quota >= quota:
self.left_quota -= quota
return True
return False
Funnel对象的make_space是漏斗算法的核心,每次灌水前都会被调用以触发漏水,给漏斗腾出空间。能腾出多少空间取决于过去了多久以及流水的速率。
Redis-Cell
一个限流Redis模块,使用了漏斗算法