电商、直播等业务要求以非常快的速度完成请求应答,计算和存储的飞速提高也在推动HPC、分布式训练集群、超融合等新应用的普及,网络变成制约性能的主要因素之一。为此,我们设计了低开销高性能的RoCE网络,构建了低时延、无损的大型以太网数据中心,作为RDMA等技术的底层基石,也为UCloud未来的物理网络建设打下了良好基础。

一、低开销高性能的无损网络选型

普通的内网进行数据包交互时,通常会使用系统级的TCP/IP协议栈或者是DPDK技术,这两种方案都是依靠软件进行协议栈解封装的,对系统的CPU有不少消耗。而有一种方案:RDMA,可以直接使用网卡进行协议栈解封装,无需消耗系统CPU,能有效降低数据处理的延时。

RDMA并没有规定全部的协议栈,比如物理链路层、网络层、传输层每个字段长什么样,如何使用,但对无损网络有相当高的要求:

– 不轻易丢包,重传带来的延时非常大。

– 吞吐量巨大,跑满最好。

– 延时越低越好,100us都嫌长。

依据上述要求,主流的网络方案有三种:

UCloud高性能RoCE网络设计_第1张图片

图:主流的RDMA网络方案

① InfiniBand: 该方案重新设计了物理链路层、网络层、传输层,是RDMA最初的部署方案,所以要使用专用的InfiniBand交换机做物理隔离的专网,成本较大,但性能表现最优;

② iWARP: 该方案的目的是让主流的以太网支持RDMA,将InfiniBand移植到TCP/IP协议栈,使用TCP协议保证无丢包,但缺点在于TCP开销较大,且算法复杂,所以性能表现较差;

③ RoCEv2: 该方案的目的也是让主流的以太网支持RDMA(RoCEv1版本已很少提及了)。网络侧使用PFC保证拥塞时不丢包,网卡侧又使用DCQCN的拥塞控制算法进一步减缓拥塞(该拥塞算法需要网络侧支持ECN标记),传统的以太网经过PFC和ECN的加持进化成为无损以太网,在无损以太网上运行RDMA性能大大增强。

RoCEv2(后文简称RoCE)方案的成熟案例较多,我们也选用了该方案进行研究。但RoCE方案仍存在一些问题,如PFC压制的不公平性、PFC传递带来的死锁风险、过多的调参、ECN标记的滞后性(ECN概率标记是软件轮询机制)等,是需要我们解决完善的。

二、网络设计的目标

要把RoCE搬到经典的数据中心网络上,这可不是一件容易的事儿。

当前数据中心是常见的CLOS架构,LCS是汇聚交换机,LAS是TOR交换机。如果RoCE直接运行在这上面,问题是显而易见的:例如出现Incast事件时,转发不了的报文会被存放在交换机缓存中,但缓存也不是无限大的,如果存满了,这个数据包就丢掉了,很明显这种丢包频率肯定不能被RDMA所接受。

UCloud高性能RoCE网络设计_第2张图片

图:CLOS架构示意

上面只是举了一个简单的例子,实际上出现的问题要更复杂一些。在设计之前,需要先明确好我们的目标是什么,做到有的放矢。

简单来讲我们的目标就是:

– 在各种流量模型下,网络带宽要能跑满;

– 缓存使用要尽可能低;

– 极限情况下缓存使用满了也不能丢包。

总结来说,为了让RoCE跑在已有的网络上,我们需要从三个方面下手:
① QOS设计:指队列、调度、整形等一系列的转发动作,相对独立。

② 无损设计:是RDMA的要求之一,使用PFC技术实现。无损是一种基本保障,含义是在最拥塞的情况下也能保证其可用性,让上层应用可以放心发送数据,不必担心丢包的风险(所以说PFC并不是降速的手段)。

③ 拥塞控制设计:使用DCQCN技术实现。拥塞控制是满足基本保障前提下的进一步优化,含义是在开始拥塞的时候,就告知服务器两端,使其从源端开始降速,从根本上解决问题。

补充一点拥塞带来的坏处:当出现拥塞后,必然要使用缓存,使用缓存后虽然不丢包了,但是带来的后果是延迟上升,而且吞吐也不能再增加一丝一毫。网络中拥塞点有很多,每一跳都可能成为拥塞点,在上图的网络中,最多会有3个拥塞点。

