被DDoS打了先保谁?业务优先级取舍实战

当 DDoS 攻击把带宽和连接表同时打到极限时,服务器能做的其实只有一件事:把有限的资源留给你最输不起的业务。本文用 9 个问答讲清 DDoS 业务优先级取舍的判定方法、网关层分流配置、分级降级策略与切换清洗的时机。

Q:为什么「所有业务一起保」等于全部保不住?

资源是守恒的。一次 20Gbps 的四层攻击会同时打满三样东西:出口带宽、连接跟踪表条目、内核的 SYN_RECV 队列。这三项都不是「多给点 CPU」能缓解的,所以平均分配的结果是每个业务都卡到不可用。

  • 带宽被塞满后,所有业务的 RTT 一起抬升,支付接口超时率飙升。
  • 连接表被打满后,新连接直接 SYN dropped,连 SSH 都进不去。
  • 源站进程被大量半开连接拖住,健康检查失败,负载均衡把节点全部摘掉。

结论很直接:被攻击时的目标不是「都活着」,而是「核心业务活着」。取舍不是妥协,而是应急设计的一部分,必须提前写进预案,而不是等打上门才临时决定。

Q:怎么给业务定优先级?分几档比较合理?

建议分三档,判断标准是「停一小时损失多少」和「能不能事后补回来」:

  • P0 不可停:登录鉴权、订单与支付、核心 API。停摆即直接损失收入,且无法补回。
  • P1 可降级:商品详情、内容页、用户中心。可以变慢、可以少功能,但不能白屏。
  • P2 可暂弃:图片与视频原图、导出下载、历史报表、站内搜索、统计与推荐位接口。

把这份清单写成一文件,而不是留在脑子里。示例结构:

# /etc/nginx/conf.d/priority.conf
# P0: /api/pay /api/auth /api/order
# P1: / /product /user
# P2: /static/img /download /export /search /stats

关键点:P2 的判定要往前放。攻击刚开始时就该把 P2 先摘掉,而不是等 P0 出问题了才动手,这时已经来不及了。

Q:「保谁弃谁」的判定依据是什么?

四个维度打分,不要只看访问量:

  • 收入相关度:直接产生交易的最优先,展示类最低。
  • 资源占用比:同样保 1 个 QPS,接口只吃 CPU 几毫秒,图片要吃几 MB 带宽。优先弃「单位请求消耗大」的。
  • 可替代性:能走 CDN 的(静态资源)、能延迟处理的(报表导出)、能事后补的,全部排后面。
  • 用户感知强度:白屏最刺眼,转圈次之,提示「功能维护中」感知最低。
# 现场看谁在吃资源:按请求的响应字节排序,找出消耗最大的路径
awk '{print $7, $10}' /var/log/nginx/access.log \
  | awk '{s[$1]+=$2; c[$1]++} END {for(i in s) printf "%12.0f  %8d  %s\n", s[i], c[i], i}' \
  | sort -rn | head -20

读法:字节数大但请求数少的路径,是最值得先弃的;请求数大但字节小的路径(比如 API),弃掉的收益低,应该保。

Q:怎么在 Nginx 或负载均衡层做业务级分流?

核心思路是给不同优先级打不同的限速与并发额度,让 P2 先被削掉,P0 始终有配额。用 map 按路径给不同 zone 是最干净的写法:

map $uri $biz_level {
    default            p1;
    ~^/api/(pay|auth|order)   p0;
    ~^/(product|user)         p1;
    ~^/(static|download|export|search|stats)  p2;
}

map $biz_level $limit_key {
    p0  "";                    # P0 不限速,空 key 不计入 limit_req
    p1  $binary_remote_addr;
    p2  $binary_remote_addr;
}

limit_req_zone $limit_key zone=p1zone:10m rate=30r/s;
limit_req_zone $limit_key zone=p2zone:10m rate=2r/s;

server {
    location / {
        limit_req zone=p1zone burst=60 nodelay;
    }
    location ~ ^/(static|download|export|search|stats) {
        limit_req zone=p2zone burst=5 nodelay;
        limit_conn p2conn 2;
    }
}
limit_conn_zone $binary_remote_addr zone=p2conn:10m;

两个注意点:一是 P0 用空 key 免限速的前提是它前面已经有鉴权,否则攻击者直接打 P0 路径就能绕过所有限制;二是所有限速都要配 limit_req_status 429,返回语义正确的错误码,方便前端做区分处理。

Q:图片和视频这类大流量业务被打了怎么办?

P2 里最典型的就是静态大文件。处置顺序是「先切走、再限速、最后才封」:

