四层负载均衡详解:TCP/UDP代理、源地址哈希、轮询、最少连接算法

说实话,四层负载均衡是整个流量入口体系里最基础但也最容易翻车的一层。我前几年给一个电商平台做架构的时候,就因为对调度算法的理解不够深,压测时发现后端服务器负载严重倾斜,最后连夜改了调度策略才稳住场面。

四层负载均衡处理的是传输层(TCP/UDP)的数据包,它不关心应用层的内容,只看源IP、源端口、目标IP、目标端口和协议号这五元组。说白了就是替客户端转发数据包,让后端服务器集群看起来像一台机器。

TCP/UDP代理的工作机制

四层代理有两种常见工作模式:

  • NAT模式:负载均衡器修改数据包的目的IP,转发给后端,响应再改回源IP返回客户端。我早期用LVS的DR模式比较多,但NAT模式在流量大的时候容易把入口带宽吃满。
  • Full NAT(SNAT + DNAT):不仅改目的IP,还改源IP为负载均衡器自身地址。这样后端看到的源IP是均衡器的,记录不到真实客户端IP。嗯,这算是个小坑,需要加TCP option或者用Proxy Protocol来传递客户端地址。

为什么要区分这两种?你在做架构选型时得权衡:NAT模式性能略高但路由复杂,Full NAT部署简单但丢了真实源IP。我个人习惯是在K8s环境里用eBPF+XDP直接绕过内核协议栈,但那又是另一个话题了。

三种主流调度算法

我用过一个很形象的比喻:调度算法就是餐厅门口的叫号员。他得决定下一个客人(请求)去哪个厨师(后端)的窗口。叫得不好,有人撑死、有人闲死。

轮询(Round Robin)

最简单的算法,不带任何权重,把请求挨个分配给后端,循环往复。你看它很公平对吧?但注意——它只认请求数量,不认请求难度。如果后端处理时间差异很大,比如有的请求1ms,有的要10s,轮询反而会让重请求堆积在某台机器上。

轮询怎么配? 看一段HAProxy的片段:

frontend web-frontend
    bind *:80
    default_backend web-backend
    
backend web-backend
    balance roundrobin          # 轮询调度
    server web1 192.168.1.10:80 check
    server web2 192.168.1.11:80 check
    server web3 192.168.1.12:80 check

这里balance roundrobin就是启用轮询。一两年我就被坑过一次——后端机器配置不一样,轮询照样把每个新请求均匀分下去,结果高配机器闲得慌、低配机器忙到爆。

最少连接(Least Connections)

它看的是后端当前活跃连接数,每次挑连接最少的那个。这个算法对长连接场景特别有用。比如WebSocket服务、数据库连接池,这些连接会持续一段时间,单纯轮询容易把连接集中在某几台机器。

同样HAProxy配置:

backend mysql-backend
    balance leastconn          # 最少连接
    option tcpka
    server db1 192.168.1.20:3306 check
    server db2 192.168.1.21:3306 check

不过你得注意:最少连接依赖连接计数器,如果后端处理极快(比如短请求秒级完成),这个算法反而因为频繁计算开销,不如轮询稳定。我曾经在压测时发现高并发短连接场景下,最少连接比轮询的吞吐低了5%左右,因为每次调度都要查连接数,维护这些结构本身消耗了CPU。

源地址哈希(Source IP Hash)

这个算法根据客户端的源IP计算哈希值,然后映射到后端服务器。同一个IP永远会落到同一台后端。这对有状态服务特别友好——会话不需要共享存储了,因为请求永远去找同一台机器。

配置示例:

backend app-backend
    balance source              # 源地址哈希
    hash-type consistent         # 一致性哈希(避免扩缩容大量重映射)
    server app1 192.168.1.30:8080 check
    server app2 192.168.1.31:8080 check
    server app3 192.168.1.32:8080 check

这里有讲究:hash-type consistent 启用一致性哈希,当新增或移除后端服务器时,只有少量请求需要重映射;否则用普通哈希,一扩缩容几乎所有请求都会跳到别的后端,造成缓存大量失效。我用Nginx Stream模块做过类似实现,那个叫hash $remote_addr consistent。

不过源地址哈希也有硬伤:如果同一个NAT网关后面的所有客户端请求,会集中到一台后端,造成单机热点。我在一个CDN边缘节点上就遇到过——公网出口IP只有几个,哈希后所有用户流量全压在一台机器上,其他机器闲置。后来改用了source + header组合哈希,或者加一层随机化前缀。

算法对比速查

算法 核心依据 适合场景 主要风险
轮询 请求数量均分 后端性能一致、处理时间接近 后端差异大时负载不均
最少连接 活跃连接数 长连接、慢处理服务 高并发短连接下CPU开销大
源地址哈希 源IP 有状态服务、会话保持 NAT汇聚导致热点、扩缩容影响

一个经验之谈:选算法不要只看名字。先分析你的请求特征:连接时长、处理耗时、后端异构程度。我一般会先用轮询跑基线,然后在压测中用最少连接和源地址哈希各跑一遍,看实际延迟和负载分布,最后挑综合最稳的。

调度算法流程总览

下面这张图把三种算法的核心决策路径画在了一起。你看到请求到负载均衡器后,算法决定轮到谁,最终发给后端。我把它做成了一个纵向流程图,便于对照。

四层负载均衡调度算法流程 四层负载均衡调度算法流程 客户端请求 四层负载均衡器 轮询 (Round Robin) 按序分配,循环 最少连接 (LeastConn) 选连接数最少的 源地址哈希 (Source) 源IP取模或一致性哈希 后端服务器池 Server 1 ── Server 2 ── Server 3 (实际池中机器数量不限) 响应返回客户端 说明 三种算法独立分支, 实际配置仅启用一种。

关键要点:

  • 负载均衡器只做包转发和调度决策,不参与应用层交互。
  • 轮询和最少连接是无状态算法,适合短连接和高性能后端。
  • 源地址哈希是有状态算法,但要注意NAT环境下的热点问题。
  • 所有算法都可以结合权重(weight)使用,让不同配置的服务器承担不同比例的流量。

选型的避坑指南

我曾经在一个跨国游戏项目中,因为选了最低延迟(least latency)算法导致全球节点负载剧烈抖动。后来发现最低延迟结合了加权平均,网络抖动就让后端分数突变,请求疯狂切换。最后老老实实换成加权轮询加会话保持,世界安静了。

所以我的建议是:

  • 先明确你的需求——是要吞吐量优先,还是连接稳定性优先?
  • 看后端业务类型——短请求(API)用轮询+加权;长连接(数据库)用最少连接;有会话粘滞需求(购物车)用源地址哈希或cookie保持。
  • 混搭也是策略——比如Nginx Stream模块支持先源地址哈希,失败时回退到轮询,保证高可用。

⚠️ 注意:不要在生产环境没有任何压测数据支撑下,随意把负载均衡算法改成一个你只在文档里看过的“高级算法”。我见过不止一个团队把maglev哈希当银弹,结果因为服务器权重配置失误,把流量全打给了同一台机器。

好了,这一节我们聊了四层负载均衡的代理原理和三种核心调度算法。你大概能感受到,没有完美的算法,只有匹配场景的选择。下回你搭建流量入口时,记着拿这几种算法试试,再结合监控数据做决定,基本不会出大篓子。