缓存的使用能带来多少延时?

我们按25Gbps来算,缓存25Mb的数据,大约需要1ms的时间才能发送完毕,25Mb也仅仅是3.1MB,而常见的Broadcom Trident 3芯片有32MB的缓存。

有了这三个方面的认识,我们就可以化繁为简,逐一破解。

三、QOS设计

QOS的设计,无非是入队、调度、监管和整形。

入队方式可以依据DSCP、TOS、COS等标记,然后信任某种标记入队,也可以选择使用策略抓取其它报文特征入队。我们最终选择的策略是:在IDC边界处,使用报文特征抓取入队,并重写DSCP,IDC内部仅根据DSCP入队(IDC内部减少策略使用,满足高速转发即可)。这样,既能保证DSCP标记的可信任,又能减轻IDC内部的策略复杂度。根据这个思路,我们分别设置对应策略:

– 对ToR下行端口与Border上行端口: 抓取特定报文,进入特定队列。

– 对其余设备和端口设置:信任DSCP,按映射入队。

用图表表达即:

■ IDC边界入队

次序

Match

Action

1

udp_dport==4791  &&
dscp==48

入队列6

2

udp_dport==4791  &&
dscp==46

入队列5

其他

其他

修改dscp为预定义

*这是已有的标记策略,我们IDC内部为业务进行分类,并标记特定的DSCP。

*其中次序1、2只在RoCE网络的ToR部署。

■ IDC内部DSCP映射

DSCP

队列

48

6

46

5

其他

2…

下面该聊聊调度设计了,调度的对象是缓存中的数据,也就是说,调度是仅在拥塞时才生效的,而且调度生效后,影响的将是各队列的流量大小。

带着以上的认识,我们开始调度设计。在一般的RoCE网络中,使用的有如下队列(或流量):

① 协议信令类,目前来看只有CNP流量;(其它协议均不跨跳,所以不考虑)

② RoCE流量;

③ 业务/管理流量。

UCloud高性能RoCE网络设计_第3张图片

这三大类流量,还可以继续分小类。按照ETC所推荐的调度模型,我们选择了SP+WDRR的调度方式,即:1类流量绝对优先,在缓存积压的时候优先调度,直到队列为空。2类和3类流量次优,两者之间按照WDRR调度,权重值可以灵活定义。这样就能保证CNP报文在3us内转发给流量源站(没有拥塞的网络单跳的延时在1us以内)。

以上调度设计中有个漏洞:如果队列6的流量过大,可能会将低优先级的队列饿死(即长时间得不到调度),虽然理论上队列6的流量一般都在几十~几百Mbps,但仍要提防服务器恶意***行为。于是,我们将SP的队列限制其队列使用带宽。这个便是所谓的监管和整形了。

四、无损涉及与分析

RoCE的流量需要保证运行在无损队列中,无损队列使用了PFC技术,能针对某一队列发送Pause帧,迫使上游停流。

UCloud高性能RoCE网络设计_第4张图片

在博通的XGS系列芯片中,有一块缓存管理单元MMU(简称缓存),存放已收到但没转发走的报文,并给入口和出口都计数:“0/1的入口和0/2的出口,都用了1个cell”(cell是缓存资源的最小单位)。

缓存会给每个入口和出口设置一个上限,超过这个上限就不能再使用cell缓存报文了。上限以下还画了很多其它的水线,同时对每一个出口和入口进行进一步细分,可以按照队列进行统计限额其中入方向。入方向上,细分了PG-Guaranteed大小、PG-Share大小、Headroom大小;出方向上,细分了Queue-Guaranteed大小,Queue-Share大小(如下图所示,这里我们不考虑端口,只考虑队列)。

UCloud高性能RoCE网络设计_第5张图片

图:队列入方向与出方向示意

缓存使用的时候,总是从下往上依次申请使用,所以更喜欢把这些区块大小称之为“水线”,当“某区块”都使用完毕,就称之为“缓存水位”到达了“某水线”。例如:当PG-Share区块使用完毕,就称之为,入口缓存水位已经到达PG-Share水线。如果所有区块用完就产生丢包了,称为no buffer丢包。

每一块大小都有其特殊用处,先简单看下其作用,后面再探讨下无损队列中的这5个水线应该如何设置。

