上周有个客户找我,说他们的订单接口"偶尔超时",但复现极其困难线上跑一天能碰到两三次,日志里只有一句 Read timed out,没有堆栈,没有 5xx,流量监控看着一切正常。客户自己的说法是"可能是网络抖动",但抖多少、抖多久,谁也说不清。
最后我们决定换了个思路:不猜了,直接在服务器上人为制造弱网环境。既然能控制延迟和丢包,就能把"偶尔"变成"每次",问题自然会浮出来。Linux 自带的 tc 命令干这件事是它的老本行,其中 netem 这个队列规则(qdisc)专门用来模拟网络延迟、丢包、抖动、限速这些不理想特性。这一篇就把我这次排查的完整过程和踩到的几个坑写下来。
如果你手上只有一台小机器也想复现这类问题,可以先看看这几家的报价和技术说明:goodlyclouds(美国双 ISP / 英国原生 IP 精品线路)双 ISP 回程线路本身抖动就比普通机房小很多,做弱网对比实验时更干净;干杯cloud(美西 AT&T 原生住宅宽带独立 IP)的 KVM 实例分钟级开通,拿来临时当实验节点很省事。

netem 是挂在网卡出方向(root)的一个队列规则。最小可用的一条命令:
tc qdisc add dev eth0 root netem delay 200ms
执行完不报错,就说明规则挂上了。这时候 ping 一下网关,会发现 RTT 确实多了 200ms 左右。确认生效后再往下走,别一次性把参数全堆上去。
这一步的坑在于netem 会整个替换掉网卡原有的 root qdisc。很多人在上面本来挂过 HTB 做限速、或者挂着 fq 做多队列调度,直接 add 一个 netem 上去,原来那些整形规则全没了。表现出来的现象很迷惑:明明之前限速限得好好的,加完 netem 之后带宽限制失效了。
稳妥的做法是先看一眼现状:
tc qdisc show dev eth0
如果原来已经有别的 root 规则,要么用 tc qdisc change 改,要么把 netem 挂到下层(比如挂在 HTB 的某个 class 下面)。

我要模拟的是"延迟 200ms,同时丢包 5%"。第一条命令我是这么写的:
tc qdisc add dev eth0 root netem delay 200ms loss 5%
看起来没毛病,但 tc qdisc show 里只有一条 netem,丢包没生效。原因是我把 loss 写在了 delay 后面,参数解析时 loss 被当成了 delay 的修饰参数给吞掉了netem 的参数是按顺序逐个消费的,不是按名字取的。
正确写法是先 limit 再 delay 再 loss,或者干脆每类参数单独一条命令叠加:
tc qdisc add dev eth0 root netem limit 10000 delay 200ms 50ms loss 5%
这里的 limit 也不能省。netem 默认的队列上限是 1000 个包,一旦超了,新到的包直接被丢弃,表现就是"加了 200ms 延迟之后带宽莫名其妙掉了一大截"。这是很多人的第一个疑问:明明我只加了延迟,怎么吞吐也降了?答案就是 limit 太小,队列爆了。
还有一个小坑,delay 后面那个 50ms 是抖动量,意思是延迟在 150ms~250ms 之间随机浮动。如果你想要的是稳定的 200ms,就别加第二个数字,不然你会看到 RTT 一直跳。

实验做完要恢复。第一种写法:
tc qdisc del dev eth0 root
如果网卡上原本没有 root 规则、或者你删错了设备,这条会报 RTNETLINK answers: No such file or directory,属正常,报完就说明本来就没东西可删。但如果网卡上原本挂着别的业务 qdisc,这一下就把生产规则一起干掉了。
更稳的做法是平时就把实验规则挂在一个单独的 class 下面,删除时只动自己那一层;或者干脆用另一个网络命名空间(ip netns)做实验,规则和真实网卡完全隔离,测完直接删命名空间,不留任何痕迹。
恢复之后一定要验证,别默认删干净了:
tc qdisc show dev eth0
ping -c 5 <目标地址>
我们那次恢复完发现 tc qdisc show 里还残留一条 netem,延迟还有 100ms,整个测试环境的基线全错了,后面的结论差点被带偏。所以"删完必须复测"这条我写进了自己的检查清单。
netem 是挂在网卡 root 上的队列规则,作用的是这台机器发出去的包。回包方向要加在对面那台机器上,或者用 ifb(Intermediate Functional Block)把入方向重定向出来再整形,这是另一个坑了。我们第一版测试就因为只配了出方向,结果"客户端超时、服务端正常",误判成服务端代码问题,浪费了大半天。
弱网实验的最终结论是:客户那条超时链路在延迟超过 120ms 加上 3% 丢包时必现,而代码里有个 HTTP 客户端默认超时写的是 100ms。这事最后是改了两行配置加一次连接池调整解决的,跟服务器性能一点关系都没有如果当初没把弱网环境复现出来,这个锅大概率会扣到服务器头上。

所以 tc netem 这个工具,值得每个做线上排查的人抽半小时熟悉一下。它不能帮你解决任何性能问题,但能帮你把"感觉是网络问题"变成一个可以复现、可以量化、可以写进报告的具体条件。
