文章插图
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输 。这时候漏桶算法可能就不合适了,令牌桶算法更为适合 。
令牌桶算法
如下图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务,令牌桶算法通过发放令牌,根据令牌的rate频率做请求频率限制,容量限制等 。
文章插图
文章插图
在Wikipedia上,令牌桶算法是这么描述的:
每过1/r秒桶中增加一个令牌 。桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃 。当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包 。如果桶中可用令牌小于n,则该数据包将被缓存或丢弃 。
令牌桶控制的是一个时间窗口内通过的数据量,在API层面我们常说的QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在1s罢了 。以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务 。令牌桶的另外一个好处是可以方便的改变速度,一旦需要提高速率,则按需提高放入桶中的令牌的速率 。
在我们的工程实践中,通常使用google开源工具包Guava提供的限流工具类RateLimiter来实现控制速率,该类基于令牌桶算法来完成限流,非常易于使用,而且非常高效 。如我们不希望每秒的任务提交超过1个
文章插图
首先通过RateLimiter.create(1.0);创建一个限流器,参数代表每秒生成的令牌数,通过limiter.acquire(i);来以阻塞的方式获取令牌,令牌桶算法允许一定程度的突发(允许消费未来的令牌),所以可以一次性消费i个令牌;当然也可以通过tryAcquire(int permits, long timeout, TimeUnit unit)来设置等待超时时间的方式获取令牌,如果超timeout为0,则代表非阻塞,获取不到立即返回,支持阻塞或可超时的令牌消费 。
从输出来看,RateLimiter支持预消费,比如在acquire(5)时,等待时间是4秒,是上一个获取令牌时预消费了3个两排,固需要等待3*1秒,然后又预消费了5个令牌,以此类推 。
RateLimiter通过限制后面请求的等待时间,来支持一定程度的突发请求(预消费),在使用过程中需要注意这一点,Guava有两种限流模式,一种为稳定模式(SmoothBursty:令牌生成速度恒定,平滑突发限流),一种为渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值,平滑预热限流)两种模式实现思路类似,主要区别在等待时间的计算上 。
SmoothBursty 模式:RateLimiter limiter = RateLimiter.create(5);
RateLimiter.create(5)表示桶容量为5且每秒新增5个令牌,即每隔200毫秒新增一个令牌;limiter.acquire()表示消费一个令牌,如果当前桶中有足够令牌则成功(返回值为0),如果桶中没有令牌则暂停一段时间,比如发令牌间隔是200毫秒,则等待200毫秒后再去消费令牌,这种实现将突发请求速率平均为了固定请求速率 。
SmoothWarmingUp模式:
RateLimiter limiter = RateLimiter.create(5,1000, TimeUnit.MILLISECONDS);
创建方式:RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit),permitsPerSecond表示每秒新增的令牌数,warmupPeriod表示在从冷启动速率过渡到平均速率的时间间隔 。速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率) 。可以通过调节warmupPeriod参数实现一开始就是平滑固定速率 。
放在Controller中用Jemter压测
文章插图
注:RateLimiter控制的是速率,Samephore控制的是并发量 。
RateLimiter的原理就是令牌桶,它主要由许可发出的速率来定义,如果没有额外的配置,许可证将按每秒许可证规定的固定速度分配,许可将被平滑地分发,若请求超过permitsPerSecond则RateLimiter按照每秒 1/permitsPerSecond 的速率释放许可 。注意:RateLimiter适用于单体应用,且RateLimiter不保证公平性访问 。
推荐阅读
- 非常有用的16个Linux 服务器监控命令
- 服务器被挖矿了?用这个思路可以彻底解决
- 云服务器中三种常用的Linux系统镜像
- lol出现无法连接服务器 英雄联盟无法连接服务器
- 如何计算存储和服务器的机械磁盘性能?
- ssh访问服务器和snap/apt的使用
- App与服务器的通信接口如何才能设计得更好?
- 梦见一大一小两头黄牛回家 梦见一大一小两头黄牛爬上山顶
- Mac完全免费的Apache服务器环境套件
- python实现客户端和服务器端传输图片的代码