限流器(rate limiter)用于控制网络流量,比如限制客户在一定时间内能够发起的请求数量,如果超过了该数量,那么接下来的请示都会被阻挡,以下是一些限流的需求示例:
限流的用处包括但不局限于:
沟通并确定设计需求,以下是一些可供讨论的点:
1. 哪端限流?客户端限流or服务端API限流?
2. 限流策略?基于IP限流 or 基于用户ID限流 or 其它?
3. 系统规模?
4. 分布式?
5. 限流器作为一个单独的服务还是集成到代码中?
6. 需要通知被限流的用户吗?
以下是一个需求示例:
1. 精确限制超额访问流量
2. 低延迟,不能降低HTTP响应速度
3. 尽可能少得使用内存
4. 分布式,可跨服务器提供限流服务
5. 异常处理,为限流用户提供异常报警
6. 高容错率,当限流器异常时,不能影响整个系统的使用
1. 在客户端限流,一般不靠谱
2. 在服务端限流
3. 在中间件中限流,比如API网关
维护一个固定容量的令牌桶,系统以稳定的速率向桶内添加令牌,当令牌数量超过容量时多余的令牌会被抛弃。
每次用户请求都会消耗一个令牌,当桶内没有令牌时,限流生效,用户请求被阻挡。

令牌桶算法依赖两个参数:
设计这两项参数需要根据实际情况来确定,比如下面是一些实际场景:
优点:
缺点:
所有的请求先存放在一个固定容量的漏桶里,系统以恒定的速率从漏桶里取出请求进行处理。如果漏桶满了,则后续的请求会被丢弃。
漏桶与令牌桶的区别是漏桶对请求的处理速率是固定的。
漏桶算法依赖两项参数:
优点:
缺点:
将时间线划分成一个一个的固定窗口,在每个窗口内维护一个计数器,每次用户访问时计数器加一,到达阈值后新的访问被丢弃。
固定窗口计数器的预期目标是限制每个时间段内的流量,但如果流量峰值出现在时间段的开头和结尾处,则仍会出现时间段内的流量超时限定值的情况,如下:

上面的固定窗口是1分钟,流量上限是5,从2:00:00~2:01:00和2:01:00~2:02:00这两个时间段来看,流量都没有超出限定值,但是从2:00:30~2:01:30这个时间段来看,流量是10,超出了限定值。
优点:
缺点:
用于解决固定窗口计数器中流量峰值出现在边缘位置时实际流量超标的问题,实际操作如下:
优点:
缺点:
固定窗口计算器算法和滑动窗口日志算法的结合,以下面的流控举例:

假设每分钟允许的最大流量是7,前一分钟有5次请求,当前分钟有3次请求。在当前分钟的30%位置有新请求到来,那么当前窗口的请求数量按下面的公式计算:
当前窗口的请求数 + 前一个窗口的请求数 * 滑动窗口中前一个窗口的占比
按公式计算当前滑动窗口的请求数量是 3 + 5 * 70% = 6.5,根据实际情况可以向上或向下取整。以向下取整为例,滑动窗口有6个请求,所以新的请求会被接受。
优点:
缺点:

要点:
1. 使用内存缓存数据库Redis记录同一用户的请求次数,在中间件做流量控制。
2. 每次有请求到达时,从Redis中取出该用户的请求次数,判断是否超出限制。如果超出,则丢弃这次请求,否则将请求传递给API服务器,并且对Redis记录的请求次数加1。
这里举两个示例:
domain: messaging
descriptors:
- key: message_type
Value: marketing
rate_limit:
unit: day
requests_per_unit: 5 |
这个示例描述的是每天最多有5条推广消息。
domain: auth
descriptors:
- key: auth_type
Value: login
rate_limit:
unit: minute
requests_per_unit: 5 |
这个示例描述的是每分钟最多登录5次。
当请求被流控限制时,一般会在HTTP响应中回复代码429(too many requests)。根据实际场景,我们可以直接丢弃本次请求,或是先放入消息队列,等待后续处理。
可以在HTTP响应头中加入以下字段以通知客户端流控详情:
X-Ratelimit-Remaining: 当前窗口剩余的请求数X-Ratelimit-Limit: 每个窗口允许的请求数X-Ratelimit-Retry-After: 至下次请求合法时应等待的秒数

重点:
分布式环境下,主要要考虑两个问题:
发生在读-判断-写回过程,两个独立的读-判断-写回语句可能发生重叠,导致最终结果不正确,如下:

互斥锁可以用于处理竞态条件,但是会显著降低性能,除此外还有两个常见的处理办法:Lua脚本和Redis排序集合。
分布式环境下可能有不止一个流控服务器,当有多个流控服务器时如何进行数据同步是一个问题,一般可以使用一个集中的数据存储服务器来做同步,如下:

通过监控下面两项来优化限流器的运行效果:
如果流控太严格导致大量请求被限制,则可以适当放松流控规则。如果突发情况下流控失效,则需要调整流控算法,比如使用令牌桶算法。
流控算法:
硬控 vs 软控:
其他层的流控:
客户端改良: