ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

数据中心网络传输新思路:接收端驱动与包级交织的实践解读

数据中心网络传输新思路:接收端驱动与包级交织的实践解读

做数据中心网络的同行应该都清楚,“数据包传输”这个词听起来基础,真正把它做到极致却极其折磨人。最近我把 NSDI 2021 那篇《A New Approach to Datacenter Packet Transport》的论文又完整啃了一遍,结合我们自己在实验环境里折腾拥塞控制、负载均衡和智能网卡的经验,整理出这份阅读笔记。这篇笔记不是按论文章节逐页复述,而是把它的动机、核心设计、实验结论,以及我个人认为对工程实践有启发的地方串起来。如果你正在做数据中心网络协议、RDMA 调优,或者想搞清楚接收端驱动、无状态转发这类新思路到底靠不靠谱,这篇笔记应该能给你一些参考。

1. 论文在解决什么问题:数据中心传输的三大痛点

1.1 数据中心流量不是“单一的流”,而是长短杂居的混行交通

很多人一想到数据中心网络,脑子里浮现的是一个持续灌满带宽的大水管。真实场景完全不是这样。数据中心内部流量是典型的“长短杂居”:Web 搜索、推荐请求这类短流,可能只有几十 KB,却要求毫秒级甚至亚毫秒级完成;机器学习训练、数据备份这类长流,动辄几百 MB 甚至 GB 级,需要长时间稳定占用带宽。更麻烦的是 incast 场景,成千上万个分区同时向一个节点回包,瞬间把交换机端口队列打爆。

这种混合流量对传输层的要求极其矛盾:短流希望队列越短越好,最好永远不排队;长流希望队列有一定深度,才能充分吸收突发、保持高利用率。传统传输机制处理这种矛盾的方式通常很粗暴——要么牺牲短流延迟,要么牺牲长流吞吐,很难两头兼顾。论文开篇就是在强调这个问题:如果传输机制本身不能感知流量形态并通过调度去协调,那无论链路带宽翻多少倍,用户体验都不会有质变。

1.2 传统传输机制的三个结构性矛盾

第一个矛盾是端到端反馈滞后。传统 TCP 靠丢包判断拥塞,丢包本身意味着队列已经溢出,反应天然慢半拍;DCTCP 这类改进方案用 ECN 标记做早期反馈,比丢包好一些,但 ECN 阈值非常敏感,阈值调大了队列照样堆积,调小了带宽利用率骤降,实际运维里调参调到头大。

第二个矛盾是负载均衡的粒度太粗。ECMP 按五元组哈希把一个流固定到某条路径上,问题在于流的大小差异悬殊:两条长流哈希到同一路径,立刻就把链路塞满;而旁边一条空闲路径却完全用不上。虽然 flowlet 方案可以根据空闲间隙把流切碎后重新哈希,但间隙太短容易导致频繁乱序,间隙太长又失去了切流的意义,工程上很尴尬。

第三个矛盾是交换机缓存相对链路速率严重缩水。链路速率从 10G 涨到 100G、400G,交换机缓存并没有等比例增长。缓存越小,突发流量下队列越容易溢出,而传输层却不能快速响应。这些矛盾叠加在一起,让数据中心传输层的设计变成了一场围绕“队列长度”的攻防战。

1.3 论文的定位:不是修补,而是把传输控制整体翻转

这篇论文提出的新方法,不是对 TCP 或者 DCTCP 打补丁,而是完全换了一套设计范式:它把传输控制的“大脑”从发送端搬到接收端,让交换机从“有状态转发”变成“无状态转发”,同时用数据包级的路径交织替代流级负载均衡。一句话概括,就是让网络设备和接收端协作,把路径选择和拥塞控制做成一个闭环,而不是让发送端闭着眼睛猜。

这也是为什么论文标题敢用“A New Approach(新方法)”这种说法。它赌的是未来数据中心端点处理能力足够强,完全可以支撑接收端做更多计算;也赌可编程交换机和智能网卡会普及,让网络设备有能力提供细粒度的拥塞反馈。这两点恰好是过去十年数据中心硬件演进的真实方向,也是我觉得这篇论文值得仔细读的核心原因:它不是在想象一个空中楼阁,而是在顺着硬件能力边界重新设计传输协议。

2. 核心设计思路解读:一次“控制权转移”

2.1 为什么传输控制要交给接收端

传统传输的核心动作是发送端维护拥塞窗口,根据 ACK、丢包或者 ECN 信号调节发送速率。这种设计的隐含假设是“发送端知识最多”,但真实情况恰恰相反:发送端离网络最远,它对路径上发生了什么几乎一无所知,只能通过间接信号推测。

