令牌桶算法思想如下面的示意图所示:

令牌桶以固定的速率往桶里添加token,一个请求过来时,会申请一个令牌(也可以申请多个),如果请求的速率已经达到QPS,则抛弃该次请求;如果未达到,则从桶中移除一个令牌,程序继续操作。
看完令牌桶的大致原理,我们可以闭目想一想,如果让你来实现令牌桶算法,该如何做呢?
上面的示意图似乎在有意无意地引导我们:
1)设计一个线程,以固定的速率生产token,并设计一个容器或数组用来存放token;
2)提供一个函数供调用者获取token,如果桶中有token,则移除一个token,则返回成功;否则返回失败。参见下图的批注。

思路听上去还可以,但实现起来可能会遭遇不少问题,例如生产token后,如何放入桶中,token的移除,生产和消费token的同步等。另外,令牌桶为空时抛弃请求码头军觉得更容易理解一些;在桶为空时一定抛弃请求吗?能否等待一定时间,超时后再判断是否能获取token呢?
带着这些问题,我们一起看下Google的Guava是如何实现令牌桶算法的。它会给我们一个满意的答案。
首先,令牌桶算法中的令牌只是一个数字,QPS的倒数代表生产令牌的速率。比如:QPS=5,说明以1/5秒,即200毫秒的速率生产1个token。
生产token并不需要有个线程来以固定速率做操作,它是在每次请求到来时,以请求这一刻的相对时间nowPeriod(即相对于创建并启动当前令牌桶的时刻)与下次不等待获取令牌的相对时间nextFreePeriod(不等待的意思是立即获取到令牌,等待时间为零)做比对:
如果nowPeriod小于nextFreePeriod,说明请求过快,超过了所允许的QPS,如果桶中还有token,能满足请求,则要求请求线程休眠(nextFreePeriod-nowPeriod)时间,之后放行,线程继续执行;
如果桶中没有token或满足不了需求,则计算需要新生成的token数目,并增加nextFreePeriod的值,同样要求请求线程休眠(oldNextFreePeriod-nowPeriod)时间,之后放行,线程继续执行。
因此,不管当前请求的token是多大,线程休眠的时间是固定的,影响的是下一次请求。
好了,Guava的令牌桶实现暂时分享到着,明天会继续深入源码看下Google大神们的神来之笔。