文章目录
- 前言
- 一、Linux Bonding驱动底层架构简述
- 二、Hash Policy
- 三、 策略解析(layer2 / layer2+3 / layer3+4)
- 1.layer2
- 2.layer2+3
- 3.layer3+4
- 四、 底层实现细节(以Kernel源码为例)
- 总结
前言
今天同事在部署环境的时候遇到了一个奇怪的问题:
两台服务器 A, B。操作系统是银河麒麟,过交换机做LACP bond mode4链路聚合,使用的默认layer2哈希策略,LACP已协商成功,聚合状态为UP。但是通过iperf3多并发测试流量长期集中在一条物理链路。
于是针对这个场景展开了排查最终锁定在xmit_hash_policy
参考资料:
1.https://access.redhat.com/solutions/71883
2.https://www.kernel.org/doc/Documentation/networking/bonding.txt
3.https://docs.redhat.com/zh-cn/documentation/red_hat_enterprise_linux/9/html/configuring_and_managing_networking/ref_xmit-hash-policy-bonding-parameter_configuring-network-bonding
4.https://github.com/torvalds/linux/blob/master/drivers/net/bonding/bond_main.c#L3376
一、Linux Bonding驱动底层架构简述
Linux Bonding机制本质上是一种内核模块,它将多个slave interface封装到一个bond interface上,使其对外表现为单个逻辑网卡,从而实现:链路冗余,负载分担,带宽聚合
Bonding驱动的行为主要受两个核心配置影响:
- mode(工作模式):定义负载/冗余策略,例如 active-backup、802.3ad、balance-xor 等;
- xmit_hash_policy(散列策略):控制在某些负载均衡模式下(如 balance-xor 和 802.3ad)如何选择发送接口。
二、Hash Policy
在聚合多个物理链路时,最难处理的问题之一是:
如何公平、稳定地把“多个连接/流量”分配到多个链路上?
如果没有合适的散列策略:
- 相同源/目的 的多个包可能全部走同一物理链路
- 导致链路利用不均衡;
- 对某些模式下的连接性能造成瓶颈。
Linux Bonding的设计者引入了一个散列算法(Hash Algorithm),用于对数据包的部分头部字段进行组合计算,再根据结果映射到具体的物理端口。这个过程就是xmit_hash_policy的核心任务。
三、 策略解析(layer2 / layer2+3 / layer3+4)
在802.3ad(LACP)或balance-xor这类模式下,Bonding 必须决定每一个流到底从哪一块物理网卡发出去。它不会随机发,而是做一个 hash(散列):
hash(报文头的一些字段) → 得到一个数 → 对链路数量取模 → 选定某条物理口发送
这样做的目的有两个:
- 稳定性:同一个流始终走同一条链路,避免乱序。
- 负载分担:不同的流尽量分散到不同链路。
xmit_hash_policy决定的就是hash输入用哪些字段。字段用得越多,区分“流”的粒度越细,但也更依赖报文类型、可能更容易碰到边界情况。
举个例子,类似于快递分拣的场景:
layer2相当于只看发件地址和收件人地址,所有寄到A小区的包裹全部走A通道,寄到B小区的包裹全部走B通道,好处是包裹外箱子印着小区名好分辨,大的小的跨国的都能寄,坏处是万一双十一A小区快递爆满了A通道堵死了B通道还闲着
layer2+3相当于 收件人小区 + 收件人手机号 比如A小区+手机号123走A通道 A小区+手机号987走B通道,优点是同一小区不同人可以分到不同通道,比layer2更均衡。但是缺点是需要在包裹上贴手机号(相当于是IP),如果前台代收(所有的包裹写同一个手机号)还是会挤到一起,没手机号的匿名包裹会随机分配通道
layer3+4相当于 收件人手机号+订单编号 比如手机号123+订单#2026-1(买的手机)走A通道,123+#2026-2(买的耳机)走B通道,123+#2026-3(买的电脑)走C通道。这样的优点是同一个人的多个订单可以完全分开,3 条通道全跑满,负载最均衡。缺点是必须每个包裹都写清楚订单号(相当于 TCP/UDP 端口),如果大件家具拆成3箱:只有第一箱有订单号,后两箱只写续件那么分拣系统看不懂可能会乱分,如果没订单号、没手机号也会乱分,如果标签是外语格式或者扫描仪解析失败,那么会默认走某一条,可能造成倾斜
1.layer2
layer2用MAC来分流(最粗粒度)
用的字段是源 MAC(src MAC)和目的 MAC(dst MAC)
所以对于layer2来说同一对 MAC 地址 = 同一条流
这就会导致,如果你的服务器几乎所有流量都发往同一个网关/同一台对端设备,那么:
- 目的 MAC 基本固定(比如都是网关 MAC)
- 源 MAC 也固定(bond 的 MAC 或某块口的 MAC)
- hash 输入几乎不变 → 结果不变 → 很可能长期只跑一条链路
场景:服务器 A(bond0)访问数据库网关 G(同一个网关):
A 的 src MAC:固定
G 的 dst MAC:固定
不管开 10 个TCP连接还是1000个连接,在layer2下它们看起来都属于同一个二层对话 → 可能一直走同一条物理口。
优点:最稳,最简单,兼容性最好
缺点:在对端 MAC 很集中的场景下,负载很难均匀
2.layer2+3
layer2+3用的是MAC + IP来分流
用的字段是layer2的MAC对再加上:源IP和目的IP
同一对IP地址(在同一对 MAC 的基础上)更容易被区分
可以理解为:
layer2只能区分我跟哪个设备说话
layer2+3能进一步区分我跟哪个IP说话
这样当服务器面对的是很多不同的客户端IP/不同的后端IP时,负载会明显更均匀。但如果你的业务本来就是所有连接都打到同一个VIP/同一个数据库 IP”,它仍然可能集中,因为dst IP也固定
场景 :很多客户端访问
1000个客户端IP访问服务(dst IP 是你的服务,src IP 各不相同)
这样hash输入变化很大,更容易把不同客户端的流分散到不同链路
优点:在多数数据中心场景里,是既稳又均衡的最佳折中
缺点:如果通信本来就是固定对固定(同一对 IP)则提升有限
3.layer3+4
layer3+4用的是IP+端口来分流
用的字段是源IP/目的IP和源端口/目的端口
也就是同一对IP不够,还要看端口
可以理解为:
layer2+3区分是主机对主机,
layer3+4是进一步区分会话对会话。
如果有大量短连接(比如 HTTP/微服务调用),每个连接端口都不同hash输入差异大,能把连接更细粒度地分散到多条链路,但对于少量长连接,一个连接的4元组(srcIP, dstIP, srcPort, dstPort)是固定的,固定这个连接会一直走同一条链路
| 策略 | hash 看什么 | 能把哪些流区分开 | 最适合的场景 |
|---|---|---|---|
| layer2 | MAC | 设备对设备 | 简单、稳定、对端不集中的场景 |
| layer2+3 | MAC + IP | 主机对主机 | 数据中心最常用折中方案 |
| layer3+4 | IP + 端口 | 会话对会话 | 高并发、多短连接、多端口 |
四、 底层实现细节(以Kernel源码为例)
在bonding驱动源码中,可以看到xmit_hash_policy的枚举和计算逻辑:
| policy | 计算字段 | 是否802.3ad | 说明 |
|---|---|---|---|
| layer2 | MAC | 是 | 基于 MAC 驱动的最简单策略 |
| layer2+3 | MAC+IP | 是 | 推荐散列策略 |
| layer3+4 | IP+端口 | 否 | 更精细但不严格 802.3ad |
内部实现考虑了:
- IPv4/IPv6
- TCP/UDP vs 其他协议
- 分片/重组行为
当 hash 值计算完成,它会通过:
slave_index=hash%slave_count;将流映射到具体的物理端口。
实践中的权衡选择指南
| 场景 | 推荐散列策略 | 原因 |
|---|---|---|
| 标准 LACP + 交换机聚合 | layer2 或 layer2+3 | 兼容 802.3ad 标准 |
| 大量短连接 | layer2+3 | 更均衡分发 |
| 多端口多协议通信 | layer3+4 | 分发更细粒度流 |
| 高要求链路顺序 | layer2 / layer2+3 | 避免分片顺序不一致 |
总结
简单说,Bonding的负载均衡不是把一个连接拆成两路跑,而是给每一条流连接选一条出口,xmit_hash_policy做的事也很直白,他是决定用哪些信息来算这个出口,从而把不同的流尽量分散到不同链路上。所以选对策略才能更高效地利用聚合带宽。