在 nginx 中,`limit_req` 无法直接作用于 `rewrite` 指令,因为 `rewrite` 属于配置解析阶段,而限流发生在请求处理阶段。要实现限制重写频率,必须将限流逻辑前置到 `location` 块中,利用 `limit_req_zone` 定义共享内存中的速率限制规则,并通过 `limit_req` 指令应用该规则。当请求触发限流时,nginx 会返回 503 错误并中断后续处理,从而阻止了重写的执行。

如何配置基于 IP 的限流区域
要限制特定客户端的重写频率,首先需要定义一个共享内存区域。使用 `limit_req_zone` 指令可以在 `http` 上下文中创建这个区域,指定键值(如 `$binary_remote_addr`)和速率(如每秒 1 次)。接着,在包含 `rewrite` 规则的 `location` 块中使用 `limit_req` 绑定该区域。如果请求超过设定速率,Nginx 默认立即拒绝并返回 503,除非配置了 `burst` 参数来允许突发流量。

server {
listen 80;
# 定义限流区域:名称为 one,大小 10m,针对二进制 IP 地址,速率 1r/s
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
location /api/ {
# 应用限流规则,允许 5 个请求的突发,延迟处理
limit_req zone=one burst=5 nodelay;
# 重写规则:将 /api/v1/resource 重定向到 /api/v2/resource
rewrite ^/api/v1/(.*)$ /api/v2/$1 last;
# 后续处理逻辑...
}
}
上述配置中,`zone=one` 引用了之前定义的内存区域。`burst=5` 表示允许队列中最多积压 5 个请求,`nodelay` 表示这些突发请求不延迟立即处理。如果请求频率持续超过 1r/s 且队列满,Nginx 将直接返回 503 Service Temporarily Unavailable。`last` 标志告诉 Nginx 停止处理当前 `location` 块中的剩余指令,并重写 URL 后重新匹配位置块,但限流检查仅在进入该 `location` 时进行一次。
如何处理限流触发的 503 错误
当 `limit_req` 触发限制时,Nginx 默认返回 HTTP 503 状态码。为了提供更好的用户体验或便于监控,可以使用 `error_page` 指令自定义错误响应。这通常涉及创建一个专门处理 503 错误的内部 location,或者返回一个 JSON 格式的提示信息。此外,可以通过日志记录被限流的请求,以便分析流量模式。

server {
listen 80;
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
# 自定义 503 错误页面
error_page 503 @limited;
location /api/ {
limit_req zone=one burst=5;
rewrite ^/api/v1/(.*)$ /api/v2/$1 last;
}
location @limited {
internal;
default_type application/json;
return 429 '{"error": "Too Many Requests", "message": "Please try again later."}';
}
}
在这个配置中,当 `limit_req` 拒绝请求时,Nginx 会跳转到名为 `@limited` 的内部 location。该 location 设置了 `internal` 标志,确保它只能通过内部跳转访问,不能直接通过 URL 访问。它返回 429 Too Many Requests 状态码(更符合 RESTful API 规范)和一个 JSON 错误消息。这种处理方式比默认的 503 更清晰,前端可以根据状态码 429 进行特定的 UI 提示,如显示“请求过于频繁”。
基于特定 URI 参数的限流效果
除了基于 IP,还可以根据 URI 中的特定参数进行限流。例如,限制某个搜索接口的调用频率。这需要修改 `limit_req_zone` 中的键值,使用 `$request_uri` 或自定义变量。结合 `rewrite`,可以确保只有经过重写的请求才受限流约束,或者对重写前后的不同路径应用不同的限流策略。以下示例展示了对 `/search` 路径下所有请求进行严格限流,无论其是否被重写。
server {
listen 80;
# 基于完整 URI 进行限流,适用于区分不同资源
limit_req_zone $request_uri zone=search_limit:10m rate=10r/m;
location /search {
# 应用每分钟 10 次的限流
limit_req zone=search_limit burst=2;
# 假设这里有一个重写,将 /search?q=xxx 映射到后端服务
rewrite ^/search$ /backend/search last;
}
location /backend/search {
# 实际处理逻辑
proxy_pass http://backend_server;
}
}
此配置中,`$request_uri` 作为键值意味着每个不同的 URI 路径都有独立的限流计数器。对于 `/search` 路径,每分钟最多允许 10 次请求,突发允许 2 次。当请求命中 `/search` 时,首先经过限流检查,如果通过,则执行 `rewrite` 跳转到 `/backend/search`。注意,`rewrite` 后的新 URI `/backend/search` 不会再次触发 `/search` 的限流规则,因为它匹配的是不同的 `location` 块。这种分离确保了限流只应用于入口点,避免了对内部处理的重复限制。











