RDMA网络刚上手那阵子,我一度以为只要把网卡插上、驱动装好、跑个测试工具,就能享受到“绕过内核、零拷贝、超低延迟”的极致体验。结果被现实狠狠教育了一顿:Performance 上不去、断连、重传、丢包,各种玄学问题轮番上演。折腾到最后,几乎所有问题的矛头都指向同一个东西——PFC(Priority-based Flow Control,基于优先级的流控),也就是无损网络里那个让人又爱又恨的“开关”。
这篇东西不打算写成教科书,而是想把我自己在配置无损网络、调优PFC过程中踩过的坑、总结出的思路,以及最后怎么把网络从“半残”调到“相对稳定”的过程记录下来。如果你也在搞RDMA,或者正准备给存储集群、AI训练集群搭建RoCE网络,希望这篇能让你少走点弯路。
1. 先搞清楚无损网络和PFC到底在解决什么问题
1.1 RDMA与“有损”网络的天然冲突
RDMA(Remote Direct Memory Access)的核心卖点是绕过CPU、绕过内核,让数据直接从网卡到应用内存,省去内核协议栈的拷贝、中断、上下文切换。理念很性感,但这套机制有一个致命前提:网络不能丢包。
传统TCP为什么能容忍“有损”?因为TCP有滑动窗口、有ACK重传、有拥塞控制,丢了包可以重传,延迟大了可以降速。但RDMA的设计假设是“网络足够可靠”,数据到了网卡就直接交给应用了,没有内核兜底。一旦发生丢包,RDMA网卡只能整个报文序列重传,而且重传粒度不是单个MTU,可能是多个报文一起重传。这个代价在数据中心场景里是灾难性的:哪怕只有万分之一的丢包率,吞吐量都可能直接暴跌到一半以下。
所以就有了“无损网络”这个概念:让网络设备在正常情况下不会丢包。而实现无损的关键技术之一,就是PFC。
1.2 PFC不是“暂停整个网口”,而是“暂停某一条队列”
很多人对PFC的误解是:它像802.3x流控一样,把一个端口整个暂停掉,网卡说停就停,交换机能空转就空转。实际不是,PFC的粒度是“优先级队列”级别。
以太网报文里有PCP(Priority Code Point)字段,3个bit,可以标识8个优先级。PFC协议(IEEE 802.1Qbb)允许交换机对每一条优先级队列独立做流控:当某个队列的缓存占用超过阈值时,交换机向对端设备发送暂停帧(Pause Frame),让对端暂停发送这个优先级的数据,但其他优先级不受影响。
这意味着:你可以把RDMA流量放到高优先级队列,把普通TCP放到低优先级队列,PFC暂停的时候只暂停高优先级,TCP流量照常走。这样RDMA流量“无损”,TCP流量用传统重传机制兜底,各得其所。
但是,这个“各得其所”是理想状态。实际配置中,队列划分、优先级映射、缓存阈值、死锁检测,每一个环节都可能搞出问题。我后面会详细拆。
1.3 分类探讨:RoCE v2环境下PFC的典型架构
目前数据中心里常见的RDMA实现是RoCE v2(RDMA over Converged Ethernet version 2),它把RDMA报文封装在UDP/IP里,可以在普通IP网络上跑。但因为跑在UDP上,就没有TCP的拥塞控制机制,必须依赖底层网络提供无损通道。
典型架构是这样的:
- 服务器上的RDMA网卡(Mellanox CX5/CX6、Broadcom网卡等)把流量打上特定优先级(通常是优先级3,也有用4的),作为高优先级流量发送。
- 交换机端启用PFC,针对优先级3开启流控。
- CNP(Congestion Notification Packet)机制配合DCQCN(数据中心量化拥塞通知)算法,作为端到端的二级流控:发送端通过接收CNP来感知拥塞,调整发送速率。
PFC解决的是“交换机缓存溢出”的问题,DCQCN解决的是“源端速率调整”的问题。很多人只配了PFC,没配DCQCN,结果PFC频繁触发,Pause帧把整个网络的局部带宽全拖垮了,这就是典型的“只治标不治本”。
2. 配置PFC前的关键抉择:方案选型与设计思路
2.1 先确定流量优先级,别乱抢“车队”
我见过很多团队直接照抄网络大厂的配置:优先级3给RDMA,VLAN里优先级的映射按标准来,Egress buffer留足。结果全网一上业务就出问题——因为他们没考虑自己的业务特点。
举个例子,AI训练集群里有两个重要流量:GPU通信流量(AllReduce、AllGather)和普通存储流量(读写日志、模型文件)。这两个都是RDMA流量,但特点是不同的:GPU通信对延迟极端敏感,必须在几微秒内完成同步;存储流量对吞吐有要求,但延迟容忍度稍微高一些。
如果你把两种流量都放到同一个PFC优先级队列里,一旦存储流量突发占用完buffer,GPU通信就得一起被Pause,整个训练任务的同步时间就会被拖长。这非常影响分布式训练效率。
我的建议是:把RDMA流量至少分成两个PFC优先级。
- 优先级3:GPU通信流量,高优先,转发优先,但触发PFC后要快速速降。
- 优先级4:存储流量,中高优先,带宽变化容忍度更高。
同一个物理网络,让不同业务走各自的PFC队列,可以减少“小队里的菜鸟拖累全队”的情况。代价是配置复杂度上升,交换机上要配好map,网卡上要配置好UP和queue的映射。
2.2 网卡侧还是交换机侧:PFC配置其实要“两头热”
PFC不是交换机一端配置完就完事的,它需要服务器网卡和交换机端口之间协商一致。简单来说:
网卡侧:你要告诉网卡“哪些优先级开PFC,哪些不开”,并且配置好队列映射。
- 如果用Mellanox网卡,可以用
cma_roce_mode、cma_roce_tos等参数,或者用mlxconfig改LINK_TYPE_P1、KEEP_ETH_LINK_UP_P1这些参数。 - 更常用的做法是直接用
mstconfig或mlxconfig配合tc命令,在网卡上配置优先级队列映射。
- 如果用Mellanox网卡,可以用
交换机侧:在端口下开启PFC,并把对应的PCP映射到正确的优先级队列。
- 以Cisco Nexus为例,配置
priority-flow-control mode on,加上priority-flow-control priority 3。 - 以Arista为例,使用
priority-flow-control on、priority-flow-control priority 3。 - 以SONiC为例,需要编写
qos.json和qdpm.json,配置PFC的watchdog、buffer pool、profile等。
- 以Cisco Nexus为例,配置
两头任何一边配置不匹配,PFC都不会正常工作。最常见的问题是:交换机侧开了PFC优先级3,但网卡发出的报文优先级是0或者2,Pause帧根本管不到它。
2.3 为什么不能全链路全开PFC:死锁和PFC风暴的隐患
还有一个新手常犯的错误:为了确保无损,把交换机的所有端口、所有优先级都开启PFC。这看起来是“既然要无损,那就全面无损”,实际会埋下两个雷。
第一个雷是PFC死锁。假设多台交换机形成拓扑环,某些队列的Pause帧互相等待,就会形成“谁都在暂停、谁都收不到数据”的死锁状态。一旦PFC死锁,整个网络的链路都会卡死,只能重启交换机设备。
第二个雷是PFC风暴。当某个队列持续触发PFC,会对对端设备产生大量Pause帧,疯狂占用交换机的内部带宽,影响其他队列转发,相当于“一台失控的车堵住了整条高速路”。
解决办法是:只在必要的接入端口开启PFC,核心和汇聚尽量不开;只对RDMA流量所属的优先级开启PFC,其他优先级全部关闭;同时在交换机上配置PFC Watchdog,一旦检测到持续的PFC风暴,自动强制丢弃该优先级的报文,打破死锁。
这些都是血泪教训里总结出来的。
3. 实操过程:一步一步把PFC配置拉起来
3.1 第一步:网卡侧的基础配置
先确认网卡型号和驱动版本。我这边用的是Mellanox ConnectX-5和ConnectX-6,驱动用官方OFED版本,建议至少更新到LTS版本。老的驱动对DCQCN、PFC的支持不全面,经常出现奇怪的丢包问题。
基础配置分三步。
第一,开启RMDA相关特性:
# 加载驱动 modprobe mlx5_core # 查看当前网卡设备 ibdev2netdev确认设备正常后,用mlxconfig配置网卡,核心配置如下:
mlxconfig -d /dev/mst/mt4119_pciconf0 set LINK_TYPE_P1=ETH mlxconfig -d /dev/mst/mt4119_pciconf0 set KEEP_ETH_LINK_UP_P1=1 mlxconfig -d /dev/mst/mt4119_pciconf0 set IP_OVER_IB=0LINK_TYPE_P1=ETH是让网卡工作在以太网模式,而不是InfiniBand模式。RoCE v2跑在以太网模式下。KEEP_ETH_LINK_UP_P1的作用是保持网卡链路UP,即使没有IP也不down掉,这对RDMA链路状态稳定很重要。
第二,设置网卡上RoCE流量的优先级映射。以优先级4为例,需要把DSCP(Differentiated Services Code Point)和用户优先级(UP)对应起来。RoCE v2的报文是UDP封装,DSCP在IP头的ToS字段里。常见的做法是从优先级4到DSCP 32或者28,具体看你的网络规划。
用rdma link命令可以管理RoCE链接:
rdma link set rxe0 pkey 0xffff第三,配置网卡上的QoS映射:
# 查看当前映射 ip link show dev enp175s0f0 # 恢复默认映射 rdma link set enp175s0f0 up真实环境下,推荐直接用tc工具标准化配置:
# 创建优先级4对应的高优先级队列 tc qdisc add dev enp175s0f0 root handle 1: mqprio num_tc 2 map 0 0 0 0 4 0 0 0 queues 0-0@0 1-1@1 hw 1 # 对优先级4的流量设置调度权重 tc qdisc replace dev enp175s0f0 parent 1:1 handle 2: tbf rate 100gbit burst 100m latency 10ms这里说明一下:map的含义是把优先级0-7映射到不同的TC(Traffic Class),上面的命令里map0 0 0 0 4 0 0 0表示优先级4的流量分配到TC1,其余分配到TC0。这样网卡在发时候就有了多队列的基础,PFC才能按照TC粒度控制。
3.2 第二步:交换机侧PFC配置
由于市面上交换机品牌较多,我以一个比较通用的、类似SONiC的CLI方式来描述。SONiC在云厂商里应用非常广泛,本身也是开源网络操作系统,很多白盒交换机都支持。
首先开启PFC:
# 全局开启PFC config qos pfc enable default # 在指定端口上开启PFC(只有端口1/1和1/2作为RDMA接入端口) config interface pfc on Ethernet1/1 config interface pfc on Ethernet1/2然后,设置优先级映射。SONiC里一般要写QoS MAP,把报文的优先级映射到内部队列:
{ "TC_TO_PRIORITY_MAP": { "AZURE": { "map": [ {"tc": 0, "priority": 0}, {"tc": 1, "priority": 4} ] } } }同时配置DSCP到TC的映射,保证RoCE流量可以被识别:
{ "DSCP_TO_TC_MAP": { "AZURE": { "map": [ {"dscp": 32, "tc": 1}, {"dscp": 28, "tc": 1} ] } } }在SONiC的环境中,这类配置通常放在/etc/sonic/qos.json里,配置完之后需要config reload才生效。
如果是Cisco Nexus设备,配置则更直接:
interface Ethernet1/1 priority-flow-control mode on priority-flow-control priority 4 no shutdown当然,不同厂商命令有细微差别。但原则一致:写清楚哪个端口开PFC、哪个优先级开PFC、DSCP怎么映射到TC。
3.3 第三步:验证PFC是否生效
配置完并不代表万事大吉,验证手段是绕不开的。
第一步,在服务器上用ethtool查看网口的流控状态:
ethtool -a enp175s0f0输出里有RX和TX两个方向。PFC模式下,RX表示“是否能够接收Pause帧”,TX表示“是否能够发送Pause帧”。对于无损网络,一般要求RX和TX都开启。如果你看到Pause frames: RX off TX off,就要回头检查网卡驱动和优先级配置了。
第二步,查看PFC帧计数器。在交换机和网卡上都能看到PFC帧的统计:
# 交换机上查看 show interface counters pfc # 网卡上查看 ethtool -S enp175s0f0 | grep pfc正常情况下,PFC帧数量应该很少,或者为零。如果PFC帧数量频繁增长,说明网络拥塞或者配置存在问题,需要进一步分析。
第三步,跑一个真正的RDMA流量测试。常用工具是ib_write_bw、ib_send_bw,也可以直接跑perftest系列的ib_write_bw -x 1,其中x表示使用RoCE模式。
# 服务端 ib_write_bw -d rxe0 -x 1 -q 8 # 客户端 ib_write_bw -d rxe0 -x 1 -q 8 192.168.1.2重点看两个指标:带宽是否达到预期(比如100Gbps网卡能跑到90Gbps以上),以及重传计数是否为零。带宽上不去或者重传有增长,基本可以判断PFC或者拥塞控制有问题。
3.4 参数“玄学”:Buffer 阈值、Credit、Watchdog
再深入一点,PFC的“微妙”之处主要集中在三个参数上:
Buffer Pool大小:交换机端口上有多个Buffer Pool,PFC队列的Buffer能否被其他队列借用,直接影响了PFC触发频率。Buffer太大,丢包少但延迟高,还可能造成“缓存膨胀”;Buffer太小,PFC频繁触发,带宽利用率低。建议按“一个RTT字节数”来估算。RTT按1微秒算,100Gbps下,一个RTT的带宽时延积约为100Gb/s × 1us ≈ 12.5KB。考虑到突发流量,建议至少给RDMA队列预留2-3个RTT的Buffer,也就是25-40KB,具体看交换机芯片是否有共享Buffer。
Credit(信用量):类似流控中的令牌机制,不是所有交换机都叫这个名字,但原理就是每发送多少字节或者每经过多少时间,才能接着发送PFC队列的数据。Credit配置过小会导致带宽利用率下降,过大则失去流控意义,需要根据实际流量模型调优。
PFC Watchdog:很重要。配置了Watchdog后,交换机会监控PFC状态,如果长时间收不到数据(比如持续STALL),就会触发恢复机制,强制丢弃或者重置队列状态,避免永久死锁。
这些参数的调整没有“万能公式”,必须根据你的业务流量模型来测试。建议用iperf3压测普通TCP,用ib_write_bw压测RDMA,逐步降低Buffer大小,看吞吐和延迟的变化,直到找到一个“丢包为零、延迟不太高”的平衡点。
4. 血泪总结:常见问题与排查技巧实录
4.1 同一条链路,为什么一边PFC生效一边失效?
有一次我配置了两台服务器直连交换机,A网卡显示链路UP、RDMA能跑,B网卡也能跑,但互相通信时,只要流量一大,A网卡就有大量重传,B网卡倒正常。查了很久,最后发现是A网卡的RoCE流量发出去是优先级3,B网卡接收端所在的交换机端口只开启了优先级4的PFC,导致从B侧发送方向出来没有Pause帧,A侧认为B主动丢包。
这个问题本质上就是优先级不一致。排查方法是用tcpdump抓RoCE报文,看里面IP头中的TOS/DSCP字段。RoCE v2的报文是UDP,端口号是4791(RoCE v2已经改用4791端口),抓到包之后就能看到实际用到的优先级。两端不一致就调整映射。
4.2 PFC死锁在集群环境里比单链路影响的更大
单链路死锁还好排查,真正可怕的是大规模集群里出现PFC风暴。我在测试一个32节点的AI训练集群时,所有节点间跑AllReduce,结果在某种负载条件下,整个集群吞吐会突然降为零,所有交换机端口疯狂打出Pause帧。原因就是多个交换机形成环路,Pause帧互相追赶,造成buffer耗尽。
这种问题的排查手段:
- 优先看交换机日志,有没有持续大量PFC的告警。
- 用
show interface counters pfc确认是哪些端口在发PFC帧,如果在短时间内计数暴涨,基本可以断定PFC风暴。 - 再进一步检查拓扑,是不是有冗余链路形成了二层环路。PFC的Pause帧不受STP控制,一旦形成环路,Pause帧会打满全网。
解决办法:除了调整拓扑,还要在交换机上开启PFC Watchdog,给PFC机制上一道保险丝。一旦持续PFC超过阈值,交换机就强制丢弃该队列报文,打破死锁。
4.3 DCQCN才是无损网络的“第二层缓冲”
PFC只是解决了“水位即将溢出时停住水流”的问题,但它没有解决“为什么水位会一直上涨”。真正避免PFC频繁触发的手段,是让发送端主动降速,这会用到DCQCN(Data Center Quantized Congestion Notification)。
DCQCN的流程是:接收端感知到拥塞(比如报文排队延迟增大),发送CNP(Congestion Notification Packet)给发送端;发送端收到CNP后,按照算法降低发送速率,过一段时间再尝试恢复。它和TCP Reno慢启动类似,不过RDMA用一个更加精细的量化步长控制。
命令行检查Mellanox网卡的DCQCN参数:
# 查看dcqcn相关参数 cat /sys/class/infiniband/mlx5_0/ports/1/cc_dcqcn_enable # 设置ECN(Explicit Congestion Notification)门限 echo 20000 > /sys/class/infiniband/mlx5_0/ports/1/cc_ecn_thresholdECN门限值对PFC触发频率影响极大。阈值设置太小,交换机频繁打上ECN标记,发送端频繁降速,带宽利用率不足;阈值设置太大,交换机buffer又可能被填满,触发PFC。我自己得到的经验值是:对于100Gbps链路、RTT 1微秒左右的场景,threshold大约在15000-25000之间比较合适,具体还要实测。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查手段 | 解决建议 |
|---|---|---|---|
| RDMA带宽上不去 | PFC未开启或优先级不匹配 | ethtool -a、抓包确认DSCP | 开启匹配的PFC优先级,统一映射 |
| 重传大量出现 | 交换机Buffer过小、丢包 | 网卡计数、交换机drop统计 | 增大RDMA队列Buffer,调节ECN阈值 |
| PFC帧数量持续增长 | 拥塞控制未配置,发送端持续满发 | 查看DCQCN是否开启,CPU占用率 | 开启DCQCN,调低ECN门限 |
| 多端口出现PFC风暴 | 拓扑环路、Watchdog未开启 | 检查STP状态、PFC计数 | 开启PFC Watchdog,调整拓扑 |
| 一台设备Pause全网 | 优先级队列被跨业务共用 | 检查业务流量DSCP | 给不同业务设置不同优先级 |
5. 经验体会:PFC不是“开关”,是一整套系统的协同
操作到后期,我的一个很深的感受是:PFC作为一个协议,本身并不复杂,但它嵌入在QoS、Buffer、ECN、DCQCN、拓扑设计这一大套系统里,任何一个环节失调,PFC就会从“保护神”变成“杀手”。很多网络工程师把PFC仅仅理解为“在端口上敲几个命令打开流控”,结果上线之后遇到各种性能问题,把锅甩给RDMA,说RoCE不稳定。其实真正的问题在于整个无损网络的配套措施没有跟上。
我个人建议的配置顺序是:
- 先梳理业务:哪些流量需要无损?哪些可以容忍丢包?各自对延迟和带宽的要求是多少?
- 再做优先级规划:为不同业务分配不同的PCP和DSCP。
- 然后配置网卡和交换机的映射,保证两端一致。
- 再调整ECN和DCQCN参数,让拥塞机制先于PFC介入。
- 最后才把PFC作为兜底机制开启,并配上Watchdog。
这套顺序是我自己试过比较顺的路径。如果一上来就把PFC全部打开,后续排查会增加不少成本。
6. 一个小技巧收尾
最后分享一个实用的小技巧:如果你手头的Mellanox网卡遇到了“带宽对不上”的问题,可以先去查看/sys/class/infiniband/mlx5_0/ports/1/cc_*下面的参数,这些文件直观反映了当前ECN门限、DCQCN使能状态、速率恢复时间等。很多情况下,性能不好不是硬件问题,而是ECN阈值设得过高,导致拥塞信号一直没有触发,等到PFC出来救场时已经晚了。
调参时可以一边用ib_write_bw压测,一边修改ECN阈值,观察带宽和PFC帧计数的变化。找到一个“PFC帧数低、带宽稳定”的窗口,把这个阈值固化到网卡配置里,之后再做大规模集群测试,稳定性会好很多。这个“边压测边调阈值”的方法,我百试不爽,你也可以直接照做。