##【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'