限流

Published August 30, 2018

缘起

为了保证API的可用性,以及系统的可靠性,需要为API限速。不然,API请求量大到系统无法处理时就会出现系统变慢,甚至宕机的情况。常见的限速场景:

  • 挡住某个用户的过多请求(用户激增或者恶意请求),确保正常处理其他用户请求。
  • 挡住过多的低优先级的请求,确保核心请求得到处理。
  • 由于系统内部错误,导致系统处理能力下降,调节系统的处理能力。
  • 挡住过多某类请求,确保其他请求可以得到处理。

限速类型

请求限速

限制API在一秒中内能够处理的请求数量。如果超过这个数量,等待或者拒绝服务。通常情况下这个是首选。

并发限制

针对资源敏感的请求,比如CPU密集型API,进行并发限制,限制某一时刻最多只有有限个请求正在被处理。防止因为这些请求占用资源,导致其他请求得不到处理。

基于资源利用率限速

针对不同的请求分配了不同百分比的资源,当某一类请求超载时,对这类请求限速。

基于worker限速

这个是基于代码特征的限速。每类API通过不同的worker线程负责处理,当worker线程中出现请求堆积时进行限速。

限速结果

http服务的话按照场景返回429或者503。

常用算法

计数

单位时间内计数,超过这个数量时,拒绝服务,每个单位时间开始后计数清零。缺点是在时间边界处,会超过上限。比如,每秒限速100,在0.9s的时候来了100个请求全部得到处理,在下一秒0.1s来了100个请求。在0.9s到1.1s这个范围小于1s,但是请求达到了200。

 0.1s 0.2s 0.3s 0.4s 0.5s 0.6s 0.7s 0.8s 0.9s 0.1s 0.2s
+----+----+----+----+----+----+----+----+----+----+----+---
| 0  | 0  | 0  | 0  | 0  | 0  | 0  | 0  | 100| 100| 0  | 0 
+----+----+----+----+----+----+----+----+----+----+----+---

队列

请求过来的时候,先如队列,处理逻辑处理队列中的请求。

  • 基于大小的队列:当队列大小超过一个阀值的时候,拒绝新来的请求。
  • 基于时间的队列:请求在队列里面的时间超过多长时间没有被处理,立即返回。RocketMQ就是采用这种方式。
  • 优先级队列:对请求做优先级分类,不同优先级的请求进入不同的队列。为了避免低优先级请求被饿死,需要对不同优先级队列分配不同的处理时间。

漏桶

有一个容量固定的桶,桶中的请求以恒定的速率被处理。请求过来的时候,尝试进入桶,当桶满时被丢弃。本质上是队列后面加一个速率限制器。

漏桶还有一个变形,在漏桶前面加一个队列。当桶满的时候,先放入队列,这样可以保留一部分请求。

令牌桶

以恒定的速率向桶中加入token,当请求过来的时候从桶中获取token。如果桶空了,请求等待或者丢弃。相比漏桶令牌桶还可以做蓄水,当桶满的时候可以预留一部分token,可以做到突发(burst)的请求。

实现

Guava中的RateLimter是一个平滑的基于令牌桶的实现,同时还实现了warm up特点的一个Ratelimiter