使用 nginx 的 http_limit_conn_module 模块可以在 nginx 的 preaccess 阶段对请求的并发做拦截。限制的有效性取决于 key 的设计,通常使用 realip 模块获取到的客户端 IP。

应用场景:假如你只希望限流用户的访问入口,但不希望管理后台也被纳入限流的范围内,因为在操作管理后台时,用户访问量激增,nginx 会频繁返回 503,那么管理后台将处于不可操作的状态。

一、编写 location 规则

假设我需要对 /app/index.php 这个路径做并发量的控制(例如:https://example.com/app/index.php?i=99&t=0&v=5.5.5&from=wxap),首先要确定的事情就是编写的 location 规则是否能被正确匹配到。下面是 nginx location 的写法:

location ^~ /app/index.php {
  return 404;
}

加上这条 location 规则后,如果访问(https://example.com/app/index.php?i=99&t=0&v=5.5.5&from=wxap)这样的链接,nginx 会直接返回 404 错误。那么说明这段 location 是能够匹配到的。

二、限制并发量

在 nginx 的 http 部分添加:

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

$binary_remote_addr 是二进制格式的 IPv4 地址,这个 IP 是请求者的 IP。将远程客户端的 IP 地址作为 zone 的 key,目的是为了对单个客户端做并发限制。
$server_name 指的是当前站点的名称,将这个作为 zone 的 key,目的是为了对某个站点做总体的并发限制。

稍稍修改一下上面的 location 规则,设置为站点并发量为 300,单个 IP 并发量限制 25:

location ^~ /app/index.php {
  limit_conn perserver 300;
  limit_conn perip 25;
  limit_rate 128k;
  
  #宝塔面板默认的 enable-php-56.conf 规则,对php文件的响应处理
  try_files $uri =404;
  fastcgi_pass unix:/tmp/php-cgi-56.sock;
  fastcgi_index index.php;
  include fastcgi.conf;
  include pathinfo.conf;
}

limit_conn 的作用是,在某个 zone 下的并发限制为多少。
limit_rate 限制的是 nginx 向客户端传送响应的速率。
limit_conn 和 limit_req 不能设置在 if 指令中,所以如果针对不同的 URL 进行限流,只能通过不同的 location 实现。
limit_rate 可以在 if 指令中,可以使用 if 指令匹配 URL 实现不同 URL 的限流。

使用 jmeter 等相关并发测试工具可以测到,确实对 /app/index.php/ 请求做了并发量的限制,超出并发量的部分 nginx 会默认返回 503。