上周五朋友的商城搞周年庆秒杀,十点整开抢,我盯着监控看。前 30 秒一切正常,第十秒开始接口响应从 200ms 飙到 8 秒,第十五秒直接大片 502,后台数据库连接池被打满,正常用户也进不来。朋友在群里连发三条语音:"快关活动!客服已经被骂爆了!"
后来复盘,问题不在数据库本身,而在于秒杀请求是普通商品页的几十倍,Nginx 那层没做任何限流,所有请求直接打到后端 Tomcat,连接池瞬间耗尽。前后调了一下午把限流配上,记录下来。
Nginx 自带的 limit_req 是漏桶模型:请求按固定速率流出,突发流量会被排队或直接拒绝。这正好是秒杀场景要的——不管前端来多少请求,后端只按它能扛的速度处理:
# http 块定义限流区
limit_req_zone $binary_remote_addr zone=seckill_limit:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=seckill_conn:10m;
# location 块引用
location /api/seckill/buy {
limit_req zone=seckill_limit burst=20 nodelay;
limit_conn seckill_conn 5;
limit_req_status 429;
limit_conn_status 429;
proxy_pass http://tomcat_backend;
}几个关键参数:
这里踩了个大坑:商城前面挂了 CDN 和负载均衡,Nginx 看到的客户端 IP 全是 CDN 节点的 IP。按 $binary_remote_addr 限流等于给所有真实用户共享一个桶,正常用户也被 429。
必须先拿到真实 IP:
# 用真实客户端 IP 作为限流 key
set_real_ip_from CDN网段;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
limit_req_zone $http_x_forwarded_for zone=seckill_limit:10m rate=10r/s;我们一开始没加这段,压测时 50 个真实用户全被当成同一个 IP,一半请求 429,查了半小时才发现是限流 key 取错了。
限流只能挡"同一个人猛点",挡不住脚本换 IP 批量刷。这种要在应用层配合:
// 伪代码:秒杀前先做用户级限流
String key = "seckill:lock:" + userId + ":" + skuId;
Boolean ok = redis.opsForValue().setIfAbsent(key, "1", 5, TimeUnit.SECONDS);
if (!Boolean.TRUE.equals(ok)) {
return Result.fail("操作太频繁,请稍后再试");
}防护层 | 挡什么 | 典型配置 |
|---|---|---|
Nginx limit_req | 单 IP 突发洪峰 | rate=10r/s burst=20 |
Nginx limit_conn | 长连接占用 | 同 IP 5 连接 |
Redis 库存 | 后端 DB 被打满 | 预热库存到内存 |
用户级 SET NX | 同一用户狂点 | 5 秒窗口 |
这次周年庆秒杀的限流改造是在乔拓云的商城后台上做的,我主要负责配 Nginx 漏桶规则、修真实 IP 取值和 Redis 库存预热那套逻辑,改完后第二次演练同样的并发量,后端响应稳定在 300ms 以内,502 再没出现过。
以上是个人实践记录,各平台具体功能以官方实时信息为准。
你们做秒杀防护时,限流是放在 Nginx 层还是在网关/应用层统一做?
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。