4、MTU与分片:MTU概念、UDP分片机制、避免分片的最佳实践

说到UDP性能优化,MTU和分片是个绕不开的话题。我见过太多开发者,代码写得挺漂亮,结果一上生产环境,丢包率飙升,排查半天才发现是分片惹的祸。说白了,MTU这玩意儿就像高速公路上的限高杆——你车再快,过不去就是过不去。

4.1 MTU到底是什么?

MTU,全称Maximum Transmission Unit,最大传输单元。它指的是网络接口层能够传输的最大数据包大小,单位是字节。

常见的MTU值:

网络类型 MTU值(字节) 备注
以太网 1500 最常用,互联网默认
PPPoE 1492 宽带拨号常见
VPN隧道 1400-1450 取决于封装开销
Jumbo Frame 9000 数据中心内部使用
环回接口 65536 本机通信,不经过物理网卡

这里有个关键点:MTU是二层(数据链路层)的概念。IP层在发包时,必须考虑下层接口的MTU限制。你想想看,IP包最大能到65535字节,但以太网口一次只能塞1500字节,怎么办?分片呗。

核心认知:MTU不是IP层的属性,而是网络接口的属性。每个网卡、每个隧道都有自己的MTU。路径上最小的MTU,决定了你能发送的最大不丢包数据量。

4.2 UDP分片机制——不得不懂的底层逻辑

UDP本身不关心分片。它把数据交给IP层,IP层发现包太大,就切成小片再发。接收端IP层负责重组,重组完了才交给UDP层。

分片流程大致是这样:

  1. UDP应用层发送一个7000字节的数据报
  2. 加上UDP头部(8字节),IP头部(20字节),总长7028字节
  3. 以太网MTU=1500,IP头部20字节,实际可用载荷1480字节
  4. IP层将数据切成5片:4片1480字节 + 1片628字节
  5. 每片独立发送,独立路由,可能走不同路径
  6. 接收端收到所有分片后,重组还原原始数据报

听起来挺智能的对吧?但问题就出在这里。

⚠️ 分片的致命缺陷:

  • 任何一片丢失,整个UDP数据报全部作废
  • 分片越多,丢包概率越大(概率论乘法原理)
  • 重组需要缓冲区,攻击者可以伪造分片耗尽内存
  • NAT设备处理分片经常出bug

我曾经在一个视频流项目里踩过这个坑。客户端上报的MTU是1500,但中间有个VPN隧道把MTU降到了1400。结果我们发的UDP包刚好卡在临界值,偶尔分片偶尔不分片,丢包毫无规律。查了三天,最后用tcpdump抓包才发现是分片重组超时导致的。

4.3 避免分片的最佳实践

嗯,这里要说的核心就一句话:UDP应用层应该主动控制数据报大小,避免IP层分片。

怎么做?我总结了四条铁律:

4.3.1 计算有效载荷上限

公式很简单:

有效载荷上限 = 路径MTU - IP头部(20) - UDP头部(8)

假设路径MTU=1500,那UDP数据部分最大就是1472字节。超过这个值,IP层就会分片。

但问题来了——你怎么知道路径MTU?

4.3.2 路径MTU发现(PMTUD)

PMTUD的原理:发送端设置IP头的DF(Don't Fragment)标志位,如果中间某个链路MTU更小,路由器会返回ICMP消息"需要分片但DF已设置"。

UDP下做PMTUD比较麻烦,因为ICMP消息可能被防火墙拦截。我个人习惯的做法是:

  • 先假设MTU=1500,发送探测包
  • 如果收到ICMP Fragmentation Needed,按反馈值调整
  • 如果没收到反馈,逐步降低MTU试探(二分法)
  • 最终取一个保守值,比如1400或1280

💡 实战技巧:很多云服务器默认禁用了ICMP。如果你发现PMTUD不工作,先检查iptables或安全组规则。我曾经在阿里云上折腾了一下午,最后发现是安全组把ICMP全给拦了。

4.3.3 应用层分片策略

既然IP层分片不靠谱,那就在应用层自己分片。UDP应用层分片的好处:

  • 可以加序列号,检测丢片
  • 可以加校验,防止数据损坏
  • 可以控制重传粒度,丢一片只重传一片
  • 不受中间设备分片重组bug影响

一个简单的应用层分片协议设计:

+----------------+----------------+----------------+----------------+
|  魔数(2字节)   |  会话ID(4字节) |  片序号(2字节) |  总片数(2字节) |
+----------------+----------------+----------------+----------------+
|  数据长度(2字节)|  校验和(2字节) |          数据载荷(可变)          |
+----------------+----------------+----------------+----------------+

每个分片独立发送,接收端根据会话ID和片序号重组。丢片了只重传那一片,而不是整个UDP数据报。

4.3.4 保守的MTU选择

如果你不确定路径MTU,选1280。为什么是1280?因为IPv6要求所有链路至少支持1280字节的MTU。选这个值,IPv4和IPv6都能兼容。

不同场景下的推荐UDP载荷大小:

场景 推荐载荷(字节) 理由
互联网公网 1400 兼容PPPoE和VPN
数据中心内网 8972 配合Jumbo Frame
移动网络 1200 4G/5G隧道开销大
卫星链路 512 高延迟,减少重传代价
不确定环境 1280 IPv6最小MTU保证

我的经验法则:UDP数据报总大小(IP头+UDP头+数据)不要超过路径MTU。如果做不到精确探测,就选1280。宁可少发几字节,也别让IP层帮你分片。

下面这张图总结了MTU与分片的核心逻辑:

MTU与UDP分片决策流程 UDP数据报发送 数据报大小 > 路径MTU? (IP头+UDP头+数据) 否 直接发送 不分片,性能最优 是 IP层分片 ⚠️ 分片风险 一片丢失,全部作废 NAT设备兼容性问题 最佳实践:应用层控制 主动避免IP层分片 PMTUD探测路径MTU 应用层分片+序列号 保守MTU=1280 目标:UDP数据报总大小 ≤ 路径MTU,零分片

💡 快速自查清单:

  • 你的UDP数据报总大小是否超过路径MTU?
  • 是否在应用层实现了分片和重组逻辑?
  • 是否设置了DF标志位来检测分片?
  • 是否考虑了中间设备(VPN、NAT)的MTU缩减?
  • 移动端用户是否使用了更保守的MTU值?

如果以上任何一个答案是"否",你的UDP通信就可能存在分片风险。

最后说一句:MTU和分片这事儿,看着简单,坑是真不少。我见过最离谱的一次,某厂商的硬件NAT设备对分片包的处理有bug,导致UDP视频通话每隔几分钟就卡死一次。最后解决方案不是修设备,而是我们把UDP包大小从1472降到了1400——问题瞬间消失。

所以,别跟MTU较劲。该让就让,该小就小。UDP性能优化的第一步,就是确保你的包不会被分片。