►PG-GuaranteedQueue-Guaranteed是保证缓存,这部分是独享的,即使不用,别的队列也不能抢占使用。

►PG-ShareQueue-Share使用的是共享缓存,因为动态水线的缘故,它们的大小不固定,如果很多队列都在用,那平分一下,每个队列的水线就都很小。另外,PG-Share还有另一个重要的作用:PFC发送的临界点,也称为xoff水线,只要到达该水线,PFC就会从这个口发出去,回落一些后,才恢复正常。

►Headroom是一个特殊的水线,只有在无损队列中才能发挥其作用。设想一下,PFC发出去以后,流量真的能瞬间停下来么?答案是不能的!因为线缆中还有一部分数据,而且七七八八的转发处理时间也要算进去。所以Headroom空间就是用来做这个的。

**1、PG-Guaranteed和Queue-Guaranteed

**

讲完了基本原理,回过头来看网络设计。先看PG-Guaranteed和Queue-Guaranteed水线,这俩水线与“无损队列”关系不大,保证缓存的作用只是满足交换机基本的存储转发功能,所以配置为一个数据包大小即可。那我们按照最差的情况来算,即MTU=9216的巨型帧。

但实际上我们不必为此发愁,因为动态水线的缘故,共享缓存中总会有剩余的缓存以供使用,所以保持原厂的默认配置即可。

2、Queue-Share

接下来是Queue-Share水线。在无损队列中,我们希望在缓存丢包前,能触发PFC进行反压,所以在任何情况下,都应该入口PG-Share先到达水线,出口Queue-Share永远不能到达水线(PG-Share到达会发PFC,Queue-Share到达会丢包)。

之前讲过,MMU记账是出口入口各记一笔,这样来看,最差情况应该是多打一(出口的帐全记在一个队列上,入口的帐会均摊到不同队列中)。为了让出口水线永远不会到达,索性将出口水线配置为无限大好了,事实证明这样做也没有问题,因为入口的PG-Share是动态水线,总能在Buffer破产前触发该水线。

这样一来,Queue-Share好像已经搞定了,其实不然,如果TCP流量参与进来混跑呢?这问题可就严重了,TCP的Lossy队列会吃掉大量缓存,所以Lossy队列中,对应的Queue-Share水线也应当限制一下。

3、PG-Share

PG-Share水线只要配置为动态水线即可,大小可以随意调节,都不会出太大问题的,但需要满足一个不等式:(PG-Share + PG-Guarantee + Headroom) * [入口个数]≤ Queue-Share + Queue-Guarantee

该公式描述的是一个端口多打一的场景。入口个数根据实际情况选取一个较大值(拿ToR来看,最差情况是39打1,32个25G下行,8个100G上行)。

这里的PG-Share是动态水线,动态水线用一个简洁的公式即可表达:PG-Share = [剩余Buffer] * α

这里的α是缩放因子,用户可自由调节。可以看出,缩放因子决定了PG-Share水线的大小。依据上面等式,我们只要将Queue-Share水线设置为静态最大、PG-Share设置为动态即可,入口的缩放因子α可随意。当然入口α也不能设置太小,在端口少打多的情况下,由于入口的水位很低,导致均摊到每个出口时,出口的水位更低!出口的水位过低时,会发现已有的ECN配置不再生效(例如:可能出口的水位还到不了Kmax的一半)。在我们的经验看来,无损队列中PG-Share的α,配置1/8,1/4,1/2,1都可以,具体大小还要联合拥塞设计中ECN参数来决定。

4、Headroom

Headroom水线很重要,但可以通过实验+推导的方式得出合理的配置,先来看一个等式:[Headroom大小] = [PFC构造到停流的时间] * [端口速率] / [64字节小包占用的比特数]

使用64字节小包计算,是因为小包对缓存的使用率最低,单个Cell有200多字节,但只能被一个报文独享。其中,只有[PFC构造到停流的时间]是需要进一步分解的:T = Tm1 +Tr1 + Tm2 +Tr2* Tm1:下游PG检测到xoff用完,到构造PFC帧发出的时间。

* Tr1:PFC帧从下游发往上游的时间。

* Tm2:对端收到PFC帧,到队列停止的时间。

* Tr2:队列停止后,线缆中报文传输的时间。

