2、RoCE协议栈深度解析:IBTA协议分层、RoCE v2的UDP封装、GRH与IP路由、RoCE的流控机制(PFC/ECN)
好,咱们接着聊。上一章我们把RoCE的“前世今生”捋了一遍,知道了它为什么能火。这一章,咱们得把袖子撸起来,看看它的“内脏”长什么样。
说白了,RoCE能跑得又快又稳,靠的就是一套精密的协议栈。我刚开始接触的时候,也觉得这东西挺玄乎,不就是把IB的报文塞进以太网吗?后来踩了几个坑才明白,里面的门道深着呢。今天我就带你一层层剥开它。
2.1 IBTA协议分层:从应用到物理的“四层楼”
RoCE的协议栈,本质上继承了InfiniBand(IB)的分层思想。你可以把它想象成一栋四层小楼:
- 上层应用(ULP):比如Verbs API、MPI、NVMe-oF。这是咱们程序员直接打交道的地方。
- 传输层(Transport Layer):负责可靠连接(RC)、不可靠数据报(UD)等。这是RDMA的核心,保证数据不丢不乱。
- 网络层(Network Layer):在IB原生环境里是GRH(全局路由头),在RoCE v2里就变成了IP头。
- 链路层/物理层(Link/Physical Layer):在IB里是IB链路,在RoCE里就是以太网MAC和PHY。
嗯,这里要注意:RoCE v1其实只改了最下面两层,把IB的链路层换成了以太网,但网络层还是GRH。这就导致了一个问题——它没法跨网段路由。我在一个数据中心项目里就吃过这个亏,两台机器不在同一个VLAN里,RoCE v1死活连不上。后来全换成了v2,才搞定。
核心区别:RoCE v1 = IB传输层 + GRH + 以太网链路层;RoCE v2 = IB传输层 + UDP + IP + 以太网链路层。
2.2 RoCE v2的UDP封装:为什么偏偏是UDP?
你可能会问:为什么RoCE v2要选UDP?而不是TCP?或者干脆直接裸IP?
我个人习惯从两个角度来理解这个问题:
- 性能角度:RDMA要的是低延迟、高吞吐。TCP有重传、拥塞控制、保序这些“重武器”,虽然可靠,但太慢了。UDP无状态,网卡硬件处理起来飞快,这才是RDMA要的。
- 路由角度:RoCE v1不能路由,因为交换机不认识GRH。但UDP是标准协议,所有三层交换机都认。RoCE v2把GRH的内容(源/目的GID)塞进UDP的载荷里,再用标准的IP头做路由,问题就解决了。
我记得有一次调试一个跨机柜的RoCE链路,抓包一看,UDP目的端口是4791。嗯,这是RoCE v2的“官方端口”。IANA分配的,跑不了。
封装格式大致是这样的:
+------------------+------------------+------------------+------------------+
| 以太网头 (14B) | IP头 (20B) | UDP头 (8B) | BTH + 载荷 |
| (DMAC, SMAC, Type=0x0800) | (DIP, SIP, Proto=17) | (DPort=4791) | (IB传输层数据) |
+------------------+------------------+------------------+------------------+
说白了,RoCE v2就是给IB的传输层数据包,套上了一件UDP的“马甲”,让它能在标准的IP网络里畅行无阻。
2.3 GRH与IP路由:地址转换的“翻译官”
GRH(Global Routing Header)在IB世界里是用于跨子网路由的。它包含了128位的源GID和目的GID。在RoCE v2里,GID是怎么映射到IP地址的呢?
这里有个关键点:RoCE网卡在初始化时,会给自己分配一个GID。这个GID通常是根据网卡的MAC地址和子网前缀生成的。在RoCE v2中,GID的格式被设计成与IPv6地址兼容。实际上,很多实现里,GID就是直接从IPv6地址映射过来的。
我曾经遇到过一个兼容性问题:两台不同厂商的RoCE网卡,在同一个子网里,GID格式解析不一致,导致连接建立失败。后来发现是GID的索引号对不上。所以,我建议你在部署多厂商设备时,一定要先验证GID的交互是否正常。
路由过程是这样的:
- 发送端:根据目的GID,查找本地路由表,找到对应的目的IP和下一跳MAC。
- 交换机:根据目的IP做标准的三层转发。
- 接收端:收到UDP报文后,解封装,取出BTH,根据目的GID判断是否是发给自己的。
避坑指南:我曾经在配置RoCE v2跨网段路由时,忘了在交换机上开启“UDP 4791”的转发。结果报文到了网关就被丢了。记住,交换机必须知道这个UDP端口是RoCE流量,不能随便丢弃。
2.4 RoCE的流控机制:PFC与ECN的“双保险”
RoCE最怕什么?丢包!因为RDMA的传输层(RC模式)一旦丢包,就要重传,延迟瞬间飙升。所以,流控是RoCE网络的命根子。主要有两招:PFC和ECN。
2.4.1 PFC(优先级流控):简单粗暴的“刹车”
PFC是链路层的流控。它把以太网流量分成8个优先级(VLAN PCP)。当某个优先级的接收缓冲区快满时,接收端会发一个PFC暂停帧给发送端,让它在那个优先级上“闭嘴”一会儿。
说白了,就是“你发得太快了,慢点!”
但PFC有个大问题:它可能会引起死锁或队头阻塞。比如,一个低优先级的流量被暂停了,但它占着缓冲区不放,导致高优先级流量也进不来。我在一个存储集群里就遇到过,PFC一触发,整个网络的吞吐量直接掉到零。后来我们调整了缓冲区大小和阈值,才缓解了这个问题。
警告:PFC是一把双刃剑。它能防止丢包,但用不好会引发连锁反应。建议只在RoCE流量所在的优先级上启用PFC,其他普通流量不要用。
2.4.2 ECN(显式拥塞通知):更智能的“减速”
ECN是网络层的流控。它比PFC更“文明”。当交换机检测到队列深度超过阈值时,它会在IP头里打上一个标记(CE码点)。接收端看到这个标记后,会通过CNP(拥塞通知包)告诉发送端:“哥们,前面堵了,降速吧!”
发送端收到CNP后,会按照一个算法(比如DCQCN)来降低发送速率。等拥塞缓解了,再慢慢恢复。
我个人更倾向于用ECN。因为它不会像PFC那样“一刀切”,而是让发送端主动调整,更平滑。不过,ECN需要端到端的支持:网卡、交换机、驱动都得配合好。
下面这张图展示了PFC和ECN的工作位置:
在实际部署中,我建议采用ECN为主,PFC为辅的策略。ECN处理大部分拥塞场景,PFC作为最后一道防线,防止缓冲区溢出导致丢包。你想想看,如果只靠PFC,一旦拥塞,整个链路都受影响;而ECN只影响那些“肇事”的流,更公平。
好了,这一章的内容就到这里。协议栈的分层、UDP封装、路由、流控,这些都是RoCE的“内功”。理解了这些,你就能明白为什么RoCE能跑得又快又稳,也知道在出问题时该从哪里下手排查。