Nginx限流实战:用burst和nodelay搞定突发流量,附完整配置代码
当你的电商平台突然遭遇秒杀活动,或者API接口被恶意刷量时,服务器就像早高峰的地铁站,瞬间涌入的人流会让整个系统崩溃。作为运维老兵,我见过太多因为忽视流量管控而导致的惨案——数据库连接池耗尽、CPU飙升至100%、响应时间从200ms恶化到20秒...而Nginx的限流功能,就是你在流量风暴中的安全气囊。
1. 为什么burst和nodelay是黄金组合
去年双十一,我们某个商品详情页的QPS从平时的500暴涨到15000。如果没有提前配置好burst和nodelay参数,后果不堪设想。这两个参数就像交通管制中的应急车道:
- burst:相当于临时增加的等候区座位数
- nodelay:给紧急请求开绿灯的特权通行证
它们的精妙之处在于:既允许短时间内超出限流阈值,又不会彻底放开闸门。来看个真实案例对比:
| 场景 | 配置方式 | 效果 | 用户体验 |
|---|---|---|---|
| 普通限流 | rate=10r/s | 第11个请求直接返回503 | 页面突然显示"系统繁忙" |
| 突发流量处理 | rate=10r/s burst=5 | 前15个请求都成功(10+5) | 操作流畅完成 |
| 最优方案 | rate=10r/s burst=5 nodelay | 前15个请求立即处理,后续平滑限流 | 无感知排队 |
# 最佳实践配置示例 limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; location /api/checkout { limit_req zone=api_limit burst=5 nodelay; proxy_pass http://backend; }关键细节:burst大小应该根据业务容忍度设置。比如支付接口可以设为3-5,而商品查询可以设到20-30
2. 多维度限流策略实战
单纯的IP限流在复杂场景下远远不够。去年我们遇到羊毛党用代理IP池攻击,单个IP限流完全失效。这时候就需要组合拳:
2.1 用户ID维度限流
map $http_x_user_id $limit_key { default $binary_remote_addr; "~(.*)" $1; # 当存在用户ID时优先使用 } limit_req_zone $limit_key zone=user_limit:10m rate=5r/s;2.2 地理位置限流
geo $is_high_risk_area { default 0; 58.32.0.0/16 1; # 已知攻击源网段 } limit_req_zone $binary_remote_addr zone=geo_limit:10m rate=2r/s; location /api { if ($is_high_risk_area) { limit_req zone=geo_limit burst=1; } }2.3 动态限流规则
通过Lua脚本实现实时规则调整:
access_by_lua_block { local redis = require "resty.redis" local red = redis:new() local ok, err = red:connect("127.0.0.1", 6379) if not ok then ngx.log(ngx.ERR, "failed to connect to redis: ", err) return end local current_rate = red:get("dynamic_rate:"..ngx.var.host) if current_rate then ngx.var.limit_rate = current_rate end }3. 监控与调优技巧
配置完限流只是开始,真正的艺术在于持续优化。这三个工具是我的必备武器:
Nginx状态监控:
# 编译时添加--with-http_stub_status_module location /nginx_status { stub_status; allow 127.0.0.1; deny all; }限流效果可视化:
# 统计被拒绝的请求 awk '{print $9}' access.log | sort | uniq -c | sort -rn动态调整参数:
# 根据时间段调整限流阈值 map $time_iso8601 $rate_slot { default "10r/s"; "~T(08|12|18):" "20r/s"; } limit_req_zone $binary_remote_addr zone=dynamic_limit:10m rate=$rate_slot;
4. 避坑指南:血泪教训总结
在金融级系统中,我们踩过这些坑:
坑1:burst设置过大
曾经给秒杀接口设置burst=50,结果导致数据库连接池被瞬间打满。后来通过这个公式计算合理值:合理burst值 = 平均响应时间(ms) × 允许的并发数 / 1000坑2:忽略慢请求影响
某个查询接口响应时间从50ms恶化到2s,导致限流完全失效。解决方案:location /slow_api { limit_req zone=api_limit burst=5; proxy_connect_timeout 1s; proxy_read_timeout 3s; }坑3:黑白名单维护困难
改用Redis+Lua实现动态名单后,运维效率提升80%:local blacklist = redis:smembers("ip_blacklist") for _, ip in ipairs(blacklist) do if ngx.var.remote_addr == ip then return ngx.exit(403) end end
限流配置从来不是一劳永逸的事。每次大促前,我们都会用JMeter模拟真实流量场景,记录下关键指标:
# 压力测试时监控的关键指标 watch -n 1 "echo 'TCP连接数:'; netstat -ant | wc -l; echo 'Nginx活跃连接:'; curl -s http://127.0.0.1/nginx_status | grep Active; echo '后端响应时间:'; tail -100 access.log | awk '{print \$1,\$NF}'"