HOOOS

绕开TCP内卷 UDP上如何实现可靠传输 RUDP与UDT方案深度对比

0 39 老架构师阿宽 UDP可靠传输RUDPUDT网络协议
Apple

大家好,我是老架构师阿宽。咱们在做系统设计,特别是涉及到网络通信的时候,TCP 几乎是默认选项,毕竟可靠。但有时候,TCP 的一些“固执”特性,比如严格的顺序保证、队头阻塞,还有那相对固定的拥塞控制策略,在某些场景下反而成了性能瓶le颈,比如实时音视频、大规模数据分发、或者一些需要更精细化控制传输行为的场景。这时候,大家自然会想到在 UDP 这个“自由奔放”的协议上动心思,自己动手,丰衣足食,在应用层或者传输层之上实现一套可靠传输机制。

当然,现在有 QUIC 这个“当红炸子鸡”,它基于 UDP 实现了非常完善的传输机制,包括可靠性、多路复用、连接迁移等等,大有一统江湖的趋势。不过,QUIC 相对复杂,而且有时候我们可能只需要 UDP 的低延迟特性,再加上一部分可定制的可靠性保障,并不需要 QUIC 的全家桶。这时候,一些更轻量级或者有特定优化的基于 UDP 的可靠传输方案就进入了我们的视野,比如 RUDP (Reliable UDP) 和 UDT (UDP-based Data Transfer Protocol)。

今天,咱们就来深入聊聊 RUDP 和 UDT 这两种在 UDP 上实现可靠传输的思路,对比一下它们的设计哲学、核心机制、优缺点以及适合什么样的场景。希望能帮助大家在需要“折腾”UDP 的时候,做出更合适的选择。

为什么要在 UDP 上做文章?

在深入对比之前,我们先简单回顾下为什么放着好好的 TCP 不用,非要在 UDP 上“造轮子”。

  1. 避免队头阻塞 (Head-of-Line Blocking) TCP 是严格有序的,一个包丢了,后面的包就算到了也得等着,直到丢失的包重传成功。这在需要低延迟的应用(比如实时通信)或者多路复用场景(像 HTTP/2 在 TCP 上遇到的问题)下是致命的。UDP 本身无序,我们可以在其上实现选择性确认和重传,避免一个流的阻塞影响其他逻辑上独立的流。
  2. 更灵活的拥塞控制 TCP 的拥塞控制算法(如 Reno, Cubic, BBR)是内核实现的,应用层很难干预。有时候,我们根据应用的特性(比如要求稳定延迟而非最大吞吐量)或者网络环境(如高丢包的卫星链路),希望采用更激进或更保守,甚至完全自定义的拥塞控制策略。基于 UDP 实现,拥塞控制完全掌握在自己手里。
  3. 连接建立开销 TCP 的三次握手虽然可靠,但也带来了至少 1 个 RTT (Round-Trip Time) 的延迟。对于短连接或者需要快速启动的应用,这部分开销可能比较明显。基于 UDP 的方案可以设计更快的连接建立机制,甚至实现 0-RTT 连接(当然,这需要额外的安全和状态管理机制)。
  4. 内核态与用户态 TCP 的处理主要在操作系统内核态,虽然高效,但也意味着修改和优化需要动内核,门槛高,部署难。在用户态基于 UDP 实现传输控制,迭代更快,部署更灵活,更容易针对特定硬件(如 RDMA)进行优化。

当然,代价就是在 UDP 上实现可靠性,你需要自己处理确认 (ACK)、重传 (Retransmission)、流量控制 (Flow Control)、拥塞控制 (Congestion Control)、顺序保证 (Ordering) 等等 TCP 帮你做好的事情。这无疑增加了复杂性。

可靠 UDP 的核心技术点

