游戏卡顿白皮书 › TCP 重传的根本原因
MTU 黑洞(只有大包反复丢失) PMTU black hole
原因 ID rt-mtu · 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发
在含图示和实验的完整版中打开此卡片 →
中间链路能通过的包变小了,“包太大”的通知(ICMP)却被拦截,大包无论重发多少次都会丢失。
起因 VPN 或隧道段的最大包长变小,包过大通知被防火墙拦截 → 结果 发送方不知道原因,一直重传同一个大包,RTO 每次翻倍 → 画面表现 平时正常,一到打开背包、人多的地方、进场加载这类要传大数据的时刻,连后面跟着的小包也全部卡住,最终掉线或无限加载
- 症状
- 卡住, 掉线, 连不上/无限加载
- 因素
- 丢包
- 谁会遇到
- 特定地区/运营商, 只有我
- 何时出现
- 做特定操作时, 刚登录/维护结束后
- 负责方
- 主责 运维团队·网络运维 · 配合 运维团队·系统运维, 研发团队·服务器开发
- 研发团队要做的事
- 要在服务器侧直接调小,就设置 socket 的最大报文段长度(TCP_MAXSEG);只在游戏代码里把消息切小防不住(TCP 会把待发数据重新按 MSS 大小打包)。
- 运维团队要做的事
- 网络:在边界设备上做 MSS 调整(clamping);在防火墙和云网络 ACL 中放行包过大 ICMP(类型 3 代码 4,fragmentation needed)。服务器/OS:设置路径 MTU;确认服务器防火墙和云安全组也没有拦截包过大 ICMP;最后一道保险是 Linux tcp_mtu_probing=1。
- 数值参考
- 通常为 1,500 字节,经过隧道后约 1,400。同一个包重传 5~6 次,卡住的时间就会超过 10 秒。
- 监控图上
- 仅部分偏高 · 每个连接的 RTO、backoff,各地区、各运营商的掉线数
- 查看位置
- 用服务器侧抓包或 bcc tcpretrans -s(显示序列号)查看问题连接的重传,用 ss -ti 看该连接的 mss、pmtu、backoff。从服务器向该玩家地址分别发小 ping 和带 DF 标志的 1,500 字节 ping(ping -M do -s 1472),比较结果
- 确认依据
- 装满 MSS 的包以同一序列号反复重传,间隔每次翻倍,比它小的包能正常往来。收不到包过大 ICMP(Wireshark 过滤条件 icmp.type == 3 and icmp.code == 4);小 ping 有响应,只有带 DF 标志的大 ping 没有响应
- 排除依据
- 小包也一起丢失,就是与包大小无关的丢包(“瓶颈队列溢出”“路径变更/ECMP 故障路径”)。收到包过大 ICMP 且 ss -ti 的 pmtu 变小,说明路径 MTU 发现工作正常
- 确认手段
- 运维工具即可确认(无需游戏代码)
- 深入了解
- tcp_mtu_probing=1 要等重传超时持续几秒(相当于 tcp_retries1=3)之后,才判定为黑洞并把 MSS 降到 1,024 字节。这段时间画面是卡住的,所以它只能当最后一道保险,优先做能提前预防的 MSS 调整。
出处
- RFC 1191: Path MTU discovery IETF
路径 MTU 发现:包太大时用 ICMP“fragmentation needed and DF set”(类型 3 代码 4)通知 - RFC 2923: TCP Problems with Path MTU Discovery IETF
ICMP 被拦截,只有大包一直丢失的 PMTU 黑洞问题 - RFC 4821: Packetization Layer Path MTU Discovery IETF
不依赖 ICMP、由传输层探测包大小的方法(Linux tcp_mtu_probing 的基础) - IP Sysctl Linux kernel
tcp_mtu_probing:0 关闭,1 仅在检测到黑洞时开启,2 始终开启(初始 MSS 为 tcp_base_mss)。tcp_retries1 默认 3 - net/ipv4/tcp_timer.c Linux kernel
RTO 重传连续达到 tcp_retries1 次时,视为检测到黑洞,开启 MTU 探测并调低 MSS - include/net/tcp.h Linux kernel
TCP_BASE_MSS = 1,024 字节 - RFC 6298: Computing TCP's Retransmission Timer IETF
重传定时器每超时一次,RTO 就翻倍 - iptables-extensions(8) — Linux manual page netfilter
TCPMSS --clamp-mss-to-pmtu:通过调整 SYN 中的 MSS,绕过拦截 ICMP 的链路导致大包发不出去的问题 - tcp(7) — Linux manual page Linux man-pages
TCP_MAXSEG:发出数据包的最大报文段长度;在建立连接前设置,通告给对方的 MSS 也会随之改变 - Network maximum transmission unit (MTU) for your EC2 instance AWS
互联网网关和 VPN 的 MTU 为 1,500;PMTUD 需要 ICMP 类型 3 代码 4,被安全组或网络 ACL 拦截就收不到 - MTU considerations | Cloud VPN Google Cloud
Cloud VPN 网关 MTU 1,460 字节,IPv4 隧道的载荷 MTU 1,406 字节(经过隧道后约 1,400) - Demonstrations of tcpretrans, the Linux eBPF/bcc version IO Visor
每次重传输出一行,-s 会同时显示重传包的序列号 - ss(8) — Linux manual page iproute2
ss -i 的 mss、pmtu(路径 MTU)、backoff(RTO 翻倍的次数) - ping(8) — Linux manual page iputils
-M do 设置 DF 标志,不发送大于内核已知路径 MTU 的包;-s 为数据大小(另加 8 字节 ICMP 头) - Display Filter Reference: Internet Control Message Protocol Wireshark
icmp.type、icmp.code 显示过滤器
相关原因
同一层:TCP 重传的根本原因
其他层中同样导致“卡住”的原因
查看含图示和实验的原卡片