一、凌晨两点核心交换机突然开始大量TCP重传凌晨2点13分某运营商数据中心完成了一次例行软件升级。本次升级内容非常简单只包含两项升级DPDK版本23.11 → 25.11为了提高CPU利用率将RSS Queue数量由16个增加到32个升级完成后所有自检全部通过。监控显示指标状态链路状态正常BGP正常OSPF正常LACP正常RX Error0TX Error0CPU利用率100%符合PMD预期业务开始恢复。二十分钟后运营平台突然报警TCP Retransmission Rate 从0.03%飙升至6.7%。与此同时还伴随着一些奇怪的现象。交换机整体吞吐下降约28%。HTTPS建立连接明显变慢。数据库同步偶尔超时。部分长连接开始频繁重传。但是令人困惑的是所有传统指标几乎全部正常。例如RX Packets RX Errors RX Missed RX No Mbuf TX Errors 全部为0DPDK统计信息同样正常。每个PMD线程依旧稳定占满一个CPU核心没有任何线程退出也没有任何异常日志。甚至ACL命中率、LPM查表时间、Hash查找耗时都与升级前几乎一致。整个系统看起来一切正常。除了——TCP开始疯狂重传。二、第一个怀疑对象是不是DPDK处理不过来了看到吞吐下降大多数工程师第一反应都是CPU是不是已经到瓶颈了于是查看perf。热点统计如下函数CPU占比rte_acl_classify()31%rte_hash_lookup()8%IPv4 Parser13%TX Burst19%其它29%与升级前几乎完全一致。继续观察PMD。Worker0 100% Worker1 100% Worker2 100% …… Worker15 100%没有任何Worker出现异常。如果是CPU瓶颈通常会出现某几个Worker明显过载RX Queue积压RX Ring开始增长RX Miss增加然而现场一个都没有。CPU依旧保持稳定。说明问题并不是CPU算不过来。三、第二个怀疑对象是不是网卡丢包既然CPU没问题。那是不是NIC出了问题工程师首先查看Intel网卡硬件统计rx_errors 0 rx_length_err 0 crc_error 0 rx_missed 0 tx_errors 0全部正常。继续抓Mirror口。Wireshark分析发现所有TCP数据都已经进入交换机。也全部正常发出。没有任何丢包。问题再次陷入僵局。四、真正异常的是TCP既然链路没有问题。那么只能看看TCP本身。工程师随机抓取一条连接。很快发现一个奇怪现象。正常情况下Seq 1001 1002 1003 1004 1005 1006而现场却出现Seq 1001 1002 1005 1003 1004 1006第一次看到时大家都以为抓包工具出了问题。继续抓几十条连接。结果完全一致。而且这种现象并不是偶发。几乎所有高流量TCP连接都会出现。TCP乱序。这也是TCP重传暴涨的真正原因。TCP协议并不知道交换机内部发生了什么。它只知道后面的包先到了。于是立即启动快速重传。大量重复ACK开始出现。网络吞吐迅速下降。五、可是交换机为什么会让TCP乱序这是整个故障最令人费解的地方。因为交换机内部根本没有任何缓存。每个数据包都是RX ↓ Parser ↓ ACL ↓ LPM ↓ TX整个过程只有几十微秒。理论上同一条TCP连接上的所有报文都应该严格按照收到的顺序完成处理。为什么会突然出现乱序团队开始怀疑是不是DPDK内部发生了变化。直到有人提出一句话升级时我们是不是改过RSS配置现场所有人突然安静了。升级脚本里确实有这样一段配置rss_conf.rss_hf RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP; nb_rx_queue 32;所有人的第一反应都是RSS只是把流量平均分到不同Queue提高CPU利用率它怎么可能导致TCP乱序事实上这正是许多DPDK开发者容易忽略的地方。RSS并不是一个简单的“负载均衡开关”而是一套由网卡硬件、哈希算法、重定向表RETA以及CPU调度共同组成的分流机制。只要其中任何一个环节配置不当就可能破坏Flow的一致性。真正的问题并不是RSS开没开而是同一条Flow到底是如何被映射到某一个RX Queue的回答这个问题需要先理解RSS在网卡内部究竟是如何工作的。六、RSS究竟在网卡内部做了什么很多开发者认为RSS的工作过程非常简单收到一个数据包 │ ▼ 计算Hash │ ▼ 选择一个RX Queue实际上Intel网卡内部远比这复杂。一个数据包进入网卡后大致会经历以下几个硬件阶段MAC Receive │ ▼ Packet Parser │ ▼ Extract Tuple │ ▼ Toeplitz Hash │ ▼ Redirection TableRETA │ ▼ RX Queue │ ▼ DMA写入Host Memory这里有两个核心组件决定了数据包最终会落入哪个RX QueueToeplitz Hash根据报文的关键字段计算一个哈希值。RETARedirection Table并不直接使用哈希值作为Queue编号而是通过一张硬件映射表将哈希结果重定向到具体的RX Queue。很多人知道RSS会计算哈希却不知道真正控制流量分布的是RETA而不是哈希值本身。这也是后续问题的关键。七、Toeplitz Hash为什么同一条Flow理论上永远进入同一个Queue为了保证同一条连接不会被拆散RSS不会对整个报文做哈希而是只提取能够唯一标识一个Flow的字段。以IPv4 TCP报文为例参与计算的通常是五元组源IP目的IP源端口目的端口协议号TCP/UDP等这些字段经过Toeplitz算法和一把预设的RSS Key计算后会得到一个固定的32位哈希值。只要五元组不变得到的哈希值也不会变化。这意味着同一TCP连接上的所有报文应当始终得到相同的RSS Hash。相同的RSS Hash应当始终经过RETA映射到同一个RX Queue。同一个RX Queue又通常绑定到同一个PMD线程。因此在硬件配置正确且软件架构合理的前提下同一条Flow天然具有CPU亲和性Flow Affinity无需软件额外干预。那么既然RSS已经保证了Flow Affinity为什么升级之后同一条TCP连接仍然会出现乱序真正的问题不在Toeplitz算法而是在RETA配置与软件线程模型之间的关系。这一点也是下一部分将重点分析的内容。
一次RSS配置错误引发的百万级乱序事故——深入理解DPDK RSS、Toeplitz Hash、Queue Affinity与Flow一致性(上)
一、凌晨两点核心交换机突然开始大量TCP重传凌晨2点13分某运营商数据中心完成了一次例行软件升级。本次升级内容非常简单只包含两项升级DPDK版本23.11 → 25.11为了提高CPU利用率将RSS Queue数量由16个增加到32个升级完成后所有自检全部通过。监控显示指标状态链路状态正常BGP正常OSPF正常LACP正常RX Error0TX Error0CPU利用率100%符合PMD预期业务开始恢复。二十分钟后运营平台突然报警TCP Retransmission Rate 从0.03%飙升至6.7%。与此同时还伴随着一些奇怪的现象。交换机整体吞吐下降约28%。HTTPS建立连接明显变慢。数据库同步偶尔超时。部分长连接开始频繁重传。但是令人困惑的是所有传统指标几乎全部正常。例如RX Packets RX Errors RX Missed RX No Mbuf TX Errors 全部为0DPDK统计信息同样正常。每个PMD线程依旧稳定占满一个CPU核心没有任何线程退出也没有任何异常日志。甚至ACL命中率、LPM查表时间、Hash查找耗时都与升级前几乎一致。整个系统看起来一切正常。除了——TCP开始疯狂重传。二、第一个怀疑对象是不是DPDK处理不过来了看到吞吐下降大多数工程师第一反应都是CPU是不是已经到瓶颈了于是查看perf。热点统计如下函数CPU占比rte_acl_classify()31%rte_hash_lookup()8%IPv4 Parser13%TX Burst19%其它29%与升级前几乎完全一致。继续观察PMD。Worker0 100% Worker1 100% Worker2 100% …… Worker15 100%没有任何Worker出现异常。如果是CPU瓶颈通常会出现某几个Worker明显过载RX Queue积压RX Ring开始增长RX Miss增加然而现场一个都没有。CPU依旧保持稳定。说明问题并不是CPU算不过来。三、第二个怀疑对象是不是网卡丢包既然CPU没问题。那是不是NIC出了问题工程师首先查看Intel网卡硬件统计rx_errors 0 rx_length_err 0 crc_error 0 rx_missed 0 tx_errors 0全部正常。继续抓Mirror口。Wireshark分析发现所有TCP数据都已经进入交换机。也全部正常发出。没有任何丢包。问题再次陷入僵局。四、真正异常的是TCP既然链路没有问题。那么只能看看TCP本身。工程师随机抓取一条连接。很快发现一个奇怪现象。正常情况下Seq 1001 1002 1003 1004 1005 1006而现场却出现Seq 1001 1002 1005 1003 1004 1006第一次看到时大家都以为抓包工具出了问题。继续抓几十条连接。结果完全一致。而且这种现象并不是偶发。几乎所有高流量TCP连接都会出现。TCP乱序。这也是TCP重传暴涨的真正原因。TCP协议并不知道交换机内部发生了什么。它只知道后面的包先到了。于是立即启动快速重传。大量重复ACK开始出现。网络吞吐迅速下降。五、可是交换机为什么会让TCP乱序这是整个故障最令人费解的地方。因为交换机内部根本没有任何缓存。每个数据包都是RX ↓ Parser ↓ ACL ↓ LPM ↓ TX整个过程只有几十微秒。理论上同一条TCP连接上的所有报文都应该严格按照收到的顺序完成处理。为什么会突然出现乱序团队开始怀疑是不是DPDK内部发生了变化。直到有人提出一句话升级时我们是不是改过RSS配置现场所有人突然安静了。升级脚本里确实有这样一段配置rss_conf.rss_hf RTE_ETH_RSS_IP | RTE_ETH_RSS_TCP | RTE_ETH_RSS_UDP; nb_rx_queue 32;所有人的第一反应都是RSS只是把流量平均分到不同Queue提高CPU利用率它怎么可能导致TCP乱序事实上这正是许多DPDK开发者容易忽略的地方。RSS并不是一个简单的“负载均衡开关”而是一套由网卡硬件、哈希算法、重定向表RETA以及CPU调度共同组成的分流机制。只要其中任何一个环节配置不当就可能破坏Flow的一致性。真正的问题并不是RSS开没开而是同一条Flow到底是如何被映射到某一个RX Queue的回答这个问题需要先理解RSS在网卡内部究竟是如何工作的。六、RSS究竟在网卡内部做了什么很多开发者认为RSS的工作过程非常简单收到一个数据包 │ ▼ 计算Hash │ ▼ 选择一个RX Queue实际上Intel网卡内部远比这复杂。一个数据包进入网卡后大致会经历以下几个硬件阶段MAC Receive │ ▼ Packet Parser │ ▼ Extract Tuple │ ▼ Toeplitz Hash │ ▼ Redirection TableRETA │ ▼ RX Queue │ ▼ DMA写入Host Memory这里有两个核心组件决定了数据包最终会落入哪个RX QueueToeplitz Hash根据报文的关键字段计算一个哈希值。RETARedirection Table并不直接使用哈希值作为Queue编号而是通过一张硬件映射表将哈希结果重定向到具体的RX Queue。很多人知道RSS会计算哈希却不知道真正控制流量分布的是RETA而不是哈希值本身。这也是后续问题的关键。七、Toeplitz Hash为什么同一条Flow理论上永远进入同一个Queue为了保证同一条连接不会被拆散RSS不会对整个报文做哈希而是只提取能够唯一标识一个Flow的字段。以IPv4 TCP报文为例参与计算的通常是五元组源IP目的IP源端口目的端口协议号TCP/UDP等这些字段经过Toeplitz算法和一把预设的RSS Key计算后会得到一个固定的32位哈希值。只要五元组不变得到的哈希值也不会变化。这意味着同一TCP连接上的所有报文应当始终得到相同的RSS Hash。相同的RSS Hash应当始终经过RETA映射到同一个RX Queue。同一个RX Queue又通常绑定到同一个PMD线程。因此在硬件配置正确且软件架构合理的前提下同一条Flow天然具有CPU亲和性Flow Affinity无需软件额外干预。那么既然RSS已经保证了Flow Affinity为什么升级之后同一条TCP连接仍然会出现乱序真正的问题不在Toeplitz算法而是在RETA配置与软件线程模型之间的关系。这一点也是下一部分将重点分析的内容。