交换芯片数据通路四结构:Cell、Crossbar、VOQ与共享缓冲
2026/9/17 8:53:34 网站建设 项目流程

两年多前我接手过一个挺磨人的排障任务:一台 64 端口 400GE 的交换芯片,平均负载常年不到三成,却每隔几个小时冒一次丢包,而且只丢在少数几个出端口上。宏观计数翻了一圈没看出毛病,最后把芯片手册里 Crossbar、VOQ、Shared Buffer、Cell Fabric 这几章重新啃了一遍,才意识到问题根本不在某个计数器上,而在数据通路的某一段结构,对这种流量形态天生就不友好。这篇就按数据从端口进、到端口出的顺序,把交换芯片数据通路里最关键的四个结构拆开讲:报文怎么被切成定长 Cell、Crossbar 如何在没有中央大脑的情况下完成每时隙的匹配、VOQ 怎么把队头阻塞从根上拆掉、Shared Buffer 凭什么用有限的片上 SRAM 扛住微突发,以及 Cell Fabric 上的乱序、重组和顺序保证到底怎么落地。内容偏硬件微架构,但做交换系统、DPU、数据中心网络调优的人都会用到——你不需要会写 RTL,但最好知道每一次丢包和时延毛刺背后,是哪一段结构在起作用。

1. 从一块 64 字节的 Cell 说起:数据通路到底在搬运什么

1.1 报文一进芯片就被切碎,定长 Cell 是被逼出来的

以太网帧是变长的,从 64 字节一路到 1500 字节,开了巨帧还能到 9KB 甚至 12KB。如果芯片内部直接搬变长报文,会连着踩三个坑。第一,Crossbar 的时隙是固定节拍,仲裁器每一拍只能建立一组连接;报文长度不固定,时隙就没法固定,仲裁也就没法流水线化,最后只能退化成"等整包搬完再调度下一包",吞吐直接崩。第二,片上缓冲要按变长分配,得做类似 first-fit 的碎片管理,这套逻辑放在 SRAM 里做,面积和时序都撑不住。第三,多播复制时变长更麻烦,每个副本的长度都不一样,复制引擎得带一堆状态。

所以交换芯片的通行做法是:报文在入口被切成定长的 Cell,内部全程以 Cell 为单位搬运和调度,出口再拼回完整的帧。Cell 大小常见的是 64 字节,也有用 128 字节甚至 256 字节的。选 64 字节有个很直接的道理——它就是以太网最小帧的长度,64 字节的小包刚好切一块,不用跨 Cell。代价在于那些"不整"的帧长,比如 65 字节的帧要切两块 Cell,内部搬运 128 字节,效率只剩一半。

以太网帧长(字节)需要的 64B Cell 数内部实际搬运(字节)内部搬运效率
64164100%
65212850.8%
1282128100%
5128512100%
150024153697.7%
9000141902499.7%

选更大的 Cell(比如 256 字节)能显著降低调度频率,代价是小包场景下内部浪费更严重,而且最小调度粒度变粗,时延抖动变大。所以大 Cell 一般出现在面向大包和高吞吐的机型上,面向通用数据中心、小包占比高的芯片更倾向 64 字节。

提示:Cell 大小是芯片流片时定死的,改不了。选型时如果业务以小包为主,一定要把"内部搬运效率"这一项算进去,它直接决定了你标称的转发能力在真实流量下能兑现多少。

1.2 400GE 上的一笔账:为什么内部必须以 Cell 而不是包为单位

先把基础换算捋清楚。400GE 端口的线速是 400 Gbps,也就是 50 GB/s。这个数字有个很好用的等价形式——1 纳秒约等于 50 字节。一块 64 字节的 Cell 在 400GE 上占用的时间就是 512 bit ÷ 400 Gbps,约 1.28 纳秒。

再看包。以太网在线上除了帧本身,还有 8 字节前导码和 12 字节帧间隙,一共 20 字节额外开销。一个 64 字节的小包在线上一共占 84 字节,有效载荷率只有 76.2%。换算成包速率:400e9 ÷ (84×8) ≈ 5.95 亿包每秒。也就是说,一个 400GE 端口跑满小包,每秒要处理近 6 亿个包。一台 25.6Tbps 的芯片,64 个 400GE 端口全跑小包,聚合包速率是 380 亿包每秒,平均每个包的预算只有 26 皮秒。