可以看出,这四个时间中,只有线缆长度是变量,继续化简后可以得出:[Headroom大小] = (Tm1 + Tm2 +2 * [线缆长度] / [信号传播速度]) * [端口速率] / [64字节小包占用的比特数])

这里面Tm1 + Tm2 是常数,可以实验测得,剩余的都是已知量了。最后根据公式就可以算得100G口,100M光纤下,H = 408 cell;25G口,15M AOC下,H = 98 cell。当然,真正使用的时候,还要再冗余一点,毕竟这是临界值。

5、死锁分析和解决

谈到PFC就不得不提一下死锁,死锁危害极大,而且其传递性会迅速扩散到整个网络,以至于整个网络的无损队列全部停流。死锁的研究很多,其中较详细的是微软的一篇论文《Deadlocks in Datacenter Networks: Why Do They Form, and How to Avoid Them》。

死锁产生的一个必要条件是CBD(环状缓存依赖),在我们的组网环境中,是典型的CLOS组网,所以在稳定状态下不会存在CBD,也没有死锁风险。而且整个POD内部路由不做过滤,明细互知,汇聚采用4台~8台冗余,即使出现两点故障,收敛后的拓扑也不会存在CBD,即不会存在死锁风险。

UCloud高性能RoCE网络设计_第6张图片

图:CBD和死锁

至此,我们已经解决稳定状态下的死锁了,但还要考虑一点:收敛过程中,是否存在CBD?其实仔细分析一下还是会存在的,我们考虑了很多收敛场景,确实会有部分场景下,存在微环路。有微环路就一定有CBD。事实证明,我们也真实地模拟出了微环路导致的死锁。

死锁问题总是要解决的。我们使用三种方法:

1、针对各种微环路场景,通过设计网络协议,控制收敛的现先后关系,避免出现微环路出现。

2. 对于其它未知的死锁风险,使用交换机的死锁检测功能,释放缓存(释放缓存会产生丢包,但收敛过程本身就有乱序/丢包情况)。

3. 将PG-Share的水线适当拉高,尽量使用DCQCN拥塞控制来压制流量。

五、拥塞控制设计与分析

网络拥塞控制是一个很复杂的课题,这里只讲一些基本的设计思路。
RoCE使用的拥塞控制算法是DCQCN,_《Congestion Control for Large-Scale RDMA Deployments》_这篇论文很详细地描述了该算法。

这里先简单的描述下这个算法:维护这个算法的节点是服务器,也就是流量的两端,中间的交换机作为传输节点,通告是否拥塞。发送方叫Reaction Point,简称RP;接收方叫Notification Point,简称NP;中间交换机叫 Congestion Point,简称CP。发送方(RP)以最高速开始发送,沿途过程中如果有拥塞,会被标记ECN显示拥塞,当这个被标记的报文转发到接收方(NP)的时候,接收方(NP)会回应一个CNP报文,通知发送方(RP)。收到CNP报文的发送方(RP),就会开始降速。当发送方没有收到CNP报文时,就开始又提速了。

上述过程就是DCQCN的基本思路。虽然整个算法十分复杂,但都是围绕这个基本思路,继续完善算法细节(下图分别是NP的状态机和RP的算法)。可调参数也十分众多,比如降速要降低多少?提速效率是否积极?网络拥塞度如何维护?拥塞度更新周期多久?CNP报文的敏感度多大?这都是问题,需要对流量建模后找出合理参数。

UCloud高性能RoCE网络设计_第7张图片

图:接收方

UCloud高性能RoCE网络设计_第8张图片

图:发送方

DCQCN算法中,对RP、NP和CP都有很多参数可以调节。RP和NP节点在服务器上,准确来说应该是在网卡上,网卡初始化的参数已经为最优值,无需再进行调整,这样就剩CP上的参数需要调整了。

CP上有三个参数其实就是WRED-ECN的那三个参数,分别是Kmin,Kmax,Pmax,这三者的关系,可以用下图来表示。横轴是出向队列长度,纵轴是报文被标记的概率。从图中可以看到,在队列长度超过Kmax时,标记概率出现一个跳变,从Pmax直接到达100%。

UCloud高性能RoCE网络设计_第9张图片

根据上面的理论分析,我们可以通过实验证实和试错的方法一步步找到最优解。

