四层负载均衡详解: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汇聚导致热点、扩缩容影响 |
一个经验之谈:选算法不要只看名字。先分析你的请求特征:连接时长、处理耗时、后端异构程度。我一般会先用轮询跑基线,然后在压测中用最少连接和源地址哈希各跑一遍,看实际延迟和负载分布,最后挑综合最稳的。
调度算法流程总览
下面这张图把三种算法的核心决策路径画在了一起。你看到请求到负载均衡器后,算法决定轮到谁,最终发给后端。我把它做成了一个纵向流程图,便于对照。
关键要点:
- 负载均衡器只做包转发和调度决策,不参与应用层交互。
- 轮询和最少连接是无状态算法,适合短连接和高性能后端。
- 源地址哈希是有状态算法,但要注意NAT环境下的热点问题。
- 所有算法都可以结合权重(weight)使用,让不同配置的服务器承担不同比例的流量。
选型的避坑指南
我曾经在一个跨国游戏项目中,因为选了最低延迟(least latency)算法导致全球节点负载剧烈抖动。后来发现最低延迟结合了加权平均,网络抖动就让后端分数突变,请求疯狂切换。最后老老实实换成加权轮询加会话保持,世界安静了。
所以我的建议是:
- 先明确你的需求——是要吞吐量优先,还是连接稳定性优先?
- 看后端业务类型——短请求(API)用轮询+加权;长连接(数据库)用最少连接;有会话粘滞需求(购物车)用源地址哈希或cookie保持。
- 混搭也是策略——比如Nginx Stream模块支持先源地址哈希,失败时回退到轮询,保证高可用。
⚠️ 注意:不要在生产环境没有任何压测数据支撑下,随意把负载均衡算法改成一个你只在文档里看过的“高级算法”。我见过不止一个团队把maglev哈希当银弹,结果因为服务器权重配置失误,把流量全打给了同一台机器。
好了,这一节我们聊了四层负载均衡的代理原理和三种核心调度算法。你大概能感受到,没有完美的算法,只有匹配场景的选择。下回你搭建流量入口时,记着拿这几种算法试试,再结合监控数据做决定,基本不会出大篓子。