高并发之API接口,分布式,防刷限流,如何做?( 二 )


漏桶算法漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率 。

高并发之API接口,分布式,防刷限流,如何做?

文章插图
 
对于很多应用场景来说,除了要求能够限制数据的平均传输速率外,还要求允许某种程度的突发传输 。这时候漏桶算法可能就不合适了,令牌桶算法更为适合 。
令牌桶算法如图所示,令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务,令牌桶算法通过发放令牌,根据令牌的rate频率做请求频率限制,容量限制等 。整编:微信公众号,搜云库技术团队,ID:souyunku
高并发之API接口,分布式,防刷限流,如何做?

文章插图
 
在Wikipedia上,令牌桶算法是这么描述的:1、每过1/r秒桶中增加一个令牌 。
2、桶中最多存放b个令牌,如果桶满了,新放入的令牌会被丢弃 。
3、当一个n字节的数据包到达时,消耗n个令牌,然后发送该数据包 。
4、如果桶中可用令牌小于n,则该数据包将被缓存或丢弃 。
令牌桶控制的是一个时间窗口内通过的数据量,在API层面我们常说的QPS、TPS,正好是一个时间窗口内的请求量或者事务量,只不过时间窗口限定在1s罢了 。以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务 。令牌桶的另外一个好处是可以方便的改变速度,一旦需要提高速率,则按需提高放入桶中的令牌的速率 。
在我们的工程实践中,通常使用google开源工具包Guava提供的限流工具类RateLimiter来实现控制速率,该类基于令牌桶算法来完成限流,非常易于使用,而且非常高效 。如我们不希望每秒的任务提交超过1个
public static void main(String[] args) {String start = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());RateLimiter limiter = RateLimiter.create(1.0); // 这里的1表示每秒允许处理的量为1个for (int i = 1; i <= 10; i++) {double waitTime = limiter.acquire(i); // 请求RateLimiter, 超过permits会被阻塞System.out.println("cutTime=" + System.currentTimeMillis() + " call execute:" + i + " waitTime:" + waitTime);}String end = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());System.out.println("start time:" + start);System.out.println("end time:" + end);}首先通过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参数实现一开始就是平滑固定速率 。整编:微信公众号,搜云库技术团队,ID:souyunku


推荐阅读