【背景】
8 年网络工程,日常负责跨机房 HTTP/1.1 + gRPC over HTTP/2 调用链的稳定性。单侧新建连接速率峰值约 800 conn/s,存量长连接 2 万上下。过去 12 个月,我复盘了至少 7 次 P99 抖动工单,每次开工第一件事都是同一个问题:这次抖动到底是应用层的事,还是网络层的事? 这个问题如果定不下来,后面所有排查都是扯皮。
我列一下 7 次工单的实际归因分布(脱敏后):
| 序号 | 现象 | 最终根因 | 归因 | |------|------|----------|------| | 1 | P99 从 18ms → 95ms,持续 4min | JVM Full GC 停顿 80ms × 2 次 | 应用层 | | 2 | P99 从 12ms → 110ms,跨机房链路 | 某台 ToR 交换机端口 CRC 错误,丢包率 0.3% | 网络层 | | 3 | P99 从 25ms → 60ms,间歇性 | 下游服务连接池打满,queue wait 200ms+ | 应用层 | | 4 | P99 从 15ms → 200ms,单实例 | 容器 cgroup CPU throttle(cpu.max 设低了) | 应用层(K8s 配置) | | 5 | P99 从 10ms → 80ms,跨 AZ | 中间 NAT 设备连接表项老化,大量半开连接 | 网络层 | | 6 | P99 从 20ms → 150ms,全量 | 核心路由 BGP flap,ECMP hash 不均 | 网络层 | | 7 | P99 从 14ms → 45ms,单服务 | 慢 SQL 占连接,其他请求排队 | 应用层 |
7 次里 3 次网络、4 次应用。但关键是:在工单提交时,有 5 次应用团队第一反应是「网络抖动」,1 次网络团队第一反应是「应用自己的问题」。
【常见分歧:三种典型做法】
做法 A:「默认甩网络」型
应用团队说:「我们代码没问题,肯定是网络。」网络团队抓包半天,没抓到明显丢包(因为丢包率 0.3% 在抓包窗口内可能只丢 2 个包),最后靠 eBPF 的 tcp_retrans 计数器才定位到。耗时 3 天。
做法 B:「默认甩应用」型 网络团队说:「交换机计数没问题(input errors = 0),你们自己看吧。」应用团队看应用日志,P99 确实高了但看不到具体原因,最后靠用户投诉才暴露,一查是 NAT 设备表项满。耗时 5 天。
做法 C:「各看各的」型 应用看应用日志、网络看交换机计数,两边都觉得自己这边没问题,最后靠一次「同机房 vs 跨机房」的对比实验才定位。耗时 2 周。
【我的倾向:一条 5 步判定流程,30 分钟内出结论】
我主张不应该靠「谁嗓门大」来归因,而应该靠一组可观测数据 + 明确阈值,把判定时间从「天」压到「30 分钟」。具体 5 步:
Step 1:同机房 vs 跨机房 P99 差值(1 分钟出结论)
- 对比同一条业务路径,同机房 RTT 的 P99 vs 跨机房 RTT 的 P99。
- 判据:跨机房 P99 / 同机房 P99 > 3 → 大概率网络路径问题(路由、NAT、交换机)。
- 反例:如果同机房也抖 → 应用层问题(GC、慢 SQL、连接池)。
- 这个指标不需要抓包,只需要在两侧各打一条
curl级别的探测(或用 eBPF 统计同方向 RTT 分布),1 分钟出结论。
Step 2:eBPF 重传计数器(2 分钟)
- 在客户端侧用
bpftrace或nstat看TcpRetransSegs(nstat -az里的TcpRetransSegs)。 - 判据:抖动窗口内
TcpRetransSegs增量 > 0 → 有重传发生 → 网络层嫌疑。 - 判据:增量 = 0 且 P99 依然抖 → 大概率应用层(GC、排队、CPU throttle)。
- 注意:0.3% 丢包率对应约 1/333 个包重传,800 conn/s 峰值下每分钟约 144 个重传事件,
nstat计数器一定能看到。
Step 3:应用侧 queue wait 时间(5 分钟)
- 从应用日志或 metrics 里拉
connection_pool.wait_time(HikariCP 的pending等待时间)或 gRPC 的queue_time。 - 判据:queue wait P99 > 50ms → 应用层排队问题(连接池、线程池、慢 SQL)。
- 判据:queue wait P99 < 5ms → 应用侧没有排队,问题在网络或下游。
Step 4:cgroup CPU throttle 检查(2 分钟)
cat /sys/fs/cgroup/cpu.stat,看nr_throttled和throttled_usec在抖动窗口内的增量。- 判据:
nr_throttled增量 > 100 且throttled_usec增量 > 100ms → CPU throttle 导致的 P99 抖动,属于 K8s 配置问题。 - 这个指标经常被忽略,但容器环境下是高频根因。
Step 5:NAT / 连接表项检查(10 分钟)
- 在中间设备上
conntrack -L | wc -l对比net.netfilter.nf_conntrack_max。 - 判据:表项使用率 > 80% → NAT 设备压力,会导致新连接建连慢、半开连接增多。
- 同时看
conntrack -L | grep ESTABLISHED | wc -l与SYN_SENT的比例,SYN_SENT / ESTABLISHED > 5%说明建连成功率在下降。
【为什么我坚持这套流程而不是「抓包看」】
抓包是最后手段,不是第一步。原因有三个:
- 抓包窗口和抖动窗口经常对不上——P99 抖动可能是 4 分钟的事,但抓包窗口通常是 30 秒,你抓到的可能是「正常时段」的包。
- 丢包率 0.1%~1% 在抓包里很难复现——0.3% 丢包在 10 万包窗口里只有 300 个重传,如果没有时间对齐,根本看不出异常。
- 抓包只能告诉你「网络层有没有问题」,不能告诉你「应用层有没有问题」——GC 停顿、连接池排队在抓包里完全看不到。
所以我的原则是:先用计数器(nstat、cgroup stat、conntrack)做 30 分钟的排除法,排除完再决定要不要抓包。 抓包是「定罪」,计数器是「排除嫌疑」。
【想听大家聊的】
- 你们团队的「P99 抖动归因」流程是什么? 有没有类似我这种「5 步判定」的标准化流程?还是每次都是「抓包 + 经验」?
- 你们用哪个指标做「网络 vs 应用」的第一道分界? 是 eBPF retrans、同机房/跨机房对比、还是别的?阈值怎么定的?
- cgroup CPU throttle 这个根因,你们遇到过吗? 容器环境下
cpu.max设太低导致 P99 抖动,这个坑你们踩过吗? - NAT 设备表项老化——你们线上有没有类似「连接表项满了导致建连变慢」的事故?怎么提前预警的?
- 你们有没有「网络团队 vs 应用团队」的明确责任边界文档? 比如「P99 抖动 > 50ms 且 retrans > 0 → 网络团队 30 分钟内响应」这种 SLA?
我特别想听应用团队的视角:当网络团队说「交换机计数没问题」的时候,你们怎么继续推进排查?有没有被「各看各的」卡过?