26 皮秒是什么概念?芯片主频按 1.6GHz 算,一个时钟周期是 625 皮秒。也就是说,每包 26 皮秒,等于说一个时钟周期里要处理二十多个包。单条流水线做包级处理是不可能的,只能靠两条路:一是把包处理拆成深度流水线,让不同包处在不同阶段;二是把调度和搬运的粒度从"包"降到"Cell",用并行度换时间。25.6Tbps 芯片的聚合 Cell 速率是 25.6e12 ÷ 512 = 500 亿 Cell 每秒,平均 20 皮秒一块 Cell。这个数字同样吓人,但它至少是可并行的——按平面拆、按 buffer bank 拆、按 VOQ 拆。

端口速率64B Cell 占用时间64B 小包线速 PPS
100GE5.12 ns1.49 亿
400GE1.28 ns5.95 亿
800GE0.64 ns11.9 亿

1.3 超速比:内部带宽为什么总要做得比端口大

很多刚接触这一行的人会问:端口加起来 25.6T,内部 fabric 也做 25.6T 不就够了吗?答案是远远不够,内部必须超速,通常 1.5 倍到 2 倍。原因至少有四条。

第一条是读写带宽。共享缓冲的每个 Cell 都要写一次、读一次,占用两倍的内存带宽。25.6Tbps 的芯片,光是缓冲的聚合读写带宽就要 51.2Tbps 量级,这还没算多播复制带来的额外读操作。

第二条是头部开销。每个 Cell 都要带 Cell 头,里面有源端口、目的端口、队列号、序号、SOP/EOP 标志、有效字节数、优先级、多播位图,实打实的几十比特。20 字节的头挂在 64 字节净荷上,开销就是 30% 以上,内部带宽自然要相应放大。

第三条是匹配失败的补偿。Crossbar 的仲裁器每个时隙解一次匹配,不可能保证每次都百分之百匹配成功。只要有输入和输出空着没配上,那部分带宽就浪费了。超速比给调度器留了容错空间。

第四条是排队时延。这条最容易被忽略。理论上,只要输入端有队列、输出端有队列,中间用匹配算法做调度,就构成了 CIOQ(Combined Input-Output Queued)结构。有经典结论表明:当内部超速比达到 2 时,CIOQ 可以模拟输出排队(OQ)交换机的行为,也就是说它能达到和"理想交换机"一样的吞吐和时延特性。1.5 倍是工程上常见的折中——功耗、走线、面积都还能接受,性能损失又不明显。

1.4 共享缓冲的容量账:1MB 能撑多久

片上的共享缓冲是有限资源,一般几十 MB 量级。64MB 的缓冲池分到 64 个 400GE 端口上,平均每端口 1MB。1MB 在 400GE 上能缓冲多久?1MB ÷ 50GB/s = 20.5 微秒。光在光纤里 1 微秒大约跑 200 米,20 微秒相当于 4 公里光纤的传播时延。

这个对比很能说明问题:片上缓冲看起来不小,实际连一条长距离链路的往返时延都覆盖不了几条。所以交换芯片的缓冲从来不是用来"存"的,而是用来吸收瞬时的不均匀——也就是微突发。指望靠加大缓冲解决拥塞,方向从一开始就错了,端到端的拥塞控制才是正解。

2. Crossbar:一张只在时隙上成立的"无阻塞"承诺

2.1 物理形态与 request-grant-accept 三次握手

Crossbar 的本质是一张 N×N 的交叉点矩阵,每个交叉点是一个受控开关。每个时隙里,每个输入最多连到一个输出,每个输出最多被一个输入连到——这就是它"无阻塞"的前提:任意输入到任意输出,只要不被别人占用,就一定能连上。

