4. 基于流的负载均衡:流的概念、流表建立、流老化机制、流哈希冲突处理

各位好,今天我们来聊聊基于流的负载均衡。说实话,这个主题在交换架构里属于「看着简单,做起来坑多」的那种。我早年刚接触数据中心交换机时,觉得不就是把流量分一分嘛,后来被线上问题教育了几次,才真正理解这里面的门道。

4.1 什么是「流」?

先说说流的概念。你想想看,网络里数据包成千上万,但很多包其实属于同一个「对话」。比如你打开一个网页,从TCP三次握手到HTTP请求响应,这中间几十个包,它们应该走同一条路径,对吧?

流,就是一组具有相同五元组(源IP、目的IP、源端口、目的端口、协议号)的数据包序列。说白了,就是一次「会话」的所有包。

核心要点:基于流的负载均衡,就是把同一个流的包始终发到同一个后端,不同流的包可以分散到不同后端。这样既保证会话一致性,又能充分利用多路径带宽。

我在项目中遇到过一种情况:某个客户抱怨他们的视频会议经常卡顿。查了半天,发现是负载均衡把同一个RTP流的包分到了两条链路上,导致接收端重组乱序。嗯,这就是典型的「没按流做哈希」的后果。

4.2 流表建立:第一次见到你,我就记住了

流表是怎么建立的?其实很简单——懒加载

当一个数据包到达交换机时,交换芯片会先查流表。如果没找到匹配项,就说明这是一个新流。这时候芯片会做两件事:

  1. 计算哈希:根据五元组算出一个哈希值
  2. 写入流表:把哈希值和对应的出接口信息存下来

后续同一个流的包再来,直接查表命中,走固定路径。这就是所谓的「首包建流」机制。

小技巧:我建议在流表项里额外存一个「命中计数器」。这样你可以知道哪些流是热点,哪些流已经很久没人用了。对后续的流老化很有帮助。

代码层面,流表通常用哈希表实现。我见过一个比较简洁的流表结构:

struct flow_entry {
    uint32_t hash_key;        // 五元组哈希值
    uint8_t  src_ip[4];       // 源IP(可选,用于冲突检测)
    uint8_t  dst_ip[4];       // 目的IP
    uint16_t src_port;        // 源端口
    uint16_t dst_port;        // 目的端口
    uint8_t  protocol;        // 协议号
    uint8_t  out_iface;       // 出接口
    uint32_t hit_count;       // 命中次数
    uint32_t timestamp;       // 最后命中时间
    struct flow_entry *next;  // 链表指针(处理哈希冲突)
};

4.3 流老化机制:该忘就忘,别死扛

流表不能无限增长。交换机内存有限,而且很多流是短连接,建了之后很快就结束了。如果不清理,流表迟早被撑爆。

流老化,说白了就是「把不活跃的流踢出去」。常见的策略有三种:

老化策略 原理 适用场景
空闲超时 流表项超过N秒无命中,就删除 通用场景,最常用
硬超时 流表项创建后最多存活T秒,不管有没有命中 长连接保活场景
LRU淘汰 流表满时,淘汰最近最少使用的项 内存紧张的高性能场景

我曾经踩过一个坑:某个业务用的是WebSocket长连接,空闲超时设成了30秒。结果用户经常莫名其妙断连。查了半天才发现,WebSocket空闲时没有数据包,流表项被老化了。后续包过来重新建流,哈希到了不同后端,会话就断了。

避坑指南:对于长连接业务,建议把空闲超时设大一些(比如300秒),或者干脆用硬超时。我曾经因为这个问题被运维同事追着骂了一下午……

4.4 流哈希冲突处理:撞车了怎么办?

哈希冲突是绕不开的问题。两个不同的流算出了相同的哈希值,怎么办?

常见的处理方式有这几种:

  • 链表法:同一个哈希桶里挂链表,冲突的流串在一起。实现简单,但链表长了性能会下降。
  • 开放寻址法:冲突了就往下一个空位放。适合流表比较稀疏的场景。
  • 多级哈希:准备多个哈希函数,第一个冲突了就换第二个。我在博通的一些芯片上见过这种做法。
  • Cuckoo哈希:冲突了就把原来的项踢走,让它去别的位置。吞吐量高,但实现复杂。

我个人习惯用链表法加「短链表优化」。什么意思呢?就是每个哈希桶的链表长度限制在4以内。超过4个,就认为这个桶太挤了,触发流表扩容或者强制老化一些流。这样能保证最坏情况下的查找性能。

关键点:哈希冲突处理不好,会导致「流极化」——多个流挤到同一个后端,其他后端闲着。负载均衡就名存实亡了。

嗯,这里还要提一句:哈希函数的选择也很重要。别用简单的异或或者加法,容易产生大量冲突。我推荐用CRC16或者MurmurHash,分布均匀,硬件实现也方便。

好了,关于基于流的负载均衡,核心就是这四个方面:流的概念、流表建立、流老化、哈希冲突处理。每个点都有不少细节,但抓住了主线,后面就好办了。


专注资料整理