写这个系列写到第十篇,后台留言越来越多人在问无损以太网的事。搞分布式存储、AI训练集群、高性能数据库的朋友,应该都对“网络卡顿”这件事又爱又恨——卡的是微秒级延迟,恨的是一旦丢包恢复就要毫秒级重传。今天这篇就把RoCEv2路上绕不开的四个关键词:无损以太网、PFC、ECN、DCQCN,外加拥塞可视化,一次说透。
这篇内容适合正在搭RoCE集群、调分布式存储网络,或者被AI训练性能问题折磨的人。哪怕你只是刚开始接触无损网络,只要理解这几个机制的原理和它们之间的配合关系,再遇到交换机告警、PFC计数器飙升、吞吐上不去这类问题,至少知道从哪里下手,不会一脸懵。
1. 为什么无损以太网成为AI与存储网络的“标配”
1.1 从TCP丢包说起:RDMA为什么不能容忍丢包
传统TCP/IP网络把丢包当成一种正常现象,拥塞了丢包,丢包了重传,重传能保证数据最终到达。听起来没毛病,但对RDMA这类追求微秒级延迟的传输方式来说,这个设计就是灾难。
RDMA(远程直接内存访问)最大的特点是绕过内核,数据直接从网卡到应用内存,CPU几乎不参与拷贝。好处是延迟极低,坏处是它没有那么复杂的重传机制。RoCEv2(RDMA over Converged Ethernet version 2)虽然基于UDP封装,但它的重传回收路径很短,一旦底层网络出现拥塞丢包,动辄就是毫秒级的恢复时间,极端情况下还会导致队列堆积、应用超时。
举个例子,在一套分布式存储集群里,多台机器同时向一台存储节点写数据,如果网络交换机的某个端口缓冲被打满,开始随机丢包,那么受影响的不只是那一条TCP流,而是所有依赖该路径的RDMA流。TCP应用可能还能扛一扛,RDMA应用直接就stall了。
所以在无损网络的设计目标里,第一条不是"带宽有多大",而是"一条流在传输过程中绝对不能因为拥塞而丢包"。这就是无损以太网出现的最根本动机。
1.2 无损不是一个开关,而是一套组合拳
很多人以为无损以太网就是在交换机上开一个PFC开关,开了就无损了。实际远没有这么简单。
无损是一个端到端的状态,需要链路层、网络层、传输层三方面配合:
链路层靠PFC(Priority Flow Control,优先级流控)做逐跳的"刹车",防止接收端缓冲溢出。
网络层靠ECN(Explicit Congestion Notification,显式拥塞通知)做拥塞标记,让发送端感知到路径上已经出现拥塞趋势。
传输层靠DCQCN(Data Center Quantized Congestion Notification,数据中心量化拥塞通知)做端到端的速率调整,在拥塞还没变成丢包之前就把发送速率降下来。
这三者各司其职,缺一个都不行。只开PFC不开ECN,拥塞信号只能在相邻设备间逐跳传播,无法快速通知发送端,容易产生PFC风暴。只开ECN不开PFC,交换机队列一旦暂满,依然会丢包,RDMA照样受损。所以真正成熟的RoCEv2无损网络,一定是PFC+ECN+DCQCN三件套一起上。
1.3 网络侧PFC与电源侧PFC,别被同名概念带偏
在搜索引擎上查PFC,很容易查到"图腾柱PFC""无桥PFC""三相维也纳PFC"这些词。这些是电源管理领域的功率因数校正(Power Factor Correction),和网络领域的Priority Flow Control完全是两码事。
电源领域的PFC解决的是电流谐波问题,网络领域的PFC解决的是流量缓冲溢出问题,两者只是缩写恰好相同。文章里后续出现的PFC均指Priority Flow Control,即IEEE 802.1Qbb标准定义的优先级流量控制。这个问题值得单独说,因为我在实际交流中发现,不少刚入行的朋友被同名缩写绕晕过,搜出来的资料牛头不对马嘴。
2. PFC:让以太网学会“踩刹车”的链路层机制
2.1 PAUSE帧与逐跳背压的工作过程
PFC的核心思路并不复杂:当一台交换机的某个接收队列快要满了,它就往上游设备发一个PAUSE帧(暂停帧),请求对方暂停发送某一类优先级的流量。上游收到PAUSE帧后,在指定时间内停止发送该优先级的数据,等下游缓过来之后,再恢复发送。
这个机制有两点关键设计。
第一,PFC是按优先级控制的,不是全部暂停。以太网报文里有3个PCP位(Priority Code Point),可以标识8个优先级(0-7)。在无损网络中,通常会把承载RDMA流量的队列设为无损队列(no-drop),其他普通流量放在有损队列(lossy)。这样PFC暂停某个优先级时,不影响其他优先级的业务,避免"一颗老鼠屎坏了一锅粥"。
第二,PFC是逐跳生效的。它不像ECN那样端到端通知,而是只在相邻两台设备之间传递信息。设备A发给设备B的流量太猛,B的缓冲不足,B就通知A先停一停。停多久由PAUSE帧里的timer字段决定,单位是多少个pause_quanta(1 quantum = 512 bit times,在10G链路上约51.2纳秒)。
逐跳背压的好处是响应快,不需要端到端的往返时间,几个微秒内就能生效。坏处是拥塞会像波浪一样向上游扩散,如果整条链路都出现拥塞,最上游的服务器网卡也会收到PAUSE帧,导致应用层发送受阻。
注意:PFC并不会消除拥塞,只是把拥塞的代价从"丢包"转移成了"暂停等待"。如果拥塞持续存在,PFC会形成一个阻塞链,从交换机一直传导到服务器网卡,表现为网卡端口上出现大量pause帧计数、发送队列堆积。所以PFC是兜底机制,真正的速率调节要交给ECN和DCQCN。
2.2 队列、优先级与阈值设置的关键参数
要真正使用PFC,交换机上要做的事包括三件:把某个优先级映射为无损队列,为该队列配置缓冲区阈值,启用PFC流控。
阈值设置是这里面最容易出问题的地方。阈值太低,队列稍微有点波动就触发PAUSE,网络利用率上不去;阈值太高,缓冲区被一个大突发占满,其他优先级没有缓冲可用,同样触发丢包。
一个实用的参考方式是:先算出端到端的带宽时延积(BDP,Bandwidth-Delay Product),然后按照该数值的一定比例来设置队列阈值。公式如下:
单链路BDP = 链路带宽 × 单向时延
假设是100Gbps链路,单向时延1微秒,BDP = 100 × 10^9 × 1 × 10^-6 = 100000 bit ≈ 12.5 KB。
这个数字看起来很小,但在实际无损网络中,交换机上的无损队列阈值通常会设置成几十甚至上百KB。原因在于PFC是逐跳生效的,PAUSE帧从发送到生效需要额外的处理延迟,而且多条流同时涌向一个端口时,瞬时堆积可能是单条流的数倍。保守起见,阈值至少设置成2-3倍的BDP。
另外,PFC的恢复阈值(resume threshold)也需要单独设置。当队列长度降到恢复阈值以下,交换机才会重新向上游发送恢复帧(实际是发一个timer为0的PAUSE帧),让对方继续发数据。如果恢复阈值设得太低,队列会频繁在暂停和恢复之间震荡,产生"抖动";设得太高,缓冲区利用率不足。经验上,恢复阈值设置为触发阈值的50%-70%比较合理。
2.3 PFC的隐性成本:死锁与队头阻塞
PFC最大的问题不是机理复杂,而是它引入了两个结构性风险:死锁和队头阻塞。
死锁场景是这样的:在一个环形拓扑里,设备A向设备B发送高优先级流量,设备B的接收队列满了,给A发送PAUSE帧;与此同时,设备B向设备A发送另一条高优先级流量,A的接收队列也满了,也给B发送PAUSE帧。结果两台设备都在等对方先恢复,谁都不发送,整个环路的这条优先级队列就卡死了。
这种情况在与存储网络互连、多路径拓扑中尤其容易出现。只要有一条路径出现拥塞,PFC暂停帧就可能在环路上形成循环等待。现在的交换机一般会有死锁检测和恢复机制,比如周期性检查某个优先级队列是否持续处于暂停状态,超时后强制丢弃该队列上的PFC状态。但如果网络规模大、路径复杂,死锁造成的业务中断依然是真实存在的。
队头阻塞(Head-of-Line Blocking,HOL阻塞)更隐蔽。交换机的物理端口通常有多个虚拟队列,但因为出口链路上的优先级队列只有一个,所有去往该优先级队列的流量如果都挤在一起,低优先级流也可能被PFC暂停连带卡住。
要降低这些风险,核心策略是:不要让PFC承担所有拥塞控制,它只负责兜底;真正的速率平滑靠ECN和DCQCN的上游反馈来做;同时控制无损失队列的流量范围,尽量只把RDMA流量放进来,其他一切流量都放到lossy队列。
3. ECN+DCQCN:从“被动暂停”到“主动降速”
3.1 ECN标记是如何产生的
ECN是IP层协议的一个能力,它用IP头里DS字段(Differentiated Services Field)中的两个bit来传递拥塞信息:一个bit是ECT(ECN-Capable Transport,支持ECN的传输),另一个bit是CE(Congestion Experienced,经历拥塞)。
发送端发送支持ECN的报文时,ECT置1。交换机在出口队列上配置了WRED(Weighted Random Early Detection,加权随机早期检测),当队列长度超过ECN标记阈值时,交换机不再直接丢包,而是在报文里把CE位置1,然后正常转发出去。接收端收到CE置位的报文后,就知道路径上出现拥塞了。
整个过程里,交换机一个包都没丢。它只是"打了一个标记",然后把拥塞的感知传递给了接收端。这就是ECN和普通丢包拥塞控制最大的区别:拥塞信号不是靠丢包来表达,而是靠标记来表达,代价低得多。
在RoCEv2无损网络中,ECN的标记阈值(ECN threshold)需要结合交换机的buffer大小和预期突发量来设置。设置得过高,ECN标记很少出现,拥塞信息传递滞后,PFC就会频繁介入;设置得过低,正常的突发流量也会被标记,导致发送端无谓降速,吞吐下降。
一个常见做法是参考队列的pause触发阈值,把ECN阈值设为pause阈值的60%-80%。这样ECN会先于PFC触发,先把速率降下来,PFC只在极端情况下兜底。
3.2 DCQCN的完整控制回路
DCQCN是微软和Mellanox提出来的拥塞控制方案,专门针对RoCEv2网络。它把ECN标记和发送端速率控制串起来,形成一个反馈闭环。
整个回路分三段:
第一段,发送端(RP,Reaction Point)以某个速率发送数据。发送时可以打上支持ECN的标记。
第二段,交换机拥塞时在报文上置CE位,接收端(NP,Notification Point)收到CE标记后,生成一个CNP(Congestion Notification Packet)报文,单播给发送端。CNP报文的优先级很高,不受拥塞队列影响,保证拥塞信息能快速到达。
第三段,发送端收到CNP后,按照DCQCN算法降低发送速率。降低的幅度由速率降低因子(g)决定,通常是按比例乘一个小于1的系数,比如0.5。与此同时,发送端开启一个恢复定时器(timer),每隔一段时间尝试增加速率,如果又收到CNP就再降,如果一段时间内没有收到CNP就继续加。
DCQCN里有两个关键状态参数:α是当前速率的下降比例估计值,它决定了降速的幅度;β是快速恢复因子,决定速率恢复的步长。实际调优时,这两个参数加上恢复时间间隔(常用10-55微秒),基本决定了整个网络的拥塞响应行为。
速率下降的方式非常简单:当前速率为Rate,收到一个CNP后,Rate = Rate × (1 - α/2)。这个"砍半再慢慢加"的策略,既避免了一次降太多导致的吞吐骤降,又能快速响应瞬时拥塞。
CNP报文的频率也很关键。如果交换机持续过着拥塞状态,接收端会持续收到CE标记并持续发送CNP,发送端会一直被压制。所以DCQCN里还设计了CNP生成速率限制,避免CNP风暴耗尽CPU。
3.3 DCQCN参数调优的经验值
网上关于DCQCN参数的文章不少,但大多数只讲概念,不讲具体怎么设。这里给一组我在实际环境中用过的基础建议,具体数值可根据硬件和业务场景微调:
g(rate decrease factor):建议0.5或1/256之间的值,数值越小降速越激进。实测中1/256比0.5更平滑,但对瞬时拥塞响应偏慢。云厂商常用的参考值是1/256。
timer(rate increase period):通常在10到55微秒之间。timer越短,速率恢复越快,但可能过早恢复导致震荡;timer越长,吞吐恢复越慢。存储场景建议从55微秒开始测试,AI训练集群可以压到4-10微秒试试。
α(alpha):初始值通常设为16(对应降速因子1/16),后续算法会自适应调整。这个值影响算法对拥塞程度的敏感度,调小会让算法更灵敏,但可能误判瞬时拥塞。
降速因子β:默认1/2,意图是快速恢复。如果网络延迟抖动大,可以把β调小到1/4。
这些参数之间互相牵扯,单纯盯一个值没有意义。我通常的调试顺序是:先把timer固定在一个中等值,调g和α,找到"吞吐不降、PFC计数不涨"的平衡点,再回头优化timer。
另外,很多网卡支持把DCQCN参数下推到硬件,用硬件定时器来做速率恢复,性能远好于软件实现。如果用的是Mellanox/NVIDIA网卡,强烈建议直接用硬件DCQCN模式,CPU占用率能低一个量级。
4. 拥塞可视化:比“看带宽”更重要的监控体系
4.1 传统监控为什么抓不到瞬态拥塞
做网络监控的人都有个体会:看带宽利用率曲线,明明都在50%以下,业务却报告网络卡顿。这不是监控数据骗人,而是传统监控的粒度太粗。
SNMP轮询通常5分钟一次取一次计数器,就算把轮询间隔压到30秒,捕捉到的也只是平均值。而无损网络里的拥塞,尤其是PFC和ECN触发的过程,往往是毫秒甚至微秒级别的瞬态事件。一次100ms的PFC暂停,反映到5分钟平均带宽上可能只有0.1%的变化,根本看不出来。
更麻烦的是,传统监控看到的是"设备总体"的状态,看不到"某个优先级队列"的状态。PFC暂停只作用于某个优先级,如果监控没拆到优先级维度,永远不知道是哪条队列在踩刹车。
所以要做拥塞可视化,第一步不是选什么工具,而是明确要观测哪些指标。
4.2 关键计数器:PFC、ECN、CNP的指标体系
拥塞可视化至少要涵盖四个维度的指标:
队列维度:每个无损队列的当前深度、最大深度、平均深度。队列深度能直接反映拥塞程度,是判断"要不要发生丢包"最前置的指标。
PFC维度:每个端口、每个优先级收到的PAUSE帧数量和发送的PAUSE帧数量。注意区分这两个方向——收到大量PAUSE帧说明自己是受害者,发送大量PAUSE帧说明自己带宽不足导致下游被暂停。
ECN维度:每个端口标记了CE位的报文数量。标记数量增加是拥塞开始出现的早期信号。
CNP维度:接收端每收到CE报文就生成一个CNP,所以CNP计数可以间接反映"端到端拥塞反馈"的频率。
除了这些,网卡侧还有几个指标值得关注:网卡收到的PAUSE帧数(Mellanox网卡可以在ethtool -S里看到rx_pause和tx_pause)、RDMA的丢包重传计数、平均往返时间(RTT)。尤其是RTT,它往往比PFC计数更早反映路径上的排队延迟增加。
一个比较实用的组合是:用交换机的队列深度和PFC计数判断拥塞发生的"位置",用ECN标记率判断拥塞的"烈度",用CNP频率判断控制回路是否在正常工作。四个指标综合起来,基本就能准确描述一次拥塞事件的完整生命线。
4.3 从数据采集到可视化看板的落地路径
搞清楚了指标,具体落地的工具链可以从轻到重分三步走。
第一步,直接在交换机上用命令行查。华为、H3C、思科、Mellanox交换机的命令行都支持查看PFC计数和队列深度,比如Mellanox的mlnx_qos -i eth0命令可以查看当前端口上的PFC设置和统计。问题在于这些命令只能看到当前瞬间的值,无法跟踪趋势,适合应急排查,不适合长期监控。
第二步,用Telemetry技术把数据推出来。现代数据中心交换机普遍支持流式遥测(Streaming Telemetry),可以以秒级甚至毫秒级周期上报队列深度、PFC计数、ECN标记计数等指标。数据到了Prometheus这类时序数据库中,再用Grafana画图,就能实现历史回溯和告警联动。
第三步,做一些弹性的聚合分析。当网络规模大了,单纯看单点数据效率太低,可以把所有交换机端口的PFC暂停时长加总,做一个全网"PFC活跃度"的热力图。哪个区域的颜色深,说明那个区域在频繁拥塞,需要重点排查业务流量模型。
我见过不少团队在可视化上花了很多功夫,最后发现最有效的还是那几张朴素的折线图——某个端口的队列深度随时间变化的曲线。一旦拥塞发生,曲线会有一个明显的尖峰;配合PFC暂停计数,几乎可以还原出拥塞从源头到扩散的全过程。
5. 常见问题与排查技巧实录
5.1 PFC死锁与风暴的定位思路
场景:某天存储集群的写性能突然掉到脚踝,业务反馈存储延迟从100微秒涨到5毫秒。登录交换机一看,某几个端口的tx_pause计数在疯狂增长,远超正常值。
排查思路分三步。
第一步,确认PFC暂停的方向。tx_pause高说明本端在向下游发暂停帧,代表本端出方向的缓冲不足;rx_pause高说明本端收到了上游的暂停帧,代表上游出方向的拥塞已经波及到了本端。存储写性能下降,大概率是数据流入方向的交换机端口rx_pause暴涨导致网卡发送被暂停。
第二步,定位是哪条优先级队列在暂停。用ethtool或交换机命令行按优先级查看PFC计数,找出异常的那条优先级。正常情况下,无损队列的PFC计数应该是低频的,如果持续高频暂停,说明有某种流量在持续冲击这个队列。
第三步,找出是哪条流在制造拥塞。可以用sFlow/NetFlow按五元组统计该优先级的流量分布,重点关注瞬间带宽最大的流。常见元凶是并行的数据备份任务、多个计算节点同时对同一存储节点发起写流量。
死锁的处理更紧急,一般先物理隔离环路上的备份链路或冗余路径,等业务恢复后再排查拓扑。注意不要在拥塞高峰期做大范围配置变更,容易把问题从单点蔓延到全网。
一个容易踩的坑:PFC风暴的根因不一定是交换机,也可能在网卡侧。有些网卡异常时会持续发出PAUSE帧把自己收到的流量全部暂停,导致交换机的发送方向出现大量PFC暂停。排查时一定要同时看交换机的收发两个方向和网卡的PFC计数,才能快速圈定实际"发病点"。
5.2 ECN与PFC之间的微妙平衡
ECN阈值和PFC阈值的配合关系,是全网无损网络调整里最微妙的地方。
之前说过,ECN阈值建议设置在PFC阈值的60%-80%。这样设计的好处是:ECN先触发,发送端降速,队列深度下降,PFC就不会触发。但如果ECN阈值相对PFC阈值太靠近,比如95%,流量突发时ECN还没来得及把速率降下来,PFC就被触发了,等于ECN完全没有起到"预警"作用。
另一个常见问题是ECN阈值设置过低。发送端对拥塞过于敏感,一点点正常突发流量就会触发降速,导致本来就健康的网络吞吐下降。这时候从监控面板上看,PFC计数很低,但吞吐也不高,业务延迟虽然在可接受范围内,但性能上不去。
遇到这种情况,调参顺序应该是:先调高ECN阈值,让ECN标记只出现在真正有拥塞的时刻;如果调整后出现了PFC计数快速上涨,再把ECN阈值调回来。用"ECN标记频率"和"PFC暂停次数"两个指标画一个二维坐标系,找到两者都快接近零的甜点区间,就是最优配置。
5.3 一份可以直接抄走的检查清单
最后整理一份无损网络健康检查的速查表,按重要性排序:
- 检查所有无损队列的PFC收发计数是否在正常基线内。不同业务基线不同,关键是持续观察,建立自己的基线。
- 检查ECN标记率。持续超过1%说明网络存在持续拥塞;突然飙升说明有突发流量。
- 检查CNP报文频率。频率过高说明拥塞反馈过于频繁,需要调整DCQCN参数或ECN阈值。
- 检查队列最大深度是否频繁触及PFC阈值。触及次数多说明buffer配置偏小或流量波动大。
- 检查网卡侧RDMA丢包重传计数。这个值永远应该是0,一旦出现非0值,说明无损链路已经失效,需要立刻定位。
- 检查是否有新的业务流量误入无损队列。常见于新上线的分布式存储服务默认把所有流量都纳入无损队列,导致PFC影响面扩大。
这些检查不需要一天做一次,但建议每周至少跑一次脚本,把核心端口的PFC、ECN、CNP计数记录下来,按月对比。做网络维护的人都知道,最怕的不是出了问题,而是问题出现时不知道什么状态是"正常状态"。有了历史基线,异常判断就会简单很多。
我在实际使用中发现,调试无损网络的一大半工作是和数据打交道:看计数器、对比基线、还原流量模型。PFC、ECN、DCQCN这套机制本身并不是新东西,难的是把它们在生产环境中调到一个彼此配合默契的状态。不同交换机、不同网卡、不同业务的流量特征各不相同,没有一组万能的参数,只有一套可靠的排查方法和持续观察的耐心。真正帮你定位问题的,往往不是复杂的高级功能,而是对几个关键计数器含义的理解深度,以及对人手一份基线的重视程度。
如果这篇文章能让你在下次面对PFC风暴时少一点恐慌,多一点思路,那这一篇就值了。下一篇有机会可以聊聊无损网络的多路径选路和自适应路由,那是比单机调优更上一层的话题。