但问题来了:谁来决定每个时隙怎么连?如果做一个中央仲裁器统一决策,N=64 时的匹配问题规模是 64×64,每个时隙(1.28 纳秒)解一次,还要考虑可扩展性,显然不现实。所以实际芯片用的是分布式的请求-授权-接受(request-grant-accept)三次握手

  • 请求阶段:每个输入端口检查自己的 VOQ,只要有非空的 VOQ,就向对应的输出端口发请求。一个输入可能同时向多个输出发请求。
  • 授权阶段:每个输出端口收到一堆请求,按自己的策略选一个输入,发授权。
  • 接受阶段:每个输入可能收到多个授权,它只能选一个输出接受,其余忽略。

为什么非得分三步?因为输入和输出各自都有约束:输入这一侧每个时隙只能发一个 Cell,输出那一侧每个时隙也只能收一个 Cell。这两组约束分别落在两端,集中求解就是一个二分图最大匹配问题,分散求解就是这套握手协议。代价是匹配质量会打折,可能解出的是极大匹配而不是最大匹配,这也是超速比存在的原因之一。

2.2 匹配算法的取舍:从 Round Robin 到 iSLIP

最简单的做法是每输出独立轮询(Round Robin)。每个输出维护一个指针,每次从指针位置往后找第一个发请求的输入,授权给它。单个输出来看很公平,但放到整个系统里会出问题:所有输出的指针容易"同相",导致大家都盯着同一批输入端口,另一些输入端口长期没人授权,吞吐掉得很难看。

iSLIP是这一行的经典解法,核心思路是让指针只在"成功握手"时才推进,用非对称的更新规则打破同步。它每个时隙迭代若干次,常见做法是迭代三次。

每个输入 i 维护授权指针 g_i,每个输出 j 维护接受指针 a_j 迭代 1..3 次: // 请求:每个非空 VOQ(i,j) 向输出 j 发请求 // 授权:输出 j 从所有请求中,从 a_j 开始轮询选一个输入,发授权 // 接受:输入 i 从收到的多个授权中,从 g_i 开始轮询选一个输出, // 发接受,并把 g_i 更新为被选中输出的下一位 // 被接受的输出 j 把 a_j 更新为对应输入的下一位

第一次迭代时所有指针都从初始位置开始,容易同相,但第一轮结束后指针就被打散了,后面几轮迅速收敛。工程上多数实现对均匀流量一次迭代就能接近满载,非均匀流量下多迭代几次收益明显,硬件为了保证时序,通常做两到四次迭代。

涉及的另一个取舍是 PIM 类算法用随机指针而非轮询,随机化能天然避免同步,但硬件里生成高质量随机数也不便宜。像 iSLIP、DRRM 这类确定性算法,实现简单、可验证,是主流选择。

2.3 加速比 1.5x / 2x 换来的到底是什么

前面提到过,CIOQ 在超速比 2 的条件下可以模拟 OQ。这句话的工程含义值得展开讲。

输出排队(OQ)交换机是理论上的理想模型:每个输出端口有自己的队列,任何时刻进来的包都能立刻被放到目的地队列里,不存在输入侧等待。它的时延最优,但需要每个端口 N 倍于线速的内存带宽,64 个 400GE 端口就是 64 倍的聚合带宽,SRAM 根本扛不住,功耗更不用谈。

CIOQ 用输入侧 VOQ 加中间 Crossbar 来近似 OQ。超速比越大,调度器越有机会在下一个时隙把"本该立刻转发"的 Cell 提前送出去,行为就越接近 OQ。工程上的现实选择是这样:

超速比吞吐表现时延表现功耗与面积典型场景
1.0非均匀流量下明显掉速抖动大最低低端或成本敏感型
1.5多数场景接近满载抖动可接受中等主流数据中心交换
2.0理论上可模拟 OQ接近理想高性能/无损网络

选型时经常有人问"1.5 倍够不够",我的经验是:如果业务以 TCP 大流为主,1.5 倍基本无感;如果跑的是无损网络、AI 训练集合通信那种突发极强、对尾时延极敏感的场景,2 倍甚至更高的超速比带来的收益是能测出来的。

2.4 多平面与 striping:把一根粗管子拆成几根细管子

