3. RSS 哈希算法详解:Toeplitz 哈希、对称哈希与哈希键配置
RSS(Receive Side Scaling)的核心在于将入站数据包的流特征(五元组等)通过哈希函数映射到一个有限的哈希值空间,再通过重定向表(Redirection Table)将哈希值映射到特定的接收队列。本章深入剖析 RSS 使用的哈希算法、对称哈希的工程意义以及哈希键(RSS Key / Hash Key)的配置方法。
3.1 Toeplitz 哈希算法
Toeplitz 哈希是 Intel 网卡 RSS 默认使用的哈希算法。它是一种基于随机矩阵的线性哈希,具有计算简单、硬件实现高效的特点。其核心思想是将输入数据(数据包字节流)与一个预先定义的随机比特序列(即 RSS Key)进行点积运算,生成一个 32 位哈希值。
算法流程:
- 输入准备:从数据包中提取用于哈希的字段(如 IP 源/目的地址、TCP/UDP 源/目的端口),拼接成一个连续的字节流。
- 密钥扩展:将 40 字节的 RSS Key 视为一个 320 位的随机序列。
- 逐位计算:对于输入字节流的每一位,与 RSS Key 的对应位进行按位与(AND)操作,然后对所有结果进行异或(XOR)求和。
- 结果输出:最终得到一个 32 位的哈希值,用于后续的重定向表索引。
rte_eth_dev_rss_hash_conf_get() 获取当前使用的哈希算法。
3.2 对称哈希(Symmetric RSS Hash)
为了解决 Toeplitz 哈希的非对称性问题,对称哈希(也称为 Consistent Hash 或 Flow Hash)被引入。对称哈希保证对于同一个 TCP/UDP 连接的双向数据包,计算出的哈希值完全相同。这确保了双向流量被分发到同一个 RX 队列(进而由同一个 CPU 核心处理),避免了跨核数据同步和锁竞争。
实现原理:
- 输入排序:在计算哈希之前,将源地址与目的地址、源端口与目的端口分别进行排序(例如,将较小的地址/端口放在前面)。
- 哈希计算:对排序后的输入应用 Toeplitz 或自定义哈希函数。
- 硬件支持:部分现代网卡(如 Intel X710/XL710、Mellanox ConnectX-5 及以上)在硬件层面支持对称哈希模式,通过配置寄存器启用。
| 特性 | Toeplitz 哈希 | 对称哈希 |
|---|---|---|
| 双向流一致性 | 不一致 | 一致 |
| CPU 亲和性 | 双向可能跨核 | 双向绑定同核 |
| 适用场景 | 通用负载均衡 | 有状态服务、TCP 优化 |
| 硬件复杂度 | 低 | 中(需排序逻辑) |
| DPDK 配置 | 默认 | 需设置 RTE_ETH_RSS_LEVEL 或网卡私有参数 |
3.3 哈希键(RSS Key)的配置与作用
RSS Key 是一个 40 字节(320 位)的随机种子,直接决定了 Toeplitz 哈希的映射结果。不同的 RSS Key 会导致相同的五元组被映射到不同的哈希值,从而影响负载均衡的分布。DPDK 和网卡驱动允许用户自定义 RSS Key,以实现更精细的流量控制。
RSS Key 的作用:
- 负载均衡均匀性:一个设计良好的 RSS Key 应使哈希值在整个 32 位空间上均匀分布,避免哈希冲突导致的队列倾斜。
- 多租户隔离:在虚拟化场景中,可以为不同的 VM 或 VF 配置不同的 RSS Key,确保流量隔离。
- 安全防护:定期更换 RSS Key 可以防止攻击者通过构造特定五元组来集中攻击某个队列(即哈希碰撞攻击)。
DPDK 中配置 RSS Key 的步骤:
- 定义 Key:准备一个 40 字节的 uint8_t 数组。
- 配置哈希字段:通过
rte_eth_dev_rss_hash_conf_get()获取当前配置,修改rss_key和rss_key_len。 - 应用配置:调用
rte_eth_dev_rss_hash_update()将新 Key 写入网卡硬件。 - 验证:使用 DPDK 的
testpmd工具或自定义程序验证哈希分布。
/* DPDK 中配置 RSS Key 的示例代码 */
#include <rte_ethdev.h>
/* 定义一个 40 字节的 RSS Key */
static uint8_t rss_key[40] = {
0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa
};
int configure_rss_key(uint16_t port_id) {
struct rte_eth_rss_conf rss_conf;
int ret;
/* 获取当前 RSS 配置 */
ret = rte_eth_dev_rss_hash_conf_get(port_id, &rss_conf);
if (ret != 0) {
printf("Failed to get RSS config: %d\n", ret);
return ret;
}
/* 设置新的 RSS Key */
rss_conf.rss_key = rss_key;
rss_conf.rss_key_len = 40;
/* 更新硬件配置 */
ret = rte_eth_dev_rss_hash_update(port_id, &rss_conf);
if (ret != 0) {
printf("Failed to update RSS key: %d\n", ret);
return ret;
}
printf("RSS Key updated successfully on port %u\n", port_id);
return 0;
}
rte_eth_dev_rss_hash_conf_get() 读取当前硬件使用的 Key,而不是假设默认值。不同网卡型号的默认 Key 可能不同。使用 testpmd 的 show port rss_hash_conf 0 命令可以快速查看当前配置。
3.4 哈希字段选择与重定向表
除了哈希算法和 Key,RSS 还允许用户选择参与哈希计算的字段(如仅使用 IP 地址、或包含端口号)。DPDK 通过 rte_eth_rss_conf 结构体中的 rss_hf(RSS Hash Functions)字段控制。
| 宏定义 | 参与哈希的字段 | 适用协议 |
|---|---|---|
RTE_ETH_RSS_IPV4 |
源 IP + 目的 IP | IPv4 |
RTE_ETH_RSS_IPV4_TCP |
IP 地址 + TCP 端口 | IPv4 TCP |
RTE_ETH_RSS_IPV4_UDP |
IP 地址 + UDP 端口 | IPv4 UDP |
RTE_ETH_RSS_IPV6 |
源 IP + 目的 IP | IPv6 |
RTE_ETH_RSS_IPV6_TCP |
IP 地址 + TCP 端口 | IPv6 TCP |
RTE_ETH_RSS_IPV6_UDP |
IP 地址 + UDP 端口 | IPv6 UDP |
重定向表(Redirection Table):哈希值计算完成后,网卡使用哈希值的低 N 位(N 取决于队列数量,通常为 7 位,支持 128 个队列)作为索引,查询重定向表,得到最终的队列编号。重定向表由驱动初始化,用户可以通过 rte_eth_dev_rss_reta_update() 修改。
1. 网卡硬件支持的哈希算法(Toeplitz 是否必须?是否支持对称哈希?)。
2. RSS Key 的字节序(大端/小端)是否与 CPU 一致。
3. 重定向表的大小(128 项还是 512 项?)和更新方式(全量更新还是增量更新?)。