无论是 RUDP、UDT 还是 QUIC,要在 UDP 上实现可靠性,都离不开以下几个核心技术:

  1. 序列号 (Sequence Number) 这是实现顺序保证和检测丢包的基础。每个发出的数据包都需要有一个唯一的、单调递增的序列号。
  2. 确认机制 (Acknowledgement, ACK) 接收方需要告知发送方哪些数据包已经收到。常见的有:
    • 累计确认 (Cumulative ACK) 类似 TCP,确认一个最大连续收到的序列号。简单,但丢包时信息量少。
    • 选择性确认 (Selective ACK, SACK) 明确告知哪些不连续的数据块收到了。丢包恢复效率更高,但 ACK 包可能更大。
    • NACK (Negative ACK) 只告知哪些包没收到。在丢包率较低时效率高,但可能需要配合心跳或超时来发现长时间的静默丢失。
  3. 重传机制 (Retransmission) 发送方在超时或者收到 NACK/SACK 指示丢包后,需要重新发送丢失的数据包。
    • 超时重传 (Timeout Retransmission) 基于 RTT 估算设置一个超时定时器,超时未收到 ACK 则重传。简单,但超时设置很关键,设长了恢复慢,设短了可能导致伪重传。
    • 快速重传 (Fast Retransmission) 类似 TCP,收到多个冗余的 ACK (表明后续包已到达,但某个包丢失) 时,不等超时就重传。通常配合 SACK 或 NACK 使用效率更高。
  4. 流量控制 (Flow Control) 防止发送方发送速度过快,导致接收方缓冲区溢出。通常基于接收方通告的接收窗口大小 (Receive Window) 来实现。
  5. 拥塞控制 (Congestion Control) 防止发送方发送速度过快,导致网络中间节点拥塞丢包。这是最复杂的部分,需要探测网络带宽、感知拥塞信号(丢包、延迟增加),并动态调整发送速率。各种算法层出不穷,如基于丢包的 (AIMD - Additive Increase Multiplicative Decrease)、基于延迟的 (Vegas, BBR)、混合型的等。
  6. 连接管理 (Connection Management) 虽然 UDP 是无连接的,但可靠传输需要维护连接状态,包括连接建立、保活、关闭等过程。

了解了这些基础构件,我们再来看 RUDP 和 UDT 是如何组合和运用这些技术的。

RUDP (Reliable UDP) 家族

RUDP 其实不是一个单一的标准协议,更像是一类协议的统称,有很多不同的实现,各有侧重。这里我们主要讨论一些共性的设计思路和常见的实现方式。

设计哲学:

通常 RUDP 的目标是在 UDP 的基础上,以相对简单的方式增加可靠性,但不一定追求极致的性能或复杂的特性。可以看作是“给 UDP 打补丁”,哪里不可靠补哪里。

核心机制特点:

  1. 确认与重传:
    • ACK 策略: 很多 RUDP 实现采用 SACK 或者 NACK 机制,因为这对于快速恢复丢包更有效。有些简单的实现可能只用累计 ACK。
    • 重传触发: 通常结合超时重传和基于 NACK/SACK 的快速重传。
    • RTT 估算: 类似 TCP,需要动态估算 RTT 来设置合适的超时时间 (RTO - Retransmission Timeout)。
  2. 顺序保证: 大部分 RUDP 实现会提供顺序保证,接收方会缓存乱序到达的数据包,并按序交付给应用层。但也有些轻量级实现允许应用层处理乱序数据,以换取更低的延迟。
  3. 流量控制: 通常会实现基于接收窗口的流量控制。
  4. 拥塞控制: 这是 RUDP 实现差异较大的地方。简单的 RUDP 可能没有拥塞控制,或者只有非常基础的速率限制。一些更完善的实现会引入类似 TCP 的 AIMD 机制,或者更简单的基于丢包率的速率调整策略。但相比 UDT 或 QUIC,RUDP 的拥塞控制通常不那么复杂和激进。
  5. 连接管理: 可能有简单的握手过程来交换初始序列号和参数,也可能依赖带外信息或应用层协议来管理连接。

常见实现与变种:

  • Reliable UDP (RUDP) by Plan 9: Bell Labs 的 Plan 9 操作系统中有一个 RUDP 实现,相对简单。
  • Enet: 一个流行的开源网络库,为游戏设计,提供了基于 UDP 的可靠有序/无序通道,带有简单的拥塞控制。
  • RakNet: 另一个主要面向游戏的网络引擎,功能更丰富,也提供了可靠 UDP 功能。
  • 自定义 RUDP: 很多公司或项目会根据自己的特定需求(比如特定网络环境、特定应用负载)实现私有的 RUDP 协议。

优点:

  • 相对简单: 相比 UDT 或 QUIC,很多 RUDP 实现的核心逻辑更简单,更容易理解和实现。
  • 灵活性: 可以根据需要选择性地实现可靠性特性(比如只保证可靠,不保证有序),定制化程度高。
  • 开销较低: 如果只实现基础的可靠性,协议开销(头部、状态维护)可能比 TCP 或更复杂的协议要小。

缺点:

  • 缺乏标准化: 没有统一的 RUDP 标准,不同实现之间无法互通。
  • 性能可能受限: 简单的 RUDP 实现可能在拥塞控制、丢包恢复等方面不如专门优化的协议高效,特别是在高带宽、高延迟、高丢包的网络环境下。
  • 成熟度不一: 不同实现的成熟度、稳定性和社区支持差异很大。

适用场景:

  • 内部系统或特定应用: 对协议有完全控制权,可以确保两端使用相同实现。
  • 对延迟敏感,但能容忍一定不可靠性或乱序的应用: 可以选择性实现部分可靠性,如游戏的状态同步(部分数据可靠有序,部分数据不可靠尽力而为)。
  • 网络环境相对较好,或对吞吐量要求不极致的场景: 简单的 RUDP 可能就够用了。
  • 需要快速原型验证或轻量级可靠传输的场景。