单平面 Crossbar 到了 25.6Tbps 这个量级,走线宽度和功耗都很难接受。所以现代芯片普遍把 fabric 拆成多个平面(plane),每个平面是一张规模小一些的 Crossbar,每个平面的速率是总速率的一部分。

Cell 在进入 fabric 前做 striping,也就是轮转着分配到各个平面上。比如四个平面,Cell 就按 0-1-2-3 的顺序轮着发。这么做有三个好处:单平面速率降低,走线和时序压力小了;每个平面有自己的仲裁器,仲裁并行度成倍提升,前面算出来的 20 皮秒一块 Cell 才有可能被消化;某个平面出故障时,其它平面还能继续工作,带宽降级但不全断。

代价也很明确:同一个包的不同 Cell 走了不同平面,到达出口的时间不再有保证,乱序是必然的。这就引出了后面第五部分要讲的顺序保证和重组问题。很多第一次设计这类系统的人会忽略这一点,以为 Cell 按顺序发出去就按顺序到达,实际不是。

3. VOQ:把队头阻塞从根上拆掉

3.1 单 FIFO 的 58.6%:一个绕不过去的数字

如果输入端口只有一个先入先出队列,会发生什么?假设队头那个包要去端口 5,但端口 5 此刻正忙,那这个包只能等。问题是它后面的包可能要去端口 7,而端口 7 完全空闲——但因为队头堵着,后面的包一个也走不了。这就是队头阻塞(HOL Blocking)

这个问题的严重程度有经典结论支撑:在均匀随机流量下,单 FIFO 输入队列的吞吐上限只有约 58.6%(准确值是 2 减根号 2)。也就是说,你买了一块 25.6T 的芯片,如果输入侧只有一个队列,实际能跑出来的可能连 15T 都不到。

解决思路非常直接:按目的地拆队列。到端口 5 的包放进队列 5,到端口 7 的放进队列 7,两者互不干扰。这就是虚拟输出队列 VOQ(Virtual Output Queue)。队头阻塞被消掉了,因为队头永远是去往某个特定输出的包,只要那个输出空闲,它就能走。

3.2 N² 队列的代价:资源不是免费的

VOQ 的代价是队列数量按平方增长。64 个输入端口,每个输入要为 64 个输出各维护一组队列,如果每个端口对之间再分 8 个优先级,就是 64 × 64 × 8 = 32768 个队列。每个队列都需要一个描述符、一个深度计数器、一对头尾指针,还要参与每时隙的请求-授权流程。32768 个队列的调度器规模,在片上是很实在的一块面积和功耗。

工程上有几种折中。一是端口分组,不按单个输出端口建 VOQ,而是把输出分成若干组,组内共用队列。组数少了,队列规模降下来,代价是组内还是有一点残余的队头阻塞(不过比全局 FIFO 好太多)。二是优先级裁剪,不是所有端口对都需要 8 个优先级,很多实现按端口角色配置队列数。三是队列描述符外置,把大表放到片外或者用更紧凑的编码。

注意:VOQ 数量不是越多越好。队列数增加会让每个队列的平均积压变浅,调度器在浅队列之间频繁切换,反而可能拉高调度开销和时延抖动。选型时看的是"VOQ 数量 × 优先级数"和实际业务队列需求的匹配度,而不是单纯比数字大小。

3.3 单芯片与多芯片:信用机制的两种用法

单芯片内部的 Crossbar 是无阻塞的,出方向又有共享缓冲兜底,所以很多实现里输入侧不需要复杂的信用机制,直接按请求-授权走就行。但一旦 fabric 跨了芯片——比如多芯片拼成一台大盒子,或者整机采用独立的交换网板——情况就变了。

这时的关键问题是:如果远端出口缓冲满了,怎么让近端停止发送?如果不管,Cell 发过去也会被丢掉,白占带宽。所以引入了基于信用的流控(credit-based flow control):接收侧为每条队列预留一定量的缓冲,把可用空间折算成信用(credit)告诉发送侧,发送侧每发一个 Cell 扣一个信用,扣到零就停。