现在设想一下:在一个拥塞场景中,当出口队列长度小于Kmin时,不会被标记,出口队列长度可能会稳步增长,当队列长度超过Kmin时,DCQCN才开始降速。
所以Kmin的大小决定了RoCE网络的基础延时,这些缓存中的报文是发送者发出,但未被接收者确认的报文,我们称之为inflight bytes,约等于延时带宽积。所以,Kmin的配置规范为小于期望的延时带宽积。有了这个理论基础后,实践测得理论符合实际,还可以根据测得的延时进一步调整该数值。

我们用同样的思路来思考Kmax,承接刚刚的思路,那就是:Kmax的配置规范为小于或等于能容忍的延时带宽积。但这次不再这么简单了,因为Kmax还决定了图中的斜率。同样决定斜率的还有Pmax,在讨论Kmax和Pmax前,我们不得不先介绍下整个ECN的理想与现实。

理想状态下,标记概率在定义域Kmin~Kmax内的变化是连续的,而且,队列的长度是准确的。但事与愿违,博通芯片SDK使用软件轮询的方式测得队列长度,而且将此刻的队列长度与历史值做指数平均,并依此计算标记概率。软件轮询带来的结果是,标记概率在定义域Kmin~Kmax内的变化是不连续的,其次,指数平均值会让测得的队列长度是滞后的(当然指数平均也带来了好处,这里不展开)。

这件事带给我们的影响就是,理论推导的Pmax,甚至Kmin、Kmax都被推翻,请继续往下看:理想状态下,一个25G端口、单QP会话下,最大的有效Pmax是多少?

根据DCQCN中NP的算法,50us内收到多个CE标记包,会被认为只有一个有效包,所以最高的CE标记速率应该为20000个包每秒(即1个包每50微秒),依此,我们算得最高有效Pmax,即是设置的Pmax值,如下表所示:我们假设一个25G端口、只有一个QP会话,此时最高有效Pmax是多少?可以根据表格中第4、5列计算出最后一列最高有效Pmax的值。

UCloud高性能RoCE网络设计_第10张图片

再回到现实,我们按照推导的数据对表格最后一行进行验证。

对端口限速模拟拥塞,测得稳定时RoCE流量pps=2,227,007,然后选取一组ECN配置:Kmin=1cell,Kmax=1400cell,Pmax=1%,理论上来说Pmax已经超出最高有效的值了,理论上即使在拥塞时,出口水位也不可能达到1400cell,所以再设置一个监控项,监控出口水位有没有超过1400cell(触发式告警,并非轮询,所以不会存在采集不到的情况)这是第一个实验。

作为对比,第二个实验使用另一组ECN配置,Kmin=800cell,Kmax=1400cell,Pmax=1%,按照之前分析,这一组配置下,出口水位也不会超过1400cell,因为在1400cell水位时,Pmax=1已经超过最高有效标记概率了。

可是实验结果并不符合预期,第一个实验没有触发告警,通过;第二个却触发告警了。这就意味着在某些时刻,缓存水位超过1400cell了!水位是波动的,并没有稳定在某个值!我们大胆猜测其中原因:从缓存队列积压,到得到缓解,这其中有太多地方消耗了时间:队列长度的轮询、指数平均算法、CNP的生成与转发,甚至于降速后线缆中的数据传输等等。

为解决这一难题,我们另辟蹊径,选择了另外一条路:首先制定了几个小目标,然后通过大量的实验来摸索出验证一套安全可靠的配置。这个方法虽然更野蛮,但很有效。

 小目标1:服务器端口吞吐量要在95%以上;

 小目标2:所有流量场景下交换机99%的时间里PFC发送速率不得高于5pps;

 小目标3:任意场景下服务器端到端延时不得高于80us(90%场景下低于40us)。

对于流量模型,我们设计筛选后,选用了50余种流量,最终我们得到了同时满足这三个小目标的合理参数。

不得不说,DCQCN很难玩转,参数众多且互有联系,这里也只是提供一些实践规律,欢迎一同深入探讨。

六、总结

为使物理网络具备承载RDMA业务流量的能力,我们选择了RoCE的网络方案,并通过QOS、无损、拥塞控制三块设计,来保证物理网络无损转发。RoCE无损网络为快杰云主机这样高性能的业务系统提供了强大的支撑,如高达120万IOPS的RSSD云盘,25Gbps的内网线速转发带宽。