
上周有个做后端的朋友半夜给我打电话说他们服务的监控面板全红了带宽被跑满、连接数爆表、CPU 直接打满运维群里第一句话就是完了DDoS 攻击这玩意儿无解。这个说法我在圈子里听了十多年从早期的论坛帖子到现在的技术群只要有人提到 DDoS 攻击底下必然有人接一句无解然后话题就结束了。doss 攻击这个写法其实圈内也常见正确写法是 DDoSDistributed Denial of Service分布式拒绝服务。它本身不是什么高深漏洞利用就是拿海量流量或者海量请求把一个服务的处理能力、带宽或者连接资源耗尽让正常用户进不来。这篇文章我想把这个无解拆开聊聊——它在什么意义上是真的无解在什么地方其实是被夸大了以及一个普通工程团队在预算有限的情况下到底能做到什么程度。内容偏防御视角适合后端开发、运维、SRE 以及对网络安全感兴趣的同学参考有基础能看懂没基础也能建立整体认知。1. 先把DDoS 攻击无解这句话拆开看1.1 无解这个结论到底是从哪来的严格说DDoS 不是无解而是没有便宜的通用解。这两句话差得很远但在传播过程中很容易被简化成前者。我见过太多团队的错误预期就是把 DDoS 当成一个可以修好的 bug——装个防护产品、加几条策略、重启一下服务问题就消失了。实际上它更像是一场持续的拉锯战你的目标从来不是打赢而是把损失控制在可接受范围内。之所以会给人无解的错觉根源在于它的攻击模型和传统漏洞完全不同。传统漏洞是打一个点你补上就完了DDoS 是打你的资源上限你补掉一个入口攻击者把流量提到两倍你还是得顶。这就好比一个小区门口本来只够两个人并排走现在来了两万个人同时挤过来你加固门框加固锁芯都没用问题出在门本身的宽度上。还有一个心理层面的因素DDoS 攻击的防御效果是看不见的。你花了钱、做了配置、清洗链路也搭好了业务稳定运行但没有人能证明这是因为防护生效还是因为这次没人打你。这种投入产出比不直观的防御工作天然容易被贴上没意义的标签久而久之就成了无解。1.2 四个常见的误读每一个我都踩过在开始讲原理之前先把几个容易跑偏的认知拆掉这些是我在实际项目里真金白银换来的。第一个误读是**封 IP 就能解决**。攻击来源是分散在各地的大量主机你今天封掉一万个 IP明天可能换两万个新 IP 上来。我在一个模拟项目里做过统计一次中等规模的流量攻击来源地址的集合规模可以轻松突破六位数而且每个地址贡献的流量都很小单看某一条连接甚至完全正常。靠黑名单硬堵本质上是用人力对抗自动化。第二个误读是**流量小就没事**。这是最容易被忽略的。攻击者不一定非要打你几百 G很多情况下几十 Mbps 的应用层请求就能让一个没做限流的服务彻底趴窝因为消耗的不是带宽而是数据库连接、线程池、后端接口的响应时间。反过来一个做了良好限流和缓存的服务可能扛住比它带宽大好几十倍的攻击。第三个误读是**上了云就自动安全**。云厂商确实提供基础的流量清洗能力但默认档位通常只覆盖小规模的流量型攻击对应用层攻击的识别往往需要额外配置策略。我见过不止一个团队以为开了基础防护就万事大吉结果被一波慢速连接直接打死因为慢速攻击在流量图上几乎看不出来。第四个误读是**无解所以不用管**。这是最要命的一个。DDoS 防御真正有效的地方不在于完全挡住而在于把攻击的成本拉高、把业务的恢复时间压缩。攻击者也是算成本账的当他发现打你一小时的花费远高于预期收益多数会换目标。这个思路后面会展开讲。1.3 成本不对称真正让人绝望的那笔账说无解的人多半是被这笔账劝退的。攻击方和防御方的成本结构天生不对等。攻击方需要的资源相对廉价一批被控制的主机、一段可被滥用的公开服务、一套自动化脚本。在很多公开报告里发起一次持续数小时的中等规模攻击成本常常被估算在几十到几百美元的区间。而防御方要顶住同样的攻击需要的是带宽冗余、清洗设备或清洗服务、策略调优的人力以及被攻击期间的业务损失。这个数量级差距才是最让人难受的地方。更麻烦的是这个差距不是线性的。你为了顶住 10 G 的攻击上了 20 G 的冗余攻击者把规模提到 50 G你要么继续加钱要么就得换架构。防御方的每一次升级都是持续支出攻击方的每一次升级只是多租一点资源。这种你追我赶、成本不对称的结构是 DDoS 防御长期做不完的根本原因。但这不等于躺平。理性的做法是给业务定一个清晰的风险等级核心交易链路按必须活来设计边缘业务按可以短暂降级来设计非关键服务干脆接受攻击期间下线。把有限预算花在刀刃上比无差别堆防护有效得多。2. 从协议栈往下看攻击为什么总能找到缝隙2.1 流量型攻击目标不是打垮服务器是打满管道流量型攻击Volumetric Attack是最直观的一类代表就是 UDP Flood、ICMP Flood 以及各种反射放大。它的逻辑非常简单粗暴不追求精巧就是用远超过你带宽上限的流量把你的出口堵死。这时候你的服务器可能 CPU 还是 5%、内存还剩一大半但外面的用户就是连不进来因为数据包在运营商那一层就被丢掉了。这类攻击的可怕之处在于它打的是你控制不了的部分。你的服务器再强也管不了上游链路的带宽。所以这类攻击的防御重点从来不在服务器上而在于流量进入你机房之前的那一段——也就是清洗中心或者 CDN 边缘节点。我做过一个简单的推演假设一个服务的正常峰值带宽是 200 Mbps出口带宽 1 Gbps。攻击流量达到 800 Mbps 时链路接近饱和正常用户的丢包率开始上升达到 1.5 Gbps 时业务基本不可用服务器本身却毫无感知监控面板上 CPU 曲线平稳得让人怀疑是不是监控坏了。这就是流量型攻击最迷惑人的地方。判断是不是流量型攻击看两个指标最直接出口带宽曲线是否突然拉成一条直线以及服务器内部资源是否与外部不可用状态严重脱节。这两个现象同时出现基本可以确定是链路被打满了。2.2 协议型攻击半连接队列与连接表协议型攻击Protocol Attack打的是协议实现里的资源上限最典型的就是 SYN Flood。它利用的是 TCP 三次握手的机制客户端发 SYN服务端回 SYN-ACK 并把这个连接放进半连接队列等待确认客户端再回 ACK连接建立。攻击者伪造源地址只发第一个 SYN 就不管了服务端傻乎乎地等着半连接队列很快被占满新的正常连接请求直接被丢弃。这个攻击的精妙之处在于它消耗的是内核里一个非常有限的资源。半连接队列的大小由tcp_max_syn_backlog这类参数控制全连接队列由somaxconn控制都是固定的数字。攻击者只要并发量超过这个数字服务端就挂了而这个数字通常只有几千。换句话说攻击者用几千个伪造的包就能让一台配置再豪华的机器拒绝服务。除了 SYN Flood还有一类容易被忽略的连接表耗尽。很多服务前面有 NAT 或者防火墙会维护一张连接跟踪表conntrack这张表也有上限。攻击者用大量短连接或者畸形包把表塞满后续正常连接就无法被跟踪表现同样是服务不可用。这个问题在容器化环境里尤其常见因为默认的nf_conntrack_max往往没有跟着业务规模调大。协议型攻击的防御相对成熟核心手段就是SYN Cookie服务端不再为每个 SYN 单独分配资源而是把连接信息编码进序列号里等客户端回 ACK 时再还原。这样即使半连接队列被打满正常用户依然能建立连接。代价是部分 TCP 选项会丢失但在攻击场景下这是值得的取舍。2.3 应用层攻击伪装成正常用户的请求如果说前两类攻击是用蛮力砸门那应用层攻击Application Layer Attack也常被叫做 CC 攻击就是装成住户骗开门。这类攻击的流量可能只有几 Mbps从带宽图上看几乎毫无异常但它打的是你的业务逻辑一个需要查三次数据库的搜索接口、一个需要渲染复杂模板的详情页、一个需要调用第三方支付的提单流程。我曾经在模拟环境里做过一次压测一个没做任何缓存的列表接口单次请求要执行 8 条 SQL平均耗时 120 ms。攻击者只要维持 200 QPS 的请求量就能把这台机器的数据库连接池打满而 200 QPS 对应的带宽消耗还不到 1 Mbps。这种量级的攻击任何基于流量阈值的防护都不会触发告警。更麻烦的是应用层攻击可以做成慢速的。比如 Slowloris 这类手法攻击者建立大量连接然后一点一点地发 HTTP 头永远不发完服务端就一直占着这个连接等。每个连接消耗的资源都很小但几百上千个连接叠起来worker 进程就被占满了。这类攻击在流量图上的表现几乎是平的只能通过并发连接数和连接平均存活时长这两个指标来识别。应用层防御的核心思路是从看流量转向看行为请求频率是否异常、请求路径分布是否异常、User-Agent 和指纹是否高度雷同、是否存在大量没有完整会话的请求、单个来源的请求间隔是否机械规律。这些特征的组合判断比任何单一阈值都可靠。2.4 反射放大让别人替你打反射放大Reflection Amplification是这几年来最高效的攻击方式因为它把成本不对称推到了极致。原理是攻击者伪造受害者的源 IP向互联网上大量开放的公共服务发查询请求这些服务把响应包发回给受害者。由于响应包远大于请求包攻击者用很小的上行带宽就能制造出几十倍甚至上万倍的攻击流量。公开研究里经常被引用的几个放大倍数DNS 查询放大在几十倍量级NTP 的 monlist 查询在几百倍量级Memcached 在某些配置下甚至能达到数万倍。这个倍数意味着什么攻击者手上有 1 Mbps 的上传能力理论上就能制造出上百 Mbps 甚至更高的攻击流量。这就是为什么很多小团队会莫名其妙地被几十 G 的攻击打中——他们根本不是被针对了只是被随机选成了反射目标。反射放大的防御有两个层面。对外的层面是确保自己的服务不会被别人拿去当放大器DNS 服务器关闭递归查询、限制 ANY 类型查询、NTP 服务升级到不再响应 monlist 的版本、Memcached 绝不能暴露在公网。这些配置做起来不复杂但漏掉一个你的服务器就成了攻击别人的工具。对内的层面是在边界上做源地址校验BCP 38 的反向路径过滤思路减少伪造源 IP 的包流出这需要和上游运营商配合。3. 一次防御推演的完整流程3.1 攻击前先把攻击面摸清楚防御工作的第一步不是配防护而是搞清楚自己有什么。我见过太多团队攻击来了才开始翻文档找 IP结果发现有三台测试机还挂在公网、两个旧域名还解析着老地址、一个对象存储桶是公开读的。这些影子资产往往是攻击者最先得手的地方因为没人管也就没人加固。具体做法我一般分三步走。第一步是资产盘点把所有对公网暴露的 IP、域名、端口、协议列成表标注负责人和业务用途。这一步用扫描工具辅助但最终要以人工确认为准因为扫描器也会漏。第二步是暴露面收敛能不对公网暴露的一律收到内网必须暴露的走统一入口管理后台加白名单或者加一层跳板。第三步是依赖梳理把 DNS 服务商、CDN 服务商、云厂商的应急联系方式整理好攻击真的来了你需要的是能在五分钟内找到人而不是现查工单系统。这里有个实操心得把这份资产表做成离线版本存在本地。攻击期间内网可能也受影响你在系统里查不到东西的时候一张打印出来的表能救急。我第三次被攻击之后才养成这个习惯虽然土但真的有用。3.2 攻击中清洗链路的搭建与流量牵引流量清洗的基本模型是正常访问先到清洗中心或者 CDN 边缘那里做流量分析和过滤干净的流量再回源到你的服务器。这个链路的关键在于源站不能被绕过否则攻击者直接打源站 IP清洗就白做了。源站隐藏的几个实操要点。第一源站只接受来自清洗节点的回源流量用白名单在防火墙上限制其他来源一律丢弃。第二源站不要和公开业务共用同一个 IP 段避免被顺藤摸瓜扫出来。第三历史 DNS 记录会泄露源站切换 CDN 之前要把老记录清理干净尤其是那些备用域名测试域名。第四邮件服务MX 记录经常直接指向源站这是最容易被忽略的泄露点邮件最好走独立的出口。流量牵引的方式上常见的有 DNS 牵引和 BGP 牵引两种。DNS 牵引改解析生效快但受 TTL 影响客户端缓存没到期的话仍然会有一部分流量打到源站。BGP 牵引是在路由层面把整个 IP 段的流量导到清洗中心切换更彻底但需要和运营商配合响应时间通常在分钟级。规模不大的团队用 CDN 加回源白名单这套组合基本够用成本也低。3.3 源站加固内核参数与限流配置清洗中心挡住了大部分流量回源的那部分仍然需要源站自己能扛。这一层的重点是两个内核参数和限流策略。内核参数这块针对 SYN Flood 和连接表耗尽我一般会调这几项。注意不同内核版本参数含义有差异改之前先在测试环境验证生产环境改完要观察一段时间。# SYN Cookie半连接队列打满时仍能建立连接 net.ipv4.tcp_syncookies 1 # 半连接队列上限按业务并发量调不要盲目贪大 net.ipv4.tcp_max_syn_backlog 16384 # 全连接队列上限配合应用的 backlog 一起调 net.core.somaxconn 32768 # SYN-ACK 重试次数调小可以更快释放半连接 net.ipv4.tcp_synack_retries 2 # 连接跟踪表上限容器环境尤其要注意 net.netfilter.nf_conntrack_max 1048576 # 缩短 FIN_WAIT 状态的停留时间 net.ipv4.tcp_fin_timeout 15改完用sysctl -p生效然后用ss -s看整体连接状态用ss -ant state syn-recv | wc -l观察半连接数量是否还在持续堆积。如果 SYN Cookie 已经生效这个数字会维持在一个可控范围内波动而不是一路涨。限流这块Nginx 层面的配置最直接。我的习惯是按来源 IP 做请求速率和并发连接双重限制同时把各种超时时间压短防止慢速连接占坑。# 定义限流区域每个 IP 每秒 10 个请求并发 20 个连接 limit_req_zone $binary_remote_addr zonereq_per_ip:10m rate10r/s; limit_conn_zone $binary_remote_addr zoneconn_per_ip:10m; server { listen 443 ssl; # 连接数上限 limit_conn conn_per_ip 20; # 请求速率限制burst 允许短时突发nodelay 不排队直接拒绝 limit_req zonereq_per_ip burst30 nodelay; # 超时压缩专门对付慢速攻击 client_header_timeout 10s; client_body_timeout 10s; keepalive_timeout 30s; send_timeout 10s; proxy_read_timeout 15s; }这里有个容易踩的坑limit_req的 rate 设得太紧会误伤正常用户尤其是那种页面加载时并发请求几十个静态资源的场景。我的经验是先按正常峰值的 3 到 5 倍设定攻击期间再动态收紧而不是一上来就卡死。另外如果前面有 CDN$binary_remote_addr拿到的是 CDN 节点的 IP一定要配置set_real_ip_from和real_ip_header不然限流会变成把 CDN 节点全限死。对更底层的简单洪水可以用 ipset 配合 iptables 做临时封禁比逐条加规则高效得多# 建一个带过期时间的黑名单集合 ipset create blocklist hash:ip timeout 1800 # 把命中规则的源地址加进去半小时后自动解封 ipset add blocklist 203.0.113.10 # 在入站链上引用这个集合 iptables -I INPUT -m set --match-set blocklist src -j DROP3.4 应用层防护挑战、指纹与行为识别应用层是防御里最花心思的一层因为这里的判断永远是概率性的。你不可能在不知道谁是正常用户的前提下区分攻击流量所以所有手段都是在提高攻击者的成本而不是做绝对的拦截。最基础的一层是缓存。把能静态化的接口全部静态化把能放 CDN 的页面全部推上去把热门查询结果放进本地缓存。这一招看起来和防护无关但它直接把攻击者能触达的后端资源砍掉一大半。我做过对比同一个列表接口加上 60 秒缓存之后同样的攻击流量对数据库的冲击下降了一个数量级。第二层是挑战机制。对可疑请求下发一段轻量的 JS 计算或者验证码正常浏览器能自动完成脚本化的请求往往过不去。这一层的精妙之处在于它对用户体验的影响可控——只对可疑流量触发正常用户无感知。配置的时候要注意不要对 API 客户端也下发挑战否则会把自己的 App 打死。第三层是行为指纹。看请求的时间间隔是否机械规律、看同一个来源是否只请求最耗资源的接口、看 TLS 握手的指纹是否高度集中、看 Cookie 和会话是否完整。这几个特征组合起来识别率相当可观。第四层是业务降级预案。这一层经常被忽略但它是最实在的。攻击来了之后把非核心功能关掉——推荐位不刷了、评论不加载了、图片降清晰度、搜索走缓存。让核心链路活着比什么都重要。我在一个模拟项目里把降级预案做成一个开关运维按一个按钮就能生效实测下来能把后端的资源消耗压到平时的三成左右。4. 常见问题与排查实录4.1 攻击来了监控却没报这是我在多个项目里遇到过的高频问题。表现是用户已经在群里喊打不开了监控面板一片绿色告警一条没发。原因通常有三个一是监控指标选错了只监控了 CPU 和内存而攻击打的是带宽和连接数二是阈值设得太高比如带宽告警设在 90%但业务在 60% 的时候就已经开始丢包了三是监控采样的间隔太长一分钟采一次攻击的尖峰被平均掉了。排查思路是先把用户体验和系统指标对齐。具体做法是在监控里把入口带宽、活跃连接数、新建连接速率、半连接队列长度、上游响应时间 P99这几个指标同时展示然后回看历史数据找出业务开始异常时这几个指标的实际水位。这个水位才是你应该设的告警线而不是拍脑袋定的百分比。我给一个项目做过这个对齐最后把带宽告警线从 90% 调到了 55%之后几次小规模攻击都在用户感知之前就被发现了。4.2 清洗生效后业务反而变慢开启清洗之后正常用户的访问变慢这在实操里非常常见原因基本集中在两点。一是回源链路变长了。清洗节点往往在离源站较远的地方回源要跨地域RTT 增加几十毫秒很正常。解决办法是把清洗节点选在离源站近的区域或者开启长连接复用回源减少握手开销。静态资源尽量在边缘节点直接返回不要回源。二是清洗策略过于激进把正常请求也当成可疑流量处理了走了挑战或者二次验证流程。这时候要去清洗服务商的控制台看拦截日志抽样分析被拦截的请求特征找到误判的规则。我通常会让服务商提供一份被拦截请求的样本然后对照自己的业务正常请求做比对看是哪条特征把正常流量误伤了。4.3 误杀正常用户怎么定位误杀的本质是规则把某个看起来像攻击的特征当成了攻击。定位的方法是从投诉用户入手拿到他的来源 IP、访问时间、User-Agent然后去日志里反查这个 IP 在误杀时间点的请求序列看它命中了哪条规则。常见的误杀场景有这么几个办公区出口 IP 共享几十个人共用一个公网 IP按 IP 限流必然误伤移动网络切换基站导致 IP 频繁变化被判定成异常搜索引擎爬虫或者监控探针的高频请求被当成攻击流量。这几种情况的处理方式不同共享出口要给白名单或者按会话限流爬虫要按 UA 和反向解析做放行监控探针最好直接加白。这里有个经验任何限流规则上线前先跑一段观察模式只记录不拦截观察一周之后再打开拦截。这一步花不了多少时间但能避免绝大部分误杀事故。4.4 常见问题速查表现象最可能的原因优先排查方向带宽拉满但 CPU 正常流量型攻击或大文件盗链出口带宽曲线、TOP 来源 IP 分布CPU 打满但带宽正常应用层攻击或慢速连接占坑并发连接数、单 IP 请求频率、接口耗时分布半连接数持续上万SYN Floodss -s输出、SYN Cookie 是否开启连接建立失败但网络通连接跟踪表满或全连接队列溢出dmesg里是否有 table full 相关记录数据库连接池耗尽应用层攻击打到了高消耗接口接口维度 QPS、慢查询日志、缓存命中率少量用户反馈异常限流规则误杀清洗日志里的拦截样本、投诉用户 IP 的请求序列清洗后延迟整体上升回源链路变长或策略过激回源 RTT、边缘缓存命中率、挑战触发比例5. 把无解变成有限损失的几个实操思路5.1 分层防御的取舍逻辑防御体系不是越全越好而是要匹配业务的风险等级。我的做法是把业务分成三档每档给不同的防护目标。核心交易链路目标是攻击期间仍然可用。这一档要上 CDN、上清洗、做多可用区、做限流降级成本最高但必须保。一般业务功能目标是攻击期间可以降级但不断。这一档做好限流和缓存就够攻击来了关掉非核心功能。边缘和内部服务目标是攻击期间可以主动下线。这一档不投入防护资源攻击来了直接切断公网入口等风头过了再恢复。这个分档的价值在于它让预算有了明确的去处。很多团队的问题不是钱不够而是钱花得平均每一层都做了一点每一层都不够用。分档之后核心链路能拿到足够的资源边缘业务也不会因为怕出事而占用不必要的预算。5.2 预案演练比买设备更重要我见过最尴尬的场景是防护设备买齐了清洗服务也开了攻击真的来了没人知道该点哪个按钮。值班的人翻文档翻了半小时等操作完业务已经挂了两个小时。所以预案必须提前写成傻瓜式操作单一页纸写清楚什么现象触发什么级别、每一级谁负责、第一步做什么第二步做什么、找谁要授权、找谁联系服务商。这份单子要打印出来贴在工位上不能只存在文档系统里。演练的频次我建议至少每季度一次模拟一次完整的攻击响应流程从发现到牵引到降级到恢复全程计时。演练里最容易暴露的问题是权限。改 DNS 需要谁的账号、调清洗策略需要谁的审批、关服务需要谁点头这些在平时都不是问题在凌晨三点就是大问题。提前把应急权限梳理清楚比任何技术手段都值钱。5.3 我个人踩过的几个坑说几个具体的教训都是实实在在付出过代价的。有一次我们以为把源站 IP 藏好了结果攻击者从一封历史邮件的邮件头里拿到了真实 IP绕过 CDN 直接打源站。从那之后所有对外发信统一走独立的邮件出口和业务源站彻底隔离。还有一次是内核参数改完没重启验证nf_conntrack_max的修改在某些环境下需要重载模块才生效结果攻击来了才发现根本没起作用。现在的习惯是改完参数必须用sysctl读一遍确认值再做一次小流量压测验证。最深刻的一次是降级预案没演练过真到用的时候发现某个开关会连带把登录也关掉直接导致所有用户掉线。降级不是简单地关功能而是要理清依赖关系哪些能关、哪些关了会连带影响核心链路这个必须提前画出来。说到底DDoS 攻击的无解从来不是技术上的绝对无解而是没有一劳永逸的低成本解。把预期从完全挡住调整到控制损失、快速恢复把资源集中在核心链路上把预案做成能直接执行的清单这套组合下来绝大多数团队都能把一次攻击的影响从业务全挂几小时压缩到部分功能降级几十分钟。这个差距在实际业务里就是实打实的价值。