这里有两个必须一起算的量。一个是信用往返时延:从发送侧扣信用到收到新信用,中间隔着链路传播、接收侧处理、返回路径,这个时间乘以线速就是"在途"数据量,信用池必须至少覆盖它,否则链路会出现空转。400GE 端口,往返 1.5 微秒,在途就是 50GB/s × 1.5µs = 75KB。另一个是信用粒度:信用按 Cell 记还是按字节记,按 Cell 记简单但会有取整浪费,按字节记精确但计数器位宽更大。实际实现里常见按 Cell 记,配合 Cell 大小做粒度换算。

3.4 队列选择与出队顺序:SP + DWRR 是最常见的组合

VOQ 解决的是"包放在哪个队列",还有一个问题没解决:"同一个输出的多个队列,先发谁"。跨端口可能同时有高优先级信令和低优先级备份流量,两者的诉求完全不同。

主流做法是**严格优先级(SP)加加权轮询(DWRR)**的组合:最高优先级队列用 SP,保证低时延业务不被拖;剩下的队列用 DWRR 按权重分配带宽。权重配置的关键是让它和实际业务带宽匹配,比如 400GE 端口上三个队列配 1:1:2,意思是在拥塞时它们的保底带宽比是 1:1:2。

我踩过的坑是权重配成比例数值而没考虑端口速率差异。同一组权重在 100GE 端口上和 400GE 端口上,对应的绝对带宽差了四倍,如果有跨端口的一致性要求,权重必须按速率归一化,或者用百分比形式表达。

4. Shared Buffer:把有限的片上 SRAM 用在最需要的地方

4.1 共享与独享:为什么共享能扛住微突发

出方向缓冲有两种组织方式。**独享(dedicated)**是每个出端口分一块固定大小的缓冲,比如 64MB 分给 64 个端口,每端口 1MB,谁也别抢谁的。**共享(shared)**是所有出端口共用一个大池子,谁有需要谁多占一点。

网络流量的一个基本特征是不均匀。同一时刻,总有几个端口在堵,另外几个端口很闲。独享方案下,堵的端口那 1MB 用完就开始丢,闲的端口那 1MB 基本闲置。共享方案下,闲端口的空间可以被堵端口临时借用,整个池子的利用率高得多。有研究做过对比,同样大小的池子,共享方式能支撑的突发吸收能力通常是独享的两到四倍。

代价也很明确。共享池的聚合读写带宽要做到 2 倍线速以上,前面算过是 51.2Tbps 这个量级;多端口同时访问同一个 bank 会冲突,需要做 bank 交织和访问调度;而且共享必然带来"某条队列吃光整个池子"的风险,必须有门限管理。

4.2 动态门限 DT 与 α 的取值

门限管理最简单的形式是静态门限:每条队列最多占 X 字节,超了就丢。它的问题是 X 很难定——定小了,池子空着也不用;定大了,几条激进队列就能把池子占满,别的队列全被饿死。

所以主流实现用动态门限(Dynamic Threshold, DT)。核心思想是:某条队列能占多少,取决于当前池子里还有多少空闲。空闲多的时候允许它多占,空闲少的时候收紧。简化形式大概是这样:

threshold(q) = alpha * (Fb - Qb) 其中 Fb = 共享池总容量 - 为无损预留的 headroom Qb = 当前所有队列已占用的总字节数 alpha = 调节系数,常见 1/8、1/4 若 队列 q 的占用量 + 本次入队量 > threshold(q): 触发丢弃(有损场景)或反压(无损场景)

α 的取值是这套机制里最需要拿捏的旋钮。取 1/8 比较保守,队列之间隔离性好,但池子利用率偏低;取 1/4 激进一些,利用率和突发吸收都更好,但要小心某条大流把池子吃得太满;取到 1/2 就非常危险了,一条队列能吃掉半个池子,其它端口的时延会被严重拖累。我的经验是:面向通用数据中心的场景用 1/8 到 1/4,面向存储和无损网络用更保守的值,因为无损场景对丢包的容忍度是零

