15-限流防刷配置:接口限流、IP限流、防恶意请求、保护SaaS后台
前言
做SaaS平台最怕什么?不是bug,是被人刷。你的售货柜SaaS后台API刚上线,竞争对手写个脚本疯狂调你的设备状态查询接口,QPS飙到5000,数据库连接池瞬间耗尽,正常用户全超时。又或者有人拿脚本批量尝试扫码开门,一天刷你几十万次请求。这些问题的第一道防线不在代码里,在Nginx。这篇就把Nginx限流配置从原理到实战讲清楚。
一、Nginx限流模块:两个核心指令
1.1 limit_req ------ 请求速率限流
limit_req 基于漏桶算法 ,控制每个KEY的请求速率。需要先用 limit_req_zone 定义限流区,再用 limit_req 启用。
nginx
# http块中定义限流区
http {
# 参数1:KEY(按IP限流)
# 参数2:共享内存区名称和大小(10m约存16万个IP)
# 参数3:速率(每秒10个请求)
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
}
nginx
# server/location块中启用
location /api/ {
limit_req zone=req_limit burst=20 nodelay;
proxy_pass http://backend;
}
参数详解:
| 参数 | 含义 | 示例 |
|---|---|---|
zone |
引用的限流区名称 | req_limit |
burst |
突发请求队列长度 | 20(允许20个请求排队) |
nodelay |
队列中的请求立即处理不等待 | 有burst时建议加 |
delay |
延迟处理前N个突发请求 | delay=5 |
1.2 limit_conn ------ 并发连接数限流
limit_conn 限制同一KEY同时持有的连接数,防止单IP开大量并发连接打满服务器。
nginx
http {
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}
server {
location /api/ {
limit_conn conn_limit 10; # 每个IP最多10个并发连接
proxy_pass http://backend;
}
}
区别 :limit_req 管"频率"(每秒几次),limit_conn 管"并发"(同时几个连接)。两个配合用才全面。
二、漏桶算法与令牌桶算法原理
2.1 漏桶算法(Nginx limit_req 的基础)
markdown
请求流入
||
vv
┌──────────┐
│ 漏 桶 │ ← burst(桶容量)
│~~~~~~~~~~│
│ 存储 │
└────┬─────┘
│ 恒定速率漏出(rate)
vv
后端处理
核心思想:请求像水一样倒入桶中,桶以恒定速率漏水。水满(超过burst)则丢弃。保证后端处理速率恒定。
rate=10r/s:每秒漏出10个请求(每100ms处理一个)burst=20:桶能暂存20个突发请求nodelay:桶里的请求不排队等待,立即放行(但仍然消耗桶容量)
2.2 令牌桶算法
markdown
定速生成令牌 → ┌──────────┐
│ 令牌桶 │ ← 桶容量
│ tokens │
└────┬─────┘
│
请求到达 → 取令牌 → 有令牌则通过,无令牌则拒绝
核心思想:系统以恒定速率往桶里放令牌,请求来了取一个令牌,取不到就拒绝。允许一定程度的突发(桶里有积攒的令牌)。
两者区别:
| 维度 | 漏桶 | 令牌桶 |
|---|---|---|
| 突发流量 | 严格限制(超过burst丢弃) | 允许突发(有令牌就能过) |
| 输出速率 | 恒定 | 可变(取决于令牌存量) |
| Nginx支持 | limit_req原生支持 | 需Lua实现 |
| 适用场景 | 保护后端不被打垮 | 允许短时突发流量 |
2.3 nodelay参数深入理解
不加 nodelay 时,burst队列中的请求会按rate速率延迟处理:
diff
请求来了30个,rate=10r/s,burst=20:
- 前10个立即处理
- 后20个进入burst队列,每100ms放行一个
- 第21个之后的请求直接返回503
加了 nodelay:
diff
请求来了30个,rate=10r/s,burst=20,nodelay:
- 前30个(10+20)立即处理
- 但burst桶被消耗,后续请求需要等桶恢复
- 超过30的请求直接返回503
生产建议 :大多数场景用 nodelay,体验更好。如果后端确实扛不住瞬时并发,去掉 nodelay 让Nginx帮你匀速。
三、接口级别限流实战
3.1 不同接口不同限流策略
售货柜SaaS后台有不同的接口类型,限流策略应有所区分:
nginx
http {
# 按IP限流 - 普通查询接口
limit_req_zone $binary_remote_addr zone=api_normal:10m rate=20r/s;
# 按IP限流 - 写操作接口(更严格)
limit_req_zone $binary_remote_addr zone=api_write:10m rate=5r/s;
# 按IP限流 - 登录接口(最严格,防暴力破解)
limit_req_zone $binary_remote_addr zone=api_login:10m rate=1r/s;
# 按IP限流 - 设备控制接口(开门、出货)
limit_req_zone $binary_remote_addr zone=api_device:10m rate=2r/s;
# 连接数限流
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# 限流时返回429而非默认503
limit_req_status 429;
limit_conn_status 429;
}
nginx
server {
listen 80;
server_name api.alspd.com;
# 全局并发连接限制
limit_conn conn_limit 20;
# 普通查询接口 - 20r/s,允许突发10个
location /api/device/list {
limit_req zone=api_normal burst=10 nodelay;
proxy_pass http://backend;
}
location /api/device/status {
limit_req zone=api_normal burst=10 nodelay;
proxy_pass http://backend;
}
# 写操作接口 - 5r/s
location /api/device/update {
limit_req zone=api_write burst=3 nodelay;
proxy_pass http://backend;
}
# 登录接口 - 1r/s,防暴力破解
location /api/auth/login {
limit_req zone=api_login burst=2 nodelay;
proxy_pass http://backend;
}
# 设备控制接口 - 2r/s,防恶意开门
location /api/device/control {
limit_req zone=api_device burst=1 nodelay;
proxy_pass http://backend;
}
}
3.2 按用户ID限流
IP限流有局限:企业用户走同一个出口IP,按IP限流会误伤。可以按用户ID限流:
nginx
http {
# 按用户ID限流(从JWT中提取,需配合Lua或后端设置header)
limit_req_zone $http_x_user_id zone=user_limit:10m rate=30r/s;
}
location /api/ {
limit_req zone=user_limit burst=20 nodelay;
proxy_pass http://backend;
}
后端在请求头中设置 X-User-Id,Nginx按用户ID进行限流。注意:此方案要求后端在Nginx之前做鉴权,或者用Nginx + Lua从JWT中解析用户ID。
3.3 多维度组合限流
同时按IP和用户ID限流,双重保护:
nginx
location /api/ {
# IP维度:单IP 30r/s
limit_req zone=api_normal burst=10 nodelay;
# 用户维度:单用户 20r/s
limit_req zone=user_limit burst=5 nodelay;
# 并发连接:单IP最多20个
limit_conn conn_limit 20;
proxy_pass http://backend;
}
优先级:Nginx会同时检查所有limit规则,任何一个触发限流就返回429。
四、白名单IP排除限流
4.1 地理白名单
内网管理服务器、监控探针不应被限流。用 geo 模块定义白名单:
nginx
http {
# 定义白名单IP段
geo $limit_ip {
default 1; # 默认需要限流
127.0.0.1 0; # 本地不限流
192.168.0.0/16 0; # 内网不限流
10.0.0.0/8 0; # 内网不限流
172.16.0.0/12 0; # 内网不限流
# 运维办公IP
116.236.xxx.xxx 0;
# 第三方合作方IP
121.40.xxx.xxx 0;
}
# 利用map将白名单IP的KEY设为空字符串,实现跳过限流
map $limit_ip $limit_key {
0 ""; # 白名单:KEY为空,不参与限流
1 $binary_remote_addr; # 非白名单:KEY为IP,参与限流
}
# 使用动态KEY定义限流区
limit_req_zone $limit_key zone=api_limit:10m rate=20r/s;
limit_conn_zone $limit_key zone=conn_limit:10m;
}
location /api/ {
limit_req zone=api_limit burst=10 nodelay;
limit_conn conn_limit 20;
proxy_pass http://backend;
}
原理 :白名单IP的 $limit_key 为空字符串,Nginx对空KEY的请求不做限流计数。这是官方推荐的白名单实现方式。
五、防DDoS基础防护
5.1 连接数封顶
nginx
http {
# 全局连接数限制
limit_conn_zone $binary_remote_addr zone=ddos_conn:10m;
}
server {
# 单IP全局最多50个并发连接
limit_conn ddos_conn 50;
# 单个location更严格
location /api/ {
limit_conn ddos_conn 20;
proxy_pass http://backend;
}
}
5.2 请求体大小限制
DDoS常通过发送超大请求体消耗带宽:
nginx
http {
# 全局默认限制1MB
client_max_body_size 1m;
client_body_buffer_size 16k;
}
# 只在需要上传的接口放开
location /api/upload/ {
client_max_body_size 50m;
proxy_pass http://backend;
}
5.3 限制请求方法
只允许必要的HTTP方法,拒绝TRACE、DELETE等:
nginx
location /api/ {
# 只允许GET/POST/PUT
limit_except GET POST PUT {
deny all;
}
proxy_pass http://backend;
}
5.4 自动封禁高频IP(配合Lua)
nginx
access_by_lua_block {
local limit = require "resty.limit.req"
-- 每秒100次请求的IP直接拉黑
local lim = limit.new("ddos_block", 100, 100)
local delay, err = lim:incoming(ngx.var.binary_remote_addr, true)
if not delay then
if err == "rejected" then
-- 记录到共享内存,后续请求直接拒绝
local block_list = ngx.shared.block_list
block_list:set(ngx.var.binary_remote_addr, 1, 3600) -- 封禁1小时
ngx.exit(403)
end
end
-- 检查是否在封禁列表中
local block_list = ngx.shared.block_list
if block_list:get(ngx.var.binary_remote_addr) then
ngx.exit(403)
end
}
六、无人售货柜SaaS后台完整限流配置
nginx
# /etc/nginx/conf.d/saas-api.conf
http {
# ========== 限流区定义 ==========
# 白名单
geo $limit_ip {
default 1;
127.0.0.1 0;
192.168.0.0/16 0;
10.0.0.0/8 0;
}
map $limit_ip $limit_key {
0 "";
1 $binary_remote_addr;
}
# 按IP限流
limit_req_zone $limit_key zone=api_read:10m rate=20r/s;
limit_req_zone $limit_key zone=api_write:10m rate=5r/s;
limit_req_zone $limit_key zone=api_login:10m rate=1r/s;
limit_req_zone $limit_key zone=api_device:10m rate=3r/s;
limit_req_zone $limit_key zone=api_upload:10m rate=1r/s;
# 连接数限流
limit_conn_zone $limit_key zone=conn_limit:10m;
# 限流状态码
limit_req_status 429;
limit_conn_status 429;
}
server {
listen 443 ssl http2;
server_name api.alspd.com;
# 全局配置
client_max_body_size 1m;
limit_conn conn_limit 30;
# ========== 登录认证接口 ==========
location /api/auth/login {
limit_req zone=api_login burst=2 nodelay;
limit_conn conn_limit 5;
proxy_pass http://backend;
}
location /api/auth/refresh {
limit_req zone=api_login burst=2 nodelay;
proxy_pass http://backend;
}
# ========== 设备查询接口(读操作) ==========
location /api/device/list {
limit_req zone=api_read burst=20 nodelay;
proxy_pass http://backend;
}
location /api/device/status {
limit_req zone=api_read burst=20 nodelay;
proxy_pass http://backend;
}
location /api/order/list {
limit_req zone=api_read burst=20 nodelay;
proxy_pass http://backend;
}
# ========== 设备控制接口(高敏感) ==========
location /api/device/control {
limit_req zone=api_device burst=1 nodelay;
limit_conn conn_limit 2;
proxy_pass http://backend;
}
location /api/device/door/open {
limit_req zone=api_device burst=1 nodelay;
limit_conn conn_limit 1;
proxy_pass http://backend;
}
# ========== 写操作接口 ==========
location /api/device/update {
limit_req zone=api_write burst=3 nodelay;
proxy_pass http://backend;
}
location /api/order/refund {
limit_req zone=api_write burst=2 nodelay;
proxy_pass http://backend;
}
# ========== 文件上传接口 ==========
location /api/upload/ {
client_max_body_size 50m;
limit_req zone=api_upload burst=1 nodelay;
limit_conn conn_limit 2;
proxy_pass http://backend;
}
# ========== 限流响应 ==========
error_page 429 = @rate_limited;
location @rate_limited {
default_type application/json;
return 429 '{"code":429,"message":"请求过于频繁,请稍后再试"}';
}
}
七、限流效果验证
7.1 压测验证
bash
# 用ab压测,模拟并发请求
ab -n 100 -c 20 -H "Authorization: Bearer xxx" https://api.alspd.com/api/device/list
# 观察返回结果:
# - 部分请求返回200(正常处理)
# - 超过限流的返回429
7.2 查看限流统计
bash
# 查看Nginx错误日志中的限流记录
tail -f /var/log/nginx/error.log | grep "limiting requests"
# 输出示例:
# limiting requests, excess: 20.1 by zone "api_read"
7.3 自定义限流日志
nginx
log_format limit_log '$remote_addr - $time_local - '
'zone=$limit_req_zone - excess=$limit_req_excess';
location /api/ {
limit_req zone=api_read burst=10 nodelay;
access_log /var/log/nginx/limit.log limit_log;
proxy_pass http://backend;
}
总结
Nginx限流三板斧:limit_req(请求速率)、limit_conn(并发连接数)、geo + map(白名单排除)。核心思路是按接口敏感程度分级限流------登录接口最严(1r/s)、设备控制次之(2~3r/s)、普通查询宽松(20r/s)。生产环境务必组合使用IP维度和用户维度限流,配合白名单排除内网和合作方IP。限流不是一刀切,是让你的系统在流量洪峰下还能稳稳服务正常用户。