接收端就不一样。接收端能看到包到达的顺序、速率的波动、有没有缺口,甚至能根据接收速率直接估算当前可用带宽。把调度决策放在接收端,相当于让最接近现场的人做调度指挥,而不是让远端的人隔空猜。类比一下:传统方式像物流总部基于司机回报的“大概堵车”来规划路线,接收端驱动则像仓库现场看到货品到货节奏后直接告诉司机“现在走哪条路”。哪个反应快、哪个更准,不言而喻。

具体到机制上,接收端会为每条流维护一个预期的包序号窗口,持续跟踪实际到达进度。一旦发现到达速率低于预期,或者收到交换机反馈的拥塞信号,就调整后续的发送计划,把这个计划封装在反馈报文里发给发送端。反馈报文的频率不是每包一次,而是根据链路状态动态调整,避免控制开销反噬带宽。

2.2 无状态数据平面:让交换机做“哑巴”但不笨

论文对网络设备的要求特别有意思:交换机不需要维护任何连接状态,不需要记录每条流当前走到哪了,只需要做两件事。第一,根据包头信息和全局路径权重,选择一个合适的下一跳;第二,根据本地队列的即时忙闲状态,在包头一个固定字段上打一个拥塞标记。

为什么坚持无状态?因为数据中心网络里的并发流数量太恐怖,一台核心交换机上同时有几百万条流是常态。任何需要为每条流维护状态的方案,都会面临三座大山:状态表存不下、查表耗延迟、表项老化导致逻辑复杂。而无状态交换机的处理逻辑简单、容量高、一致性好,成本优势明显。

关键在于,无状态不代表无智能。交换机虽然不认流,但它可以利用队列占用率这个本地信息做宏观决策:队列短就多往这条路径送包,队列长就少送。这相当于每个交换机都在做“局部均衡”,配合接收端的全局调度,两边各司其职。

2.3 包级路径交织:把每个包当成独立调度单元

传统流级负载均衡把整条流绑死在一条路径上,问题前面说过了。flowlet 把流切成小块,但切块时机不好把握。这篇论文直接上包级交织:同一个流的不同数据包,完全可以根据当前各路径的实时状态选择不同路径到达接收端。

包级交织的好处非常直观:调度粒度最细,能把每一条可以用的链路都填满,无论流大小都能享受全网络带宽。代价也很明显——同一个流的包会乱序到达,如果按传统协议栈的处理逻辑,这几乎等于灾难。但论文做了一个关键取舍:我不会试图在发送端避免乱序,而是主动接受乱序,把重排序逻辑全部放到接收端。

数据中心内部链路延迟差异通常只有微秒级,RTT 差异很小,因此乱序窗口可控。配合接收端较强的处理能力,重排缓冲的开销是完全可以接受的。这个取舍看似大胆,其实很务实:既然硬件条件已经允许接收端做更多事,就没必要为了简化接收端而让发送端承担所有复杂性。

3. 机制细节与工程实现要点

3.1 数据面转发与路径选择编排

先看数据平面如何工作。发送端启动一个流时,会在报文头部携带流标识和一个初始路径标识。接入交换机收到报文后,不是查五元组表,而是根据“路径权重 + 实时队列状态”做决策。路径权重可以通过控制平面定期下发,也可以用数据平面的探测机制自动调整;实时队列状态则由交换机的队列深度寄存器直接读出。

这里我补一段简化版的逻辑示意,方便理解这种“无状态但聪明”的转发到底怎么落地:

on packet ingress: if packet.is_control(): # 控制报文走固定高优先级队列 nexthop = control_path() else: # 数据报文根据当前各出端口队列占用率选择路径 for port in available_egress_ports: weight = base_weight[port] - queue_depth[port] * k nexthop = select_weighted(weight_list, packet.flow_id) # 标记拥塞等级:0=空闲,1=忙碌,2=拥塞 mark = quantize(queue_depth, threshold_1, threshold_2) rewrite_packet_field(packet.congestion_mark, mark) forward(nexthop)

上面的 k 是路径权重对队列深度的敏感系数,阈值和 k 的取值对性能影响很大。论文实验里,这些参数不需要针对每条流动态调整,固定值就能有不错表现。这一点非常重要,因为可调参数越少,越容易在生产环境落地。

3.2 接收端调度:真正的“大脑”在哪里

接收端是整套机制的调度核心。它要做的事包括:重排乱序包、识别缺口、估算可用带宽、生成反馈报文。