注意:厂商文档里的"动态门限"公式各不相同,有按剩余空间算的,有按占用比例算的,还有带指数加权移动平均的。改这些参数前一定要找到对应芯片的手册确认公式形式,不同实现里同样叫 α 的系数含义可能完全相反。

4.3 链表式缓冲管理:free list、链表与多播复制

缓冲池里存的是 Cell,管理它们的数据结构是链表。初始化时所有 Cell 描述符串成一条空闲链表(free list),入队时从 free list 摘一个挂到目标队列尾部,出队时把描述符还回 free list。整个过程中 Cell 本体在 SRAM 里不动,只动描述符里的 next 指针。

这套设计有几个细节容易出问题。一是描述符耗尽:Cell 存储还有空位但描述符用完了,此时新来的包只能丢,监控里要单独看这个计数。二是链表过长导致的遍历开销:出队是从头部取,入队是往尾部挂,两端都要能 O(1) 访问,所以是双向链表或者头尾指针分离的环形结构。三是多播:一个多播包要复制到多个出端口,如果每个副本都单独存一份 Cell,缓冲消耗按多播组规模线性放大,64 个出端口的多播组就是 64 倍占用。

省空间的做法是单次写入、多次读取,也叫复制读。Cell 在池子里只存一份,每个出端口在读出阶段各自读一遍,读的时候把目的端口位图(bitmap)作为 Cell 头的一部分,出方向按位图决定哪些端口要取。这样缓冲占用只算一份,代价是读带宽需求增加——但读带宽本来就是共享池的瓶颈之一,所以真实实现里通常还有个折中:小规模多播用复制读,规模特别大的多播可能用专门的复制引擎或者干脆限制。

4.4 无损网络的 headroom:缓冲到底该留多少

无损网络(RDMA over Converged Ethernet 这类场景)不允许丢包,靠的是优先级流控(PFC)。但流控信号不是瞬间到达的,从出口缓冲接近满、到发出暂停帧、到上游真的停下来,中间隔着一个往返时间。这段时间里上游还在全速发,出口必须留出一块缓冲来接住这些"还在路上"的数据,这块预留就叫headroom

headroom 怎么算?核心就是线速乘以反应时间。400GE 端口,链路加芯片内部流水线的反应时间按 1.5 微秒估,headroom = 50GB/s × 1.5µs = 75KB。工程上一般乘个安全系数取 2,也就是 150KB 左右。看起来不多,但要注意这是每个端口都要预留的,而且是只进不出的死区。

端口数每端口 headroomheadroom 总量占 64MB 池子比例
32150 KB4.8 MB7.5%
64150 KB9.6 MB15%
128150 KB19.2 MB30%

表里能看出来,端口数一多,光 headroom 就吃掉三成池子。这就是为什么高密度端口芯片上"能用的共享缓冲"比标称小不少,规划 DT 和 α 的时候必须先把 headroom 扣掉再算。

5. Cell Fabric 上的乱序、重组与顺序保证

5.1 Cell 头里到底带了哪些信息

Cell 是内部搬运单位,它必须自带足够的元信息让下游知道怎么处理。典型字段包括:

  • 源端口与目的端口:目的端口用于出口归宿,源端口用于回填统计和必要的源检查。
  • VOQ / 队列标识:标识这个 Cell 属于哪条队列,出口重组和顺序保证靠它。
  • 包标识与序号:同一个包的多个 Cell 共享一个包 ID,序号标明在包内的位置。
  • SOP / EOP 标志:标记包头和包尾,重组引擎靠它判断一个包的边界。
  • 有效字节数:最后一个 Cell 通常不满,需要知道真实长度。
  • 优先级 / 流量类别:调度和缓冲管理用。
  • 多播位图:多播场景下标识需要复制到哪些端口。
  • ECC 校验位:内部传输也得过校验,尤其是长走线的并行总线。

这些字段加起来几十比特,占了 Cell 总宽度的相当一部分。做带宽预算的时候不能只算净荷,头开销必须一起算进去。

5.2 同一条流为什么不能乱序

