为什么需要限流
如果你的后端服务器只能支持10QPS的并发量, 当用户量突然暴涨或者有CC攻击的时候并发量将远远大于这个值, 为了避免大量的请求导致后端服务器CPU/Memory异常, 或则导致后端数据库异常而导致的崩溃, 这时候我们需要对服务进行'限流降级', 保证服务器不崩溃, 这总比崩溃之后导致所有人都不能使用好.
为Nginx配置限流
Nginx默认是不限流的, 需要我们手动配置开启限流.
在conf配置文件的任何地方 写下 限流规则的定义.
语法如下:
limit_req_zone key zone=name:size rate=rate [sync];
例如
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
limit_req_zone $http_host zone=perhost:10m rate=10r/s;
上面的两个指令的意思大致为:
使用 limit_req_zone 规则给每一个Ip($binary_remote_addr) 限流, 区域(即名字)为perip, 容量为10M, 限制速率为每秒10次.
其中 key 可以是内置的变量, 或者自定义的变量, 更多写法可以参阅官方文档: ngx_http_limit_req_module.html#limit_req_zone.
然后在 server 或者 location 上下文中使用定义的规则:
server {
listen 80;
server_name localhost;
...
limit_req zone=perip burst=5 nodelay;
limit_req zone=perhost burst=5 nodelay;
...
}
上面两个指令的意思大致为:
使用perip
区域(即名字)来限制流量, 还有两项配置: burst 和 nodelay. 这两个配置需要重点理解.
- burst: 译名 爆发, 意思是如果请求速率超过了配置的速率(本例中是10次/s), 则还可以将5个请求放入缓冲区, 等到下一秒再处理这5个请求.
- nodelay: 表示如何处理缓存区(即burst)中的请求, 如果设置了nodelay, 则缓冲区里的请求会 都会被排队等待处理.
深入理解 burst 和 nodelay 的作用
我们用例子继续理解 burst 和 nodelay.
1. rate = 10r/s
即每100ms只能处理一个请求, 当在同一个时间段(如0-100ms)内有两个请求时, 第二个请求将会被丢弃.
2. rate = 10r/s burst = 5
在上面的例子中, 我们发现当有2个请求而还达不到速率10r/s的情况下, 就会有请求被丢弃, 但现实中网络由于有延迟, 就算用户是匀速访问, 都会出现多个请求同时到达服务器的情况, 这会导致大量的请求被错误丢弃.
我们可以使用 brust 来优化这个问题.
现在我们将burst设置为5
- 如果在100ms以内有2个请求同时到达Nginx, 则第2个请求会先被Nginx放在缓冲区, 直到下一个100ms(即100ms-200ms时间段)时处理第2个请求.
- 如果在100ms以内有7个请求同时到达Nginx, 则第2-6(共5个)个请求会先被Nginx放在缓冲区, 第7个请求由于超过了缓冲区大小会被丢弃, 之后每100ms从缓冲区中取出一个请求处理, 也就是说在100ms-200ms时间段开始处理第2个请求, 并且又可以新接入第8个请求. 可以预见, 处理第6请求将会多等待 5 *100ms = 500ms 的时间, 缓存区越大, 等待时间会越长, 当然也不可能一直等待, nginx也有超时机制, 超时的请求会直接返回错误.
3. rate = 10r/s burst = 5 nodelay
为了解决上例中等待时间过长的问题, 还可以设置 nodelay.
和上例唯一不同的是, 当有多个请求在缓冲区时, 这多个请求将会同时被处理, 而不是每100ms处理一个.
- 如果在100ms以内有2个请求同时到达Nginx, 则第2个请求会先被Nginx放在缓冲区, 并同时处理第2个请求.
- 如果在100ms以内有7个请求同时到达Nginx, 则第2-6(共5个)个请求会先被Nginx放在缓冲区, 第7个请求由于超过了缓冲区大小会被丢弃, 在缓冲区中的5个请求也会被同时处理(这时有6个请求在这一时刻被nginx同时处理). 之后每100ms释放一个缓冲区让新的请求进来. 最后得到的结果是: 突发请求下, 在开始的0-100ms时间段内可以处理6个请求, 然后每100ms再处理一个请求, 最后速率还是会稳定在10r/s左右.
总结
何时会丢弃请求
如果没有配置 burst, 则在一个时区(如设置了rate=100/s, 则一个时区是10ms)内只能有一个请求, 剩下的请求都会被丢弃.
如果配置了 burst, 则在一个时区中第一个请求之后的所有请求会先放入缓冲区, 当缓冲区满了之后, 之后的请求都会被丢弃.
最佳实践
rate + burst + nodelay 是最理想的方案, 将最大限度的保证可用, 但也会对后端服务器造成更大的压力, 因为瞬时并发会更大. 关于如何根据后端服务器支持的QPS配置最佳的rate与burst的值, 作者还没有仔细研究, 目前靠猜, 我的方案是 rate = burst = QPS
如果你实在害怕后端服务器被压垮, 则使用 rate + burst 方案也不错, 它保证每100ms只会有1个请求被处理.