UDT (UDP-based Data Transfer Protocol)

UDT 则是一个更具体、设计目标更明确的协议,它的核心目标是解决 高速、长距离网络 (High Bandwidth Delay Product, BDP networks) 下 TCP 传输效率低下的问题。

设计哲学:

UDT 认为在高 BDP 网络中,TCP 基于丢包的拥塞控制过于保守,恢复速度慢,无法充分利用网络带宽。因此,UDT 设计了一套更激进、更适合高速数据传输的拥塞控制机制,并结合高效的 ACK 和重传策略。

核心机制特点:

  1. 确认与重传:
    • ACK 策略: UDT 使用 周期性的 NACK 报告。接收方不主动发送 ACK,而是周期性地(比如每 10ms)检测接收缓冲区,如果发现有序列号空洞(丢包),就发送一个 NACK 包,明确告知哪些序列号丢失了。同时,接收方也会定期发送轻量级的 ACK 包,用于 RTT 测量和连接保活,这个 ACK 包只确认收到的最大连续序列号。
    • ACK of ACK (ACK2): 为了确认 ACK/NACK 包本身没有丢失,UDT 还引入了 ACK2 机制。发送方收到 ACK/NACK 后,会回复一个 ACK2 包,告知接收方它已经收到了某个 ACK/NACK。
    • 重传触发: 主要由 NACK 触发。发送方收到 NACK 后,立即重传丢失的数据包。超时重传作为辅助,用于处理 NACK 本身丢失或长时间无数据传输的情况。
  2. 顺序保证: UDT 保证数据按序交付给应用层。
  3. 流量控制: UDT 实现了基于速率的流量控制和基于窗口的流量控制。接收方会通告可用缓冲区大小(窗口),发送方也会根据估算的链路容量和拥塞状况限制发送速率。
  4. 拥塞控制: 这是 UDT 的核心亮点。它采用 基于速率的拥塞控制,并且是 混合型 的,结合了 基于丢包基于延迟 的信号。
    • 速率控制: UDT 直接控制发送速率 (packets per second) 和数据包发送间隔 (inter-packet interval)。
    • 拥塞探测: UDT 周期性地发送一对探测包 (probe packet pair) 来估算链路带宽。同时,通过 ACK/NACK 的反馈时间和 RTT 变化来感知延迟变化。
    • 算法: UDT 的拥塞控制算法大致可以描述为:在没有拥塞信号(低丢包、低延迟)时,快速增加发送速率(指数增长或更快的增长);一旦检测到拥塞(收到 NACK 或延迟增加),则根据拥塞程度(丢包率、延迟增量)进行乘性减小或加性减小。其目标是在高 BDP 网络中更快地达到并维持在高带宽利用率。
  5. 连接管理: UDT 有自己的握手协议,用于建立连接、交换参数和 Cookie(防止 IP 欺骗)。

优点:

  • 高 BDP 网络性能优越: UDT 的核心优势在于其专门为高带宽、高延迟网络设计的拥塞控制算法,能够比传统 TCP 更快、更充分地利用网络带宽,特别适合大规模数据传输。
  • 穿透防火墙: UDT 使用 UDP 传输,更容易穿透只允许 UDP 通行的防火墙或 NAT 设备。
  • 标准化与实现: UDT 有相对明确的协议规范 (虽然不是 IETF 标准),并且有成熟的开源实现 (如 SourceForge 上的 UDT V4/V5 库),被广泛应用和验证过。

缺点:

  • 公平性问题: UDT 的拥塞控制算法相对激进,在与 TCP 流量共享链路时,可能会抢占过多带宽,对 TCP 不公平。这在公共互联网上部署时需要谨慎考虑。
  • 复杂性: UDT 的协议设计和实现比简单的 RUDP 要复杂得多,特别是其拥塞控制部分。
  • CPU 消耗: UDT 的处理逻辑(速率控制、定时器管理、ACK/NACK 处理等)通常在用户态完成,相比内核态的 TCP,可能会消耗更多的 CPU 资源,尤其是在极高 PPS (Packets Per Second) 的情况下。

适用场景:

  • 大规模数据传输: 如科学计算、网格计算、大型文件分发、数据备份等,特别是在跨地域、高延迟的网络链路上。
  • 需要突破 TCP 性能瓶颈的场景: 当 TCP 的吞吐量无法满足需求时,UDT 是一个值得考虑的替代方案。
  • 内部网络或专用网络: 在可以控制网络环境,或者对公平性要求不高的私有网络中,可以更好地发挥 UDT 的性能优势。
  • 对防火墙穿透有要求的场景。

