CC 攻击最麻烦的地方不是拦不住,而是打了十几分钟你还没发现。CPU 升高、页面变慢、带宽抬升,这些现象「流量真的涨了」也一样有。本文用 9 个问答讲清怎么用缓存命中率、回源率、PV/UV 比这几个结构性指标在几分钟内判断出 CC 攻击,怎么区分正常增长与恶意消耗,并给出可直接复制的 Nginx 配置与统计命令。
Q:为什么光看 CPU 和负载判断不出 CC 攻击?
因为 CPU、负载、带宽都是总量指标,被攻击和正常促销都会把它们推高。真正的区别在结构上:
- 正常流量上涨:UV 和 PV 同比例上涨,缓存命中率基本不变,单 IP 平均请求数稳定,业务转化率同步提高。
- 被 CC 攻击:PV 猛涨但 UV 几乎不动,单 IP 请求数暴涨,缓存命中率断崖式下跌,业务转化率不动或者反向下降。
结论:总量指标只能告诉你「出事了」,结构性指标才能告诉你「出的是什么事」。
Q:为什么说缓存命中率是 CC 攻击的体温计?
因为命中缓存的请求成本接近零,未命中穿透到后端的请求才真正花钱。命中率下降,意味着同样多的请求里,有更多落到了 PHP 和数据库上。
正常站点的命中率通常很稳定(做得好能给到 85% 到 95%)。CC 攻击者的手法就是专门破坏这个稳定性:带随机参数、打不可缓存的动态路径、伪造 Cookie 让请求绕过缓存。效果就是命中率在几分钟内从 90% 掉到 30% 以下。
所以判断优先级应该是:命中率比 QPS 更早暴露攻击。QPS 上涨会被缓存吸收掉一部分,命中率不会说谎。
Q:怎么取到缓存命中率?
两种方式配合用:日志统计看趋势,stub_status 看瞬时连接状态。
# 1. 自定义日志格式,把缓存状态和回源耗时记下来
log_format cache '$remote_addr [$time_local] "$request" $status '
'cache=$upstream_cache_status up=$upstream_addr '
'rt=$request_time urt=$upstream_response_time';
access_log /var/log/nginx/cache.log cache;
# 2. 统计缓存状态分布,HIT/MISS/EXPIRED/BYPASS 一目了然
awk -F'cache=' '{split($2,a," "); c[a[1]]++} END {for (k in c) print c[k], k}' \
/var/log/nginx/cache.log | sort -rn
# 3. 开启 stub_status 看实时连接数
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
# 查看:curl -s http://127.0.0.1/nginx_status
判断技巧:BYPASS 和 MISS 的占比突然放大,就是攻击者绕过缓存的直接证据。如果 MISS 占比高但请求路径都是随机参数,基本可以定性为 CC。
Q:命中率下降,怎么区分是被攻击还是缓存配置出问题?
按「来源、路径、身份」三个维度依次排除,三条命令就能定位。
# 来源分布:单 IP 请求数 Top 20
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 路径分布:请求 URL Top 20
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 身份分布:User-Agent 聚合占比
awk -F'"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -15
- Top 20 的 IP 占了总请求 30% 以上,而且集中在少数 C 段 → 攻击。
- 某个动态路径占了 40% 以上(搜索、列表、接口)→ 攻击,正常用户的路径分布是分散的长尾。
- UA 高度重复或为空,同时几乎没有 Cookie → 攻击。
- 三个维度都正常,只是整体量涨 → 更可能是真来量,去查业务指标而不是加限速。
Q:攻击者用随机参数把缓存打穿,怎么修?
把无意义的参数丢掉,只保留业务必需的参数白名单,让带随机尾巴的请求落到同一份缓存上。
map $args $cache_ok_args {
default "";
"~(?:^|&)id=([0-9]+)" "&id=$1";
"~(?:^|&)page=([0-9]+)" "&page=$1";
"~(?:^|&)cat=([a-z0-9-]+)" "&cat=$1";
}
proxy_cache_key "$scheme$host$uri$cache_ok_args";
proxy_cache_lock on;
proxy_cache_valid 200 302 10m;
proxy_cache_use_stale updating error timeout http_500 http_502 http_503;
add_header X-Cache-Status $upstream_cache_status always;
两个关键参数:proxy_cache_key 决定「哪些请求算同一个资源」,参数白名单写对,命中率立刻回升;proxy_cache_lock 决定缓存未命中的瞬间是否只有一个请求回源,不开这个开关,攻击者能用并发把缓存击穿,源站在几秒内被打满。
Q:回源率和 PV/UV 比怎么用作判定指标?
回源率 = 1 − 命中率(粗略口径),它对应的才是真正花钱的那部分 QPS。一个正常站点回源 QPS 可能只有总 QPS 的 5% 到 15%,被 CC 时这个比例会冲到 60% 以上。
PV/UV 比是另一个很灵的口径:正常站点大概在 3 到 10 之间,被 CC 打时会冲到 50 甚至上百。
# 粗略计算 UV(去重 IP)和 PV(总行数)
awk '{print $1}' /var/log/nginx/access.log | sort -u | wc -l
wc -l < /var/log/nginx/access.log
# 按分钟统计回源请求数,看是否有台阶式跳变
awk -F'cache=' '/cache=MISS/ {print substr($0, index($0,"[")+1, 17)}' \
/var/log/nginx/cache.log | sort | uniq -c | tail -30
注意 UV 用 IP 去重只是近似值,如果攻击者用代理池,UV 会被虚高,这时候要结合 UA 和 Cookie 一起看。
Q:怎么把业务指标接进来一起判断?
技术指标说明「被打了」,业务指标说明「伤到了哪里」,两者一起看才能判断是否真的需要降级。
- 真来量:摘要、来源页、停留时长正常,搜索成功率、下单转化率同步上涨。
- 被攻击:搜索接口请求量暴涨但搜索成功率不涨(攻击者的关键词没有意义)、加购和下单转化完全不动、接口平均耗时被拖长数倍。
落地方式不复杂:在 Nginx 日志里把接口路径、状态码、耗时结构化出来,按接口维度聚合后打点到监控系统,然后设阈值告警。最值得设的一条告警是「5 分钟内缓存命中率下跌超过 20%」,它比 CPU 到达 90% 要早得多,也比带宽告警更少误报。
Q:发现异常之后,处置顺序应该怎样?
按「成本从低到高」动手,先做不伤用户的动作。
- 先把缓存救回来:修正缓存键、提高缓存有效期、对可静态化的页面做短时静态化。这一步零误伤、见效最快。
- 对异常路径单独限速,不要给整站套统一规则。
limit_req_zone $binary_remote_addr zone=dynamic:20m rate=5r/s;
location ~ ^/(search|api/list|comment) {
limit_req zone=dynamic burst=10 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
- 对 Top IP 段做临时封禁,用 ipset 带 timeout 自动解封,避免忘记删规则。
ipset create ccblock hash:net timeout 3600
ipset add ccblock 1.2.3.0/24
iptables -I INPUT -m set --match-set ccblock src -j DROP
# 一小时后自动解封,查看:ipset list ccblock
- 上人机验证或 JS 挑战,优先给动态接口和搜索接口,不要给全站加。
- 最后才考虑降级:关掉非核心功能(推荐位、评论、相关阅读),返回静态错误页,保住核心页面的可用性。
Q:还有什么补充建议?
- 平时就要建基线:把命中率、回源 QPS、PV/UV 比、单 IP 平均请求数的正常区间记下来,攻击时才有对照物。
- 告警要早不要狠:宁可先收到「命中率异常」的提示去人工确认,也不要等到 CPU 打满才开始处理。
- 日志至少留 7 天:CC 攻击经常是断续的,事后回溯需要完整的请求分布才能封准 IP 段。
- CDN 侧和源站侧指标要对齐:只盯源站,会把 CDN 节点回源当成正常流量;只盯 CDN,会漏掉绕过 CDN 直连源站的那部分攻击。两边同时看,才能真正定位攻击入口。