1. 流控
缓存、降级和限流是保护高并发系统的常用方法。缓存以空间换时间、减少了 CPU 和网络调用的耗时;降级保护了核心服务的高可用,高峰时段延时或拒绝处理非核心请求;限流是通过限制并发请求来保护系统。
限流就是,在有限资源的情况下,每个 API 接口单位时间内的服务能力有限,如果,对 API 接口的访问次数不加控制,会造成 API 接口的滥用,甚至招致 DDos 攻击。同时,如下图,API 接口的延迟,也会随着请求量的提升而迅速提升。因此,需要控制单位时间内, API 接口的请求量。
2. 什么是时间窗口
时间窗口,指的是一个统计周期的时间段。有两种时间窗口,一种是自然时间窗口,一种是滑动时间窗口。
上图,就是一个自然时间窗口,每分钟一个窗口。例如,15:01~15:02 统计一次。这种方式的问题是,如果每分钟限流为 N ,那么图中红色时间窗口的最大值是 2N。此时,限流失去了效果。因此,便有了滑动窗口。
滑动窗口的核心,就是将时间划分为更细的粒度。例如,现在是 15:02:20 ,那么统计的滑动窗口就是 15:01:20 ~ 15:02:20,不同于上面以分钟作为最小粒度,这里的滑动窗口以秒作为最小粒度,从而获得更加精准的流量控制。
3. 流量控制算法
- 计数器算法
计数器算法的思路是限制一个接口在某个维度(IP、用户、某种资源)上的响应次数。通过设置一个计数器,每响应一次,计数器加一,当计数器超过阈值时,拒绝服务。这种算法对总数量进行了简单的限制,而不是平均速率限流。 - 漏桶算法
请求以一定速率进入到漏桶中,漏桶以一定速率响应请求,当水流入速度过大时,拒绝服务。
- 令牌桶算法
按照固定速率往桶里添加令牌。随着时间流逝,系统会按恒定时间间隔往桶里加入Token,如果桶已经满了,就不再添加。新请求来临时,会各自拿走一个 Token,如果没有 Token 可拿了就阻塞或者拒绝服务
漏桶算法能够限制数据的传输速率,请求超过处理速率时,会被直接丢弃;而令牌桶算法能够在限制数据的平均传输速率的同时,还可以通过加快添加令牌的速率来处理突发请求。
4. 不同类型的限流器
- 请求限流器
限制每个用户每秒可发送 N 个请求 - 并发请求限流器
限制每秒最高请求数。请求限流器限制的是累积量,而并发限制的是峰值。 - 基于使用量的负载降级
将请求分为关键 API 请求和非关键 API 请求。设计系统时,为关键 API 请求预留一定资源,当非关键 API 请求需要占用预留资源时,不预分配,直接拒绝服务。 - 基于 Worker 利用率的负载降级
如果某个 worker 太忙,无法处理分配给它的请求,它会缓慢降级非关键请求,当然是先从测试请求开始。如果降低测试请求的过程中,worker 的处理能力恢复到好的状态,那我们就可以开始缓慢地恢复流量。
5. 实现
对于 Nginx 接入层限流可以使用 Nginx 自带了两个模块:连接数限流模块 ngx_http_limit_conn_module 和漏桶算法实现的请求限流模块 ngx_http_limit_req_module。
- ngx_http_limit_conn_module
limit_conn 是对某个 KEY 对应的总的网络连接数进行限流。
|
|
此处使用 Key 为 $binary_remote_addr
表示 IP 地址,还可以使用 $server_name 表示域名,不同的 Key 值从不同维度限制流量。
测试
|
|
- ngx_http_limit_req_module
|
|
测试
|
|
- 设置 IP 黑白名单
|
|
- django-ratelimit
django-ratelimit 是一个基于缓存的接口限速包,使用装饰器对 API 接口进行流量控制。。
安装
|
|
使用
|
|
这里的 Key 表示的是统计的维度,可以是 ip、get 中获取的某个参数、post 中获取的某个参数、header 中获取的某个参数、user、user_or_ip。rate 表示的是限速 X/u,X 表示数字,u 表示时间单位,可以是,s、m、h、d。
rate 还可以是一个函数,只需要返回指定的格式即可。通过这种方式,可以实现一些特殊的限制功能。比如,匿名用户和登录用户可以采用不同的限制值。
6. 分布式流控
分布式限流最关键的是要将限流服务做成原子化。
解决方案是,通过 Redis + Lua 或者 Nginx + Lua 的技术,实现对请求并发数和总数在时间窗口下的控制。使用 Lua 实现令牌通或漏桶算法。