##【Redis】Redis 与网络流量整形 08 Jul 2015
令牌桶算法(token bucket) 并不是网络流量整形中的奇技淫巧,而是非常常用的算法,从百度百科上已经可以对它有一个概括的了解。对此算法的深入读者可自行查阅研究,这里我通俗化的来解释一下这个算法。
在令牌桶算法中,每一个访客都拥有一个独立的“令牌桶”,在这个“令牌桶”里放了一些“令牌”,访客每次来访都会消耗“令牌桶”中的“令牌”,如果“令牌桶”空了,将会对访客做特殊处理(如拒绝其继续访问以达到限流的目的)。
问题一:访客来访是一个持续的过程,如果最初的“令牌”数目固定,“令牌桶”中的令牌会慢慢被消耗殆尽,这样正常的访客也将无法访问—-所以我们需要以一个恒定的速率来向“令牌桶”中添加一定数量的“令牌”, 这样就可以让访客持续的访问。
问题二:我们以一个恒定速率向“令牌桶”中添加“令牌”, 那么如果访客一直没来访他的“令牌桶”岂不会累积大量“令牌”么?—-所以,我们设定“令牌桶”中“令牌”的最大数量,“令牌桶”满了就不需要再去添加了。这解决了“令牌”累积的问题,也使它更像一个“桶”。
如此,“令牌桶算法”中的重要的参数有:1. 给“令牌桶”添加“令牌”的速率(如果访客以这个速率消耗令牌,将一直不会被限流); 2. “令牌桶”的容量(如果消耗令牌的速率大于添加令牌的速率,将消耗桶中的存货,如果消耗速率过大,令牌会被消耗殆尽,访客将被限流)。注意:一般情况下“令牌桶”最初的状态是满的。
import redis from redis import WatchError import time RATE = 0.1 # 令牌桶的最大容量 DEFAULT = 100 # redis key 的过期时间 TIMEOUT = 60 * 60 r = redis.Redis() def token_bucket(tokens, key): pipe = r.pipeline() while 1: try: pipe.watch('%s:available' % key) pipe.watch('%s:ts' % key) current_ts = time.time() # 获取令牌桶中剩余令牌 old_tokens = pipe.get('%s:available' % key) if old_tokens is None: current_tokens = DEFAULT else: old_ts = pipe.get('%s:ts' % key) # 通过时间戳计算这段时间内应该添加多少令牌,如果桶满,令牌数取桶满数。 current_tokens = float(old_tokens) + min( (current_ts - float(old_ts)) * RATE, DEFAULT - float(old_tokens) ) # 判断剩余令牌是否足够 if 0 <= tokens <= current_tokens: current_tokens -= tokens consumes = True else: consumes = False # 以下动作为更新 redis 中key的值,并跳出循环返回结果。 pipe.multi() pipe.set('%s:available' % key, current_tokens) pipe.expire('%s:available' % key, TIMEOUT) pipe.set('%s:ts' % key, current_ts) pipe.expire('%s:ts' % key, TIMEOUT) pipe.execute() break except WatchError: continue finally: pipe.reset() return consumes if __name__ == "__main__": tokens = 5 key = '192.168.1.1' if token_bucket(tokens, key): print 'haz tokens' else: print 'cant haz tokens'