以太网本身不保证顺序,理论上乱序是允许的。但实际网络中,乱序的代价很大。TCP 收到乱序段会触发重复 ACK,三次重复 ACK 就触发快速重传,把本来没丢的包重传一遍,有效带宽直接打对折;RDMA 这类协议对乱序更敏感,很多实现会把乱序当作错误处理。所以交换设备必须保证同一条流内部的顺序,这是硬约束,不是可选项。

麻烦在于多平面 striping。前面说过,同一个包的 Cell 会轮流分配到不同平面,走不同路径,到达出口的时间不再一致,乱序几乎必然发生。解决办法大致有三类。

第一类是同一包不跨平面。一个包的所有 Cell 都走同一个平面,包与包之间才做 striping。这保证了包内的顺序,实现简单,代价是包级别的负载均衡粒度变粗,短包多的时候平面间负载不均。

第二类是按 VOQ 保持顺序。每个平面的调度器对同一个 VOQ 的 Cell 保持先进先出,出口侧按 VOQ 做汇聚,只要每个平面内部的顺序是对的,汇聚时再按平面顺序取,就能恢复全局顺序。代价是汇聚逻辑要考虑多个平面的到达状态,缓存需求增加。

第三类是序号重组。每个 Cell 带全局序号,出口按序号重排。灵活性最高,但需要重组缓冲和超时机制,最坏情况下一个 Cell 迟到就得一直等。

实际芯片多数是第二类和第三类的组合:包内顺序靠序号保,包间顺序靠 VOQ 汇聚保。

5.3 重组缓冲与超时丢弃

出口拿到一堆乱序 Cell,需要把它们拼回完整的帧。重组引擎的逻辑是:遇到 SOP 就开一块新缓冲,把后续 Cell 按序号填进去,遇到 EOP 就把整包提交给出口队列。麻烦的是异常情况。

如果某个 Cell 在 fabric 里被丢了(比如校验错、信用不足),这个包的 EOP 就永远等不来,重组缓冲会被一直占着。所以必须有超时机制:一段时间内没等到 EOP,就把这个半包丢掉,释放缓冲,同时把该包已经拼好的部分一并丢弃,绝不能发一个残缺的帧出去。相关的计数器包括:Cell 失序计数、重组超时计数、半包丢弃计数。这几个计数在排障时非常关键,它们异常基本可以锁定 fabric 或者重组环节出了问题。

提示:重组超时值不能设得太短。设短了,正常但路径较长的包会被误杀,表现出来就是零星丢包和 CRC 错误,而且只在多平面负载不均时出现,很难复现。留足最坏路径的余量再往下调。

5.4 降级路径与背压传导

真实系统里 fabirc 也可能出问题:某个平面失效、某条链路的信用长期批不下来、某个 SRAM bank 报 ECC 错误。这些情况下系统需要优雅降级而不是直接崩。

单平面失效时,剩下的平面继续工作,聚合带宽按比例下降。如果 striping 是按固定轮转做的,失效率是 1/N;如果做了动态权重,可以先重分配。关键在于出口侧的重组引擎要能识别"这个平面不会再来 Cell 了",否则重组缓冲会被永久占用。

信用枯竭的传导路径值得单独说一下。出口缓冲满,暂停返还信用;输入侧信用见底,停止发送;VOQ 开始快速积压;积压到门限触发入口反压或丢包。这条链路上任何一环配置不当,都会表现为"某个端口吞吐突然掉一截,过一会又恢复"。排查时要顺着链路一段段看计数,而不是只盯终端表现。

6. 把架构映射到计数器:实测与排障怎么做

6.1 症状、位置、计数器的对照表

架构讲完,落到实战就是一件事:拿到一个现象,能快速定位到是哪一段结构出了问题。下面这张表是我自己排障时常用的对照。

现象大概率出问题的位置优先看的计数
平均负载不高但偶发丢包出方向共享缓冲、动态门限出队列水位峰值、DT 丢弃计数、headroom 使用量
单个出端口吞吐上不去VOQ 或 Crossbar 匹配各 VOQ 深度、授权成功率、fabric 信用余量
同一入端口到不同出端口时延差异大VOQ 队列选择、调度权重各队列积压、DWRR 权重与实际带宽比
小包达不到线速流水线、Cell 调度Cell 速率计数、流水线停顿计数
重启后短暂丢包重组与顺序保证Cell 失序计数、重组超时计数
多播业务一开就丢多播复制与缓冲占用多播缓冲占用、复制读带宽