RUDP vs UDT 对比总结

特性 RUDP (通用概念) UDT (具体协议) 思考点
设计目标 通用可靠性增强,灵活性高 高 BDP 网络下的高性能数据传输 你的首要目标是简单可靠,还是极致吞吐量?
标准化 无统一标准,实现各异 有明确规范和成熟开源实现 是否需要互操作性?是否愿意依赖特定实现?
核心机制 ACK/SACK/NACK, 超时/快速重传 周期性 NACK, ACK2, 速率控制, 探测包 UDT 机制更复杂,但针对性优化。RUDP 更易定制。
拥塞控制 简单/无,或类似 TCP 的 AIMD 激进的、基于速率的混合型拥塞控制 网络环境如何?高 BDP?是否需要与 TCP 公平共存?
性能 取决于具体实现,通常不如 UDT 激进 在高 BDP 网络下通常优于 TCP 和简单 RUDP 性能瓶颈在哪里?是带宽、延迟还是 CPU?
公平性 通常较好 (如果实现类似 TCP 的控制) 可能对 TCP 不公平 是否在公共互联网部署?
复杂性 相对较低 相对较高 开发和维护成本?
适用场景 内部系统, 游戏, 轻量级可靠传输 大文件传输, 科学计算, 高速远程数据同步 应用类型和网络条件是关键决定因素。

如何选择?

选择 RUDP 还是 UDT,或者干脆拥抱 QUIC,甚至坚持使用 TCP,并没有绝对的答案,关键在于 权衡 (Trade-off)

  1. 先问自己:真的需要 UDP 吗? TCP 经过了几十年的发展和优化,在绝大多数场景下表现良好且稳定。BBR 等新拥塞控制算法也在不断提升 TCP 在高 BDP 网络下的性能。除非你明确遇到了 TCP 的性能瓶颈(如队头阻塞、拥塞控制不适应)或者有特殊的定制需求(如自定义拥塞策略、0-RTT),否则坚持使用 TCP 通常是更省心、更安全的选择。

  2. 如果确定要用 UDP,QUIC 是不是更好的选择? QUIC (通常指 IETF QUIC,也就是 HTTP/3 的基础) 吸收了 TCP、UDP、TLS 等协议的优点,提供了连接迁移、多路复用无队头阻塞、加密、更快的连接建立、可插拔的拥塞控制等一系列现代化特性。对于需要构建复杂、高性能网络应用(尤其是 Web 相关)的场景,QUIC 是一个非常有力的竞争者。它的复杂性虽然高,但有 Google 等大厂背书和持续投入,生态也在快速发展。

  3. 在 RUDP 和 UDT 之间选择:

    • 如果你的目标是 在特定场景下(比如内部网络、游戏)对 UDP 做一些有限的可靠性增强,希望 保持简单、灵活、易于定制,并且网络条件相对可控,或者对极致吞吐量要求不高,那么选择一个合适的 RUDP 实现(或者自己实现一个简化的版本)可能更合适。
    • 如果你的核心痛点是 在高速、长距离网络下传输大量数据,TCP 表现不佳,你需要 榨干带宽,并且可以接受其相对激进的拥塞控制策略(或者在私有网络部署),那么 UDT 是一个经过验证的、专门为此设计的优秀方案。

我的思考过程:

当我面临类似选择时,通常会这样思考:

  • 问题定义: 当前遇到的核心网络问题是什么?是延迟、吞吐量、连接建立速度,还是队头阻塞?问题发生的具体网络环境是怎样的?公网还是内网?带宽、延迟、丢包率大概是多少?
  • 目标设定: 我希望通过新的传输方案达到什么目标?提升多少吞吐量?降低多少延迟?需要哪些可靠性保证(完全可靠有序?部分可靠?允许乱序?)?
  • 方案评估:
    • TCP + 最新优化(如 BBR)能否解决问题?成本最低。
    • QUIC 是否适用?特性是否满足需求?是否有成熟的库支持我的开发语言和平台?社区活跃度如何?
    • UDT 是否符合场景?高 BDP 是主要矛盾吗?公平性问题是否可以接受或规避?
    • RUDP 类的方案是否足够?需要自己实现还是有现成的库?维护成本如何?
  • 原型验证与测试: 在真实或模拟的网络环境下,对选定的方案进行性能测试和对比,验证其是否能达到预期目标。

记住,没有银弹。每种技术都有其闪光点和局限性。作为架构师或开发者,我们需要理解这些技术的内在机制和设计取舍,才能在具体的场景下做出明智的选择。

希望这次关于 RUDP 和 UDT 的对比分析能给大家带来一些启发。在 UDP 这片充满可能性的土地上,愿大家都能找到最适合自己业务的那条“可靠”之路。

点评评价

captcha
健康