Skip to content

Nginx 限流

限流(Rate Limitting)是服务降级的一种方式,通过限制系统的输入和输出流量以达到保护系统的目的。

比如我们的网站暴露在公网环境中,除了用户的正常访问,网络爬虫、恶意攻击或者大促等突发流量都可能都会对系统造成压力,如果这种压力超出了服务器的处理能力,会造成响应过慢甚至系统崩溃的问题。

因此,当并发请求数过大时,我们通过限制一部分请求(比如限制同一 IP 的频繁请求)来保证服务器可以正确响应另一部分的请求。

nginx 提供了两种限流方式,一种是限制请求速率,一种是限制连接数量;另外还提供了对下载/上传速度的限制。

1. 限制请求速率

Nginx 的 ngx_http_limit_req_module 模块提供限制请求处理速率的能力,使用了漏桶算法(leaky bucket algorithm)。我们可以想像有一只上面进水、下面匀速出水的桶,如果桶里面有水,那刚进去的水就要存在桶里等下面的水流完之后才会流出,如果进水的速度大于水流出的速度,桶里的水就会满,这时水就不会进到桶里,而是直接从桶的上面溢出。

对应到处理网络请求,水代表从客户端来的请求,而桶代表一个队列,请求在该队列中依据先进先出(FIFO)算法等待被处理。漏的水代表请求离开缓冲区并被服务器处理,溢出代表了请求被丢弃并且永不被服务。

1.1 正常限流

nginx 中有两个主要的指令可以用来配置限流:limit_req_zonelimit_req;下面是一个最简单的限流的例子:

text
limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;

server {
    location / {
        limit_req zone=test;
    }
}

limit_req_zone 用于设置限流和共享内存区域的参数,格式为:limit_req_zone key zone rate

参数说明
key定义限流对象,$binary_remote_addr 是 nginx 中的变量,表示基于 remote_addr(客户端IP) 来做限流,binary_ 是二进制存储。使用 $binary_remote_addr 而不是 $remote_addr 是因为二进制存储可以压缩内存占用量。$remote_addr 变量的大小从7到15个字节不等,而 $binary_remote_addr 变量的大小对于 IPv4 始终为4个字节,对于 IPv6 地址则为16个字节
zone定义共享内存区来存储访问信息,访问信息包括每个 IP 地址状态和访问受限请求 URL 的频率等。zone 的定义又分为两个部分:由 zone= 关键字标识的区域名称,以及冒号后面的区域大小。test:10m 表示一个大小为10M,名字为 test 的内存区域。1M 能存储16000个 IP 地址的访问信息,test 大概可以存储约 160000 个地址。nginx 创建新记录的时候,会移除前 60 秒内没有被使用的记录,如果释放的空间还是存储不了新的记录,会返回 503 的状态码
rate设置最大的访问速率。rate=2r/s(为了好模拟,rate 设置的值比较小),表示每秒最多处理 2个请求。事实上 nginx 是以毫秒为粒度追踪请求的,rate=2r/s 实际上是每500毫秒1个请求,也就是说,上一个请求完成后,如果 500 毫秒内还有请求到达,这些请求会被拒绝(默认返回 503,如果想修改返回值,可以设置 limit_req_status

limit_req_zone 只是设置限流参数,如果要生效的话,必须和 limit_req 配合使用。limit_req 的格式为:limit_req zone=name [burst=number] [nodelay]

上面的例子只简单指定了 zone=test,表示使用 test 这个区域的配置,在请求 html 文件时进行限流。我们可以理解为这个桶目前没有任何储存水滴的能力,到达的所有不能立即漏出的请求都会被拒绝。如果我 1 秒内发送了 10 次请求,其中前 500 毫秒 1 次,后 500 毫秒 9 次,那么只有前 500 毫秒的请求和后 500 毫秒的第一次请求会响应,其余请求都会被拒绝。

1.2 设置白名单

如果遇到不需要限流的情况,比如测试要压测,可以通过配置白名单,取消限流的设置。白名单要用到 nginx 的 ngx_http_geo_modulengx_http_map_module 模块。

text
geo $limit {
    default 1;
    10.0.0.0/8 0;
    192.168.0.0/24 0;
}

map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}

limit_req_zone $limit_key zone=mylimit:10m rate=2r/s;

geo 指令可以根据 IP 创建变量 $limit$limit 的默认值是 1,如果匹配到了下面的 IP,则返回对应的值(这里返回的是 0)。

之后通过 map 指令,将 $limit 的值映射为 $limit_key:在白名单内的,$limit_key 为空字符串,不在白名单内的,则为 $binary_remote_addr。当 limit_req_zone 指令的第一个参数是一个空字符串,限制不起作用,因此白名单的 IP 地址(在 10.0.0.0/8 和 192.168.0.0/24 子网中)没有被限制,其它 IP 地址都被限制为 2r/s。

2. 限制连接数

Nginx 的 ngx_http_limit_conn_module 模块提供限制连接数的能力,包含两个指令 limit_conn_zonelimit_conn ,格式为 limit_conn_zone key zone

text
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
    location ~* \.(html)$ {
        limit_conn perip 10;
        limit_conn perserver 100;
    }
}

limit_conn perip 10: key 是 $binary_remote_addr,表示限制单个 IP 同时最多能持有 10 个连接。

limit_conn perserver 100: key 是 $server_name,表示虚拟主机 (server) 同时能处理并发连接的总数为 100。

提示

需要注意的是:只有当 request header 被后端 server 处理后,这个连接才进行计数。

3. 参考

https://mp.weixin.qq.com/s/w-hSMZfkKbM-TqHQ9_EIBA