看这张表的时候要有个意识:现象的"位置"和"原因"未必在一块芯片上。比如端到端时延抖动,可能是上游某台设备的 VOQ 积压造成的,而你在下游设备上看到的只是结果。所以定位的第一步永远是确定责任设备,第二步才是看它的内部结构。

6.2 微突发怎么观测:为什么平均值看不出问题

微突发是这类系统里最常见也最难查的问题。它的时间尺度通常在微秒到几十微秒,而监控系统默认的采样窗口往往是 1 秒甚至 5 分钟。一个持续 10 微秒、打满 400GE 的突发,摊到 1 秒里只让平均速率上升了 0.4%,图上完全看不出来,但它已经足以把某个出队列的水位推过动态门限。

观测微突发有几个可行的办法。一是看端口峰值计数,很多芯片提供了带时间窗口的水位峰值寄存器,比平均值有用得多。二是看出队列丢弃的突发特征,如果丢弃是成簇出现的,而不是均匀分布,基本可以确认是突发而非持续拥塞。三是缩小采样窗口,把统计周期从秒级降到毫秒级甚至更细,代价是数据量大,得有选择地开。四是看ECN 标记和 PFC 暂停帧的时间分布,这两个信号比速率计数更贴近瞬时状态。

6.3 几个我踩过的坑

第一个坑是把共享缓冲当成独立缓冲来算。规划的时候按 64MB ÷ 64 端口算,得出每端口 1MB,然后按 1MB 去设计 DT 门限。结果一上量就丢包。真实可用的空间是 64MB 减去 headroom 预留、减去控制结构占用的那部分,实际能给数据用的可能只有 80%。

第二个坑是 VOQ 优先级配少了。一开始只配了 4 个优先级,觉得业务够用。后来上了新的业务类型,两类流量被塞进同一个优先级队列,一拥塞就互相干扰,高时延的那类被低时延大流拖得很难看。VOQ 数量在流片时是定死的,规划阶段一定要留余量。

第三个坑是忽略多播的缓冲放大效应。一个 64 个出端口的多播组,如果按每副本一份存储,缓冲占用是单播的 64 倍。测试的时候只跑单播,一切正常;一开多播业务,缓冲瞬间被吃光。后来确认了芯片用的是复制读方案,缓冲占用只算一份,但复制读带宽成了新瓶颈,需要给多播单独留带宽配额。

第四个坑是用平均值判断微突发。前面讲过,这个坑很经典。我们花了不少时间才把监控改成毫秒粒度加峰值水位的方式,改完之后很多"偶发"的问题一下就解释得通了。

第五个坑是调 α 调过头。为了提升缓冲利用率把 α 从 1/8 调到 1/4,短期看丢包确实少了,但某条大流开始能吃掉相当比例的池子,其它端口的时延明显抬升。最后回到 1/8,改从流量整形侧解决,效果反而更稳。

6.4 一个实用的验证思路

如果你手上有设备,想验证自己是不是真的理解了这套数据通路,我建议做一个简单的对照实验:构造一组从端口 0 到端口 1 的大流,同时构造一组从端口 0 到端口 1 之外的多个端口的小流,让它们共用端口 0 的入方向带宽。

如果 VOQ 工作正常,小流不应该被大流显著影响,因为它们在不同的 VOQ 里。如果观测到小流的时延随大流增大而明显上升,说明队列隔离没做到位,可能是 VOQ 数量不够或者调度权重配置把两类流量耦合在了一起。这个实验不需要很贵的仪表,用两台服务器打流加基础计数就能看出趋势,是我做方案验证时的第一步。

关注后续我会再写一篇讲控制通路的部分——查表、ACL、计量这些模块和今天讲的数据通路是怎么咬合的,那块出问题的方式和数据通路完全不同,经常表现为"配置生效了但行为不对"这种更绕的现象。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询