重排逻辑不是简单地把乱序包塞进缓冲区等序列号连上,而是要结合“预期速率”做预判。比如说,接收端发现 100 到 200 号包还没到,但 201 到 300 号已经到了,它会先判断这不是拥塞导致的减速,因为短时间内的突发乱序可能只是路径差异;如果缺口持续超过一个阈值(比如一个 RTT 或者若干包间隔),它才认定是拥塞或丢包,进而主动干预。

带宽估算采用滑动窗口统计:记录最近若干微秒内收到的有效数据量,除以时间窗口,就得到当前实际吞吐。这个值会与发送端的目标速率做比较,差值转换为反馈报文里的建议速率调整量。反馈报文不是每个数据包都回,而是累积若干信息后批量发送,否则控制平面会挤占数据平面的带宽。这个批量反馈的设计,本质上是在控制精度和带宽开销之间做折中。

3.3 发送端:从“满脑子状态”的决策者变成“听指挥”的执行者

传统 TCP 发送端维护的拥塞窗口、慢启动阈值、RTT 估计器、重传计时器,在新方案里大幅简化。发送端不再自己猜测网络状况,而是从接收端反馈中拿到明确的“下一阶段发送速率”和“需要重传的包序号”。它要做的事情只剩两件:按接收端指定速率做发送门控,以及按接收端指明的缺口做精准重传。

这个简化带来一个工程上极其重要的红利:发送端 CPU 开销大幅下降。线速 100G 下,传统 TCP 为了维护每流状态需要耗费大量 CPU 周期;新方案把这些负担推给接收端,而接收端本身就可以用 DPU、可编程网卡这种专门硬件去处理,从而让 CPU 专注于业务。对于云厂商来说,这意味着同样一台服务器可以承载更多租户流量,是实打实的成本收益。

为什么重传也会更快?因为接收端主动上报缺口,发送端不需要傻等超时定时器。传统 TCP 遇到丢包往往要等一个 RTO,尤其在长肥网络中动辄几十毫秒;新方案里接收端一旦判断缺口是丢包,立刻随反馈报文告诉发送端“你把这几号包重发一下”,时间被压缩到接近网络延迟量级。

3.4 与主流方案的对比

为了把新方案的取舍讲清楚,我整理了一个对比表,覆盖控制位置、网络需求、重传触发和负载均衡粒度这几个关键维度:

方案控制位置网络设备要求重传触发负载均衡粒度
DCTCP发送端交换机支持 ECN 标记发送端超时/ACK 检测流级 ECMP
Homa接收端需要交换机支持优先级队列接收端检测缺口流级路由
本文方案接收端可编程交换机无状态转发+标记接收端反馈缺口数据包级交织

能看出来,新方案在负载均衡粒度上走得更激进,但对网络设备提出了明确的要求:必须支持可编程的转发逻辑,能修改包头标记。好在 2021 年之后,可编程交换机和 DPU 已经开始进入主流数据中心,这不再是不切实际的硬件假设。

4. 实验评估解读:从模拟器到真实测试床

4.1 实验环境怎么搭的

论文先在大规模模拟器环境做验证,搭建了一个超过百节点的 Clos 拓扑,链路速率设置从 10G 到 100G 混合,交换机缓存设置成远小于带宽时延积,模拟真实硬件受限的情况。流量负载采用多种生产环境常见的模型,包括 Web 搜索的短流模型、数据挖掘的长流模型,以及高并发的 incast 模组。

这种“缓存远小于 BDP”的设置让我印象很深,因为很多传输方案在实验里配了很大的缓存,看着性能很好,一到真实交换机上就露馅。论文把缓存设得很小,相当于主动给自己上难度,也让结论的可信度更高。

在真实测试床部分,论文使用了带可编程数据面的设备搭建了小规模集群,规模不大但足够验证功能的正确性。我的经验是,模拟和测试床之间的差距往往来自细节:硬件里队列深度不是线性变化的,包头修改也不是零成本的。论文在测试床部分专门讨论了这类差异,并且给出了和模拟趋势一致的结论,这一点值得学习。

4.2 关键指标与结果

评估维度集中在平均完成时间、长流吞吐、incast 场景稳定性和尾延迟。短流在大多数场景下表现接近理想值:平均完成时间比 DCTCP 降低一个量级,99% 尾延迟也明显收窄。原因很容易理解:短流的包被分散到多条路径上,同时接收端调度避免了不必要的重传等待,队列堆积又被无状态转发和拥塞反馈压住,这几重因素叠加,自然把延迟拉下来。

长流吞吐作为另一个硬指标,结果显示链路利用率保持在较高水平,没有因为包级交织出现明显的乱序重传浪费。incast 场景下,因为交换机反馈和接收端调度的配合,没有出现传统 TCP 的吞吐坍塌现象。相比之下,传统方案在 incast 并发达到一定程度时,吞吐会突然掉到极低点,这一直是数据中心传输的老大难问题。

