去年 11 月 13 号晚上 9 点 40,我一个做考公资料站的朋友连着发了六条 60 秒语音,点开第一条就听到他在那边喊“网站挂了”。他那台机器是腾讯云轻量 2 核 4G、5M 带宽,跑一个 WordPress 加一个自己写的资料下载页,平时一天 PV 也就 800 出头,CPU 常年待在 5% 以下,属于那种放着不管也不会出事的站。
那天晚上 9 点 38 分到 9 点 41 分,三分钟,后台统计到 PV 12.4 万,UV 3.8 万,新增注册 2100 个。他下午 4 点发的一条抖音,18 秒,一直没什么动静,9 点 38 分开始突然起量,最后播放量 240 万。我一开始判断是 CC 攻击,因为 IP 太散了,翻了十分钟日志才推翻这个想法——referer 里 92% 是 douyin,UA 有 78% 是安卓抖音内置浏览器,请求路径高度集中,就两个:/doc/2025/shenlun.pdf 和 /article/72/,别的几乎不碰。
这就是脉冲流量。它不是“访问量大”,是“访问密度高”。12.4 万 PV 摊到一天,他那台破机器一点问题没有;挤在 180 秒里,就是平均每秒 690 个请求,峰值那 20 秒我算过,大概 1400 QPS。5M 带宽的理论上限是 640KB/s,一张 300KB 的封面图,每秒只够两个人同时加载完。
先分清是哪一种脉冲,应对完全不一样
很多人一看到流量暴涨就去加机器,这是最贵也最慢的做法。至少要先把来源分清楚,我整理了一个对比表,是我自己踩过坑之后的标准流程:
| 维度 | 推荐流脉冲 | 付费投放脉冲 | 恶意刷量 |
|---|---|---|---|
| 起量速度 | 30 秒内陡起 | 定时开始 | 几秒内瞬间 |
| referer | 集中在 1-2 个平台 | 广告平台域名 | 空或伪造 |
| 请求路径 | 1-3 个热页反复 | 落地页 | 随机、含搜索页 |
| 持续时间 | 3-15 分钟 | 按预算走 | 几小时甚至几天 |
| 主要瓶颈 | 带宽 + 数据库 | 后端并发 | 连接数 |
| 应对重点 | 缓存 + 限流 | 提前扩容 | 封 IP + 人机验证 |
我朋友那次是典型的推荐流脉冲,特征是陡、短、路径集中。这种最容易被缓存救回来,因为 3.8 万个 UV 看的是同一份 PDF。反倒是付费投放的脉冲最“可控”,你提前知道几点开始、大概多少量,可以临时升配;恶意刷量最难缠,因为它不急,它就是耗你。
排查:三行命令定位到底卡在哪
服务器 502 的时候别急着重启,先看连接状态。我当时跑的是这几条:
# 看 TCP 连接状态分布
ss -s
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
# 看 nginx 错误日志里的关键词
tail -f /var/log/nginx/error.log | grep -i "worker_connections"

# 看哪些 IP / 路径最热
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -20
结果很直接:TIME_WAIT 有 6800 多个,nginx error.log 里刷屏的是 “worker_connections are not enough”,另外 PHP-FPM 的进程池也满了,php-fpm.log 里是 “server reached pm.max_children setting (5)”。注意这个 5,是默认值。2 核 4G 的机器,pm.max_children 给 5 根本不够,但就算给到 40 也没用,因为瓶颈在它前面。
还有一个坑:他当时统计插件是实时写数据库的,每一个 PV 都往 wp_postmeta 里插一条,1400 QPS 等于 1400 次写。后面我把统计改成写 Redis 再异步落库,数据库负载直接掉了一个量级。
Nginx 限流:这几个参数我调了三次才对
先解决进程层面,events 块里改:
events {
worker_connections 10240;
multi_accept on;
use epoll;
}
worker_rlimit_nofile 65535;
然后在 http 块里加限流区域,注意是要放在 http {} 里,不是 server 里:
limit_req_zone $binary_remote_addr zone=perip:10m rate=20r/s;
limit_conn_zone $binary_remote_addr zone=connperip:10m;
limit_req_zone $server_name zone=perserver:10m rate=800r/s;
server 里用:
location / {
limit_req zone=perip burst=40 nodelay;
limit_conn connperip 20;
limit_req zone=perserver burst=200;
keepalive_timeout 15;
sendfile on;
gzip on;
}
这里我翻过车。第一次把 limit_conn 设成 20,结果 PDF 下载全断了——因为浏览器下载大文件会开多个 range 请求,一个用户就能占掉十几个连接,最后我把 PDF 单独拆了个 location,limit_conn 给到 80。rate=20r/s 这个值也是试出来的,一开始设 5r/s,正常用户刷列表页都会 503,日志里全是 limit_req 拦截记录,后来放到 20 才稳住。burst 是漏桶容量,nodelay 表示桶满了直接拒,不加 nodelay 会延迟处理,对静态站没意义。
真正救命的其实是缓存,不是限流
限流只是止血,把回源 QPS 从 1400 压到 300 出头,还是扛不住。真正翻盘的是上了腾讯云 CDN,缓存规则这么配的:
- /doc/* 目录,缓存 30 天,忽略 query string
- /static/*(图片、CSS、JS),缓存 7 天
- /article/*,缓存 2 小时
- 首页和登录页不缓存
配完之后回源率从 100% 掉到 8%,缓存命中率稳定在 92% 左右,后端 QPS 从 300 直接降到 30 上下。5M 带宽的机器,实际出口峰值只有 1.2M 左右,因为绝大部分请求在 CDN 节点就被吃掉了。
顺手还做了两件事:把注册接口的写操作丢进 Redis list,PHP 用常驻脚本消费,避免脉冲期间同步写库;下载页加了个 60 秒的排队页,超过 400 并发就让人等一会儿,转化率掉了大概 12%,但总比 502 强。
我自己的一个观点,可能跟主流说法不太一样
很多人聊抗压就是“上云、加机器、买高防”,我觉得顺序反了。脉冲流量的核心矛盾是“时间密度”,不是“总量”。你花两千块升到 8 核 16G,带宽从 5M 到 20M,也就是从每秒扛 2 张图变成 8 张图,该崩还是崩。
正确的优先级我认为是:缓存 > 排队削峰 > 限流 > 加机器。前三步基本不花钱,而且能覆盖 90% 的脉冲场景;加机器是最后的兜底,而且它解决不了突发,只能提高天花板。
另外一点:不要迷信“实时统计”。脉冲来的时候,你的统计代码本身就是压垮服务器的那根稻草。我后来给所有站点的第一条建议都是——统计异步化,日志离线分析,宁可数据晚五分钟,也别让它在高峰跟你抢数据库连接。
朋友那站最后算下来,CDN 一个月 30 多块,Redis 最小规格 10 块,加起来 40 出头。服务器没升配,还是那台 2 核 4G。第二个月他又被推了一次,峰值 9 万 PV,后台一片安静,他晚上 10 点才发现流量来过。