# 1. 把静态资源整段交给 CDN,源站只服务回源
location ~* \.(jpg|jpeg|png|gif|webp|mp4|zip)$ {
    # 不在 CDN 回源白名单里的直连请求,直接拒绝
    if ($http_x_from_cdn = "") { return 403; }
    limit_conn dl 4;
    limit_rate_after 1m;
    limit_rate 2m;
    expires 30d;
    add_header Cache-Control "public, max-age=2592000";
}

要点说明:只要不是 CDN 回源就直接拒绝,这一步比限速有效得多,因为它把攻击流量挡在了 CDN 边缘,根本到不了源站带宽。同时防盗链要升级为签名 URL,只做 Referer 白名单很容易被伪造。

Q:数据库和接口层怎么保住?

应用层被打时,真正先崩的往往是数据库。三件事按顺序做:

  • 读写分离 + 只读降级:攻击期间把非核心查询全部路由到只读库,主库只保留写入。
  • 接口分级超时:P0 接口超时给 3 秒,P1 给 1 秒,P2 直接快速失败,不要让它占着连接池排队。
  • 热点数据前置缓存:把商品、配置这类高频只读数据放 Redis,攻击期间缓存 TTL 临时延长到 10 分钟,容忍短暂数据陈旧换来可用性。
# 接口快速失败示例(Nginx proxy 层面)
location /api/ {
    proxy_connect_timeout 1s;
    proxy_read_timeout    3s;
    proxy_next_upstream error timeout http_502;
    proxy_cache api_cache;
    proxy_cache_valid 200 10s;
    proxy_cache_use_stale error timeout updating;
}

proxy_cache_use_stale 这一项在攻击期间价值极高:上游超时时直接返回陈旧缓存而不是报错,用户看到的是「数据有点旧」,而不是「服务不可用」。

Q:用户侧怎么降级?错误页和排队怎么做?

降级的核心是「让用户知道自己被限了,而不是以为网站坏了」:

  • 静态化兜底:核心内容页预生成 HTML,数据库挂了也能返回。
  • 排队页:用 302 跳到一个静态等待页,前端每 5 秒轮询一次放行接口,避免用户反复刷新加剧压力。
  • 语义化错误页:503 页要写「当前访问量过大,请稍后重试」,不要返回默认的 Nginx 页面。
# 用 token 桶思路做排队:放行号小于阈值的才进真实服务
location = /queue {
    default_type text/plain;
    add_header Cache-Control "no-store";
    return 200 "wait\n";
}
error_page 503 /static/overload.html;
location = /static/overload.html {
    internal;
    root /var/www/static;
    add_header Retry-After 5;
}

Retry-After 头一定要加,很多正常的采集合规爬虫与浏览器会尊重它,能自发降低重试频率,这是几乎零成本的压力削减。

Q:什么时候该切高防或者进黑洞?

判定不看攻击的绝对带宽,而看源站还剩多少可用余量:

# 三个数一起看:出口带宽占用、CPU 使用、连接数
sar -n DEV 1 3 | grep -E "IFACE|eth0"          # 带宽是否接近出口上限
ss -s                                          # 连接总数与 TIME_WAIT 占比
uptime                                         # 负载均值
  • 带宽占用超过出口 70%、且持续 5 分钟以上:该切高防牵引了,源站自身已经没有处置空间。
  • CPU 正常但带宽满:四层攻击,清洗层能解决,切高防或 CDN 分流都有效。
  • 带宽正常但 CPU 和连接数飙升:七层攻击,切高防收益有限,应该走 WAF 加业务降级。

特别提醒:黑洞是最后手段,它会让你的 IP 完全不可达,包括 SSH。触发黑洞后必须用同机房的跳板机或服务商控制台处理,不要指望在失联的情况下远程操作。

Q:还有什么补充建议?

  • 预案要提前演练一次。把「关掉 P2、切 CDN、开排队页」写成一键脚本,实测一遍耗时,攻击当天才不会手忙脚乱。
  • P2 清单要按季度更新。业务上线后路径会变,旧清单可能把新核心业务误判成可弃对象。
  • 降级要有开关和留痕。每次降级记录触发时间、原因、影响范围,否则事后复盘说不清是哪一步救了业务。
  • 别把「加带宽」当下策而是最后手段。如果限速与分流都没做,加了带宽同样会被吃满,还会把成本曲线一起抬高。

一句话总结:DDoS 应急的本质是资源分配问题,而不是攻防技术问题。提前把业务分级、把降级开关做好,比临场指挥有效得多。