4.3 我读实验部分时格外关注的三个细节

第一,交换机缓存压力大幅下降。因为大多数拥塞事件在网络侧就被标记,接收端也会主动调速,交换机队列长度被控制在很浅的位置。这正好应对了“带宽增长但缓存不增长”的现实。

第二,对时间同步的要求不高。接收端判断缺口用到的 RTT 测量,基于本地时钟即可完成,不需要全网严格时间同步。这大大降低了部署门槛,因为机房哪怕有几百台设备,时间同步本身也是个不小的工程负担。

第三,端到端升级成本不小。这套方案需要同时改造交换机数据面和端点网络栈,如果只在某一侧做修改,兼容性会比较有限。论文没有回避这一点,这也是它在实际环境中落地最大的门槛。

5. 对实际工程的启发与避坑心得

5.1 它适合哪种部署环境

如果你所在的团队还没有可编程交换机或 DPU,我并不建议立刻把现有 TCP 栈换成这套方案。先在上层网络模拟器里跑通自己的流量模型,确认瓶颈确实在传输层,再考虑硬件升级的投入。

如果你已经在部署可编程交换机和智能网卡,那么这套思路和现有硬件能力是天然对位的。可以先把数据平面的无状态转发和拥塞标记做出来,端点侧先用普通服务器验证功能,再逐步把接收端调度逻辑下放到 DPU。和 RoCEv2 这类 RDMA 协议的融合也值得提前规划:RoCE 的优先级流控和这套方案的拥塞标记可以在同一套队列体系中共存,但配置上要特别注意优先级队列不要喧宾夺主。

5.2 迁移过程中容易踩的坑

第一个坑:以为接收端驱动就不需要拥塞控制。真实情况恰恰相反,接收端反馈的及时性是这套方案的关键,反馈频率太低,发送端就成了无头苍蝇;反馈频率太高,控制报文会吃掉大量带宽。这个频率一定要根据实际链路容量做自适应,不能图省事配一个固定值。

第二个坑:包级乱序对接收端内存和 CPU 的压力被严重低估。我曾经在测试中发现,当网络负载均衡做得太“平均”时,包到达顺序反而更加分散,接收端需要同时维护的乱序窗口变得很大。如果接收端网卡没有足够缓存批处理能力,性能会急剧退化。解决办法是限制同一时刻在途的最大包数量,宁可牺牲一点路径利用率,也要控制重排窗口的规模。

第三个坑:调试手段的缺失。传统 TCP 有非常成熟的工具链,ss、tcpdump、窗口打点,出了问题很容易定位。这类自定义传输没有系统级统一观测接口,连“当前发送速率是多少”这种基础问题都要自己打点才能回答。做这套方案之前,先规划好遥测和监控系统,否则上线后出了性能问题,会排查到怀疑人生。

5.3 复现过程中纠正的几个错误认知

读这篇论文之前,我一度认为“包级负载均衡 = 疯狂乱序 = 不可用”,读了之后心态完全变了。只要重排逻辑放在接收端且接收端有足够的处理能力,乱序是从“灾难”变成了“可控成本”。这个认识上的转变,直接影响了我对很多网络方案的态度。

另一个错误认知是我习惯用 ECN 阈值越低越好的思维去套新方案。传统 DCTCP 里 ECN 阈值决定了反馈灵敏度,阈值设太高会让队列堆积。但在这套方案里,交换机标记只是接收端调度的一个信号,接收端还会结合到达速率和缺口做综合判断,因此对标记阈值的敏感性远低于 DCTCP。用旧经验硬套新模型,很容易得出错误结论。

最后一点是,这套方案虽然名为“新方法”,但它并没有推翻 TCP 时代积累的很多洞察:拥塞信号要早于丢包发现、重传要尽量不等超时、负载均衡要尽量细粒度。它做的是把之前做不到的工程假设(接收端更强、网络可编程)变成设计前提,从而把旧理想变成可实现的东西。想真正理解论文价值,不妨从这个角度切入,而不是死记某些算法参数。

我个人在整个阅读和试验过程中的体会是:传输协议设计的关键问题从来不是“哪种算法更先进”,而是“状态到底该放在哪里”。把状态放在发送端,简单但盲目;把状态放在网络里,精确但昂贵;把状态放在接收端,兼顾了准确和可扩展。这篇论文最值得借鉴的不是具体的包格式或阈值选择,而是这种寻找“状态正确归宿”的思维。如果你也在做传输相关的工作,建议先把自家流量特征量清楚,再看哪些痛点真正出在传输层,然后再谈引入这种新体系是否值得。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进