# 4.3 四层负载均衡 现在所说的“四层负载均衡”其实是多种均衡器工作模式的统称,“四层”的意思是这些工作的模式共同特点是维持同一个 TCP 连接,而不是说它只工作在 OSI 模型的四层。 事实上,这些模式主要工作在第二层(数据链路层,改写 MAC 地址)和第三层(网络层,改写 IP 地址),而第四层(传输层,改写 UDP、TCP 等协议的内容端口号)单纯只处理数据,做 NAT 之类的功能,所以无法做到负载均衡的转发。因为 OSI 模型的下三层是媒体层(Media Layer),上四层是主机层(Host Layer),既然流量已经到了目标主机了,也谈不上什么流量转发,最多只能做代理,但出于习惯,现在所有的资料都把它们称为四层负载均衡,笔者也继续沿用这种惯例,不论是 IP 层处理还是链路层处理,统称四层负载均衡。 # 4.3.1 四层负载均衡工作模式 四层负载均衡器最典型的软件实现是 LVS(Linux Virtual Server,Linux 虚拟服务器),但笔者并不过多介绍 LVS 的相关特点和如何使用,而是从网络数据包转发的角度去解释 LVS 中 DR、NAT、Tunnel 模式是如何工作的。 ## 1. 链路层负载均衡 LVS 的 DR 模式其实是一种链路层负载均衡,数据链路层负载均衡所做的工作,就是修改请求的数据帧中的 MAC 目标地址,让原本发送给负载均衡器请求的数据帧,被二层交换机转发至服务器集群中对应的服务器,这样真实的服务器就获得了一个原本目标并不是发送给它的数据帧。由于链路层只修改了 MAC 地址,所以在 IP 层看来数据没有任何变化,继而被正常接收。由于 IP 数据包中包含了源(客户端)和目的地(负载均衡器)IP 地址,只有真实服务器保证自己的 IP 与 数据包中的目的地 IP 地址一致,这个数据局才能继续被处理。 因此使用这种负载均衡模式时,需要把真实服务器的 VIP 配置成和负载均衡器的 VIP 一样,LVS 配置真实服务器的原理也是如此,如下示例。 ```plain // 真实服务器配置 ifconfig lo:0 1.1.1.1 netmask 255.255.255.255 up // 绑定 VIP 地址在 lo 接口上才能收到目标地址为 VIP 的包。 route add -host 1.1.1.1 dev lo // 目标地址是 VIP 的数据包从本机 lo 接口发送出去 ``` 只有请求经过负载均衡器,而服务的响应无需从负载均衡器原路返回的工作模式中,整个请求、转发、响应的链路形成一个“三角关系”,所以这种模式也被形象的称为“三角传输模式”(Direct Server Return,DSR),也称为“单臂模式”(Single Legged Model),如图 4-2 所示。 <div align="center"> <img src="https://wiki.waringid.me/download/attachments/5636343/balancer4-dsr.svg" width = "550" align=center /> <p>图4-2 链路层 DSA 模式负载均衡</p> </div> 设计 DSR 的主要原因是:在一些场景中,响应的流量要远远大于请求的流量(例如典型的 HTTP request/response 模式)。假设请求占 10% 的流量,响应占 90%,使用 DSR 技术,只需 1/10 的带宽就可以满足系统需求,这种类型的优化可以极大地节省成本,还提高了负载均衡器的可靠性(流量越低肯定越好)。 虽然 DSR 模式效率很高,但它在缺陷也很明显,因为响应不再经过负载均衡器,负载均衡器就无法知道 TCP 连接的完整状态,防火墙策略会收到影响(TCP 连接只有 SYN,没有 ACK)。其次因为是数据链路层通信,受到的网络侧约束也很大。所以优势(效率高)和劣势(不能跨子网)共同决定了 DSR 模式最适合作为数据中心的第一层均衡设备,用来连接其他的下级负载均衡器。 ## 2. 网络层负载均衡 网络层负载均衡操作的是 IP 数据包,我们先对 IP 数据包有个简单的了解。以 IP 协议为例,一个 IP 数据包由头部(Header)和载荷(Payload)组成,Header 内部包含版本、源地址、目的地地址等信息,结构如图所示。 <div align="center"> <img src="https://wiki.waringid.me/download/attachments/5636343/ip.svg" width = "350" align=center /> <p>图4-2 ip 数据包结构</p> </div> 在本文,我们无需太关注 Header 头部信息,只要知道 Header 内有源地址(Source address)和目的地地址(Destination address)即可。既然数据链层负载均衡可以通过改写 MAC 地址来实现数据包转发,到了网络层,我们也可以继续沿用改写 MAC 相似的思路修改网络层的 IP 数据包地址信息来实现数据转发。 LVS 的 Tunnel、NAT 模式都属于网络层负载均衡,只不过因为对 IP 数据包的不同形式修改而分成两种,我们先来看第一种修改方式。 ### 2.1 IP 隧道模式 第一种保持源数据包不变,新建一个 IP 数据包,将原来的 IP 数据包整体放进新 IP 数据包的 Payload 内,再通过三层交换机发送出去,真实服务器收到包之后,有一个对应拆包的机制,把负载均衡器自动添加的那层 Header 删掉,解析出 Payload 内部的 IP 数据包再进行正常处理。把一个数据包封装在另一个数据包内,其实就是一种隧道技术,比如 Linux 中的隧道技术 ipip 就是字面 IP in IP 的意思。由于 IP 隧道工作在网络层,因此摆脱了直接路由模式的网络约束,所以 LVS Tunnel 模式可以跨越 VLAN 。 由于没有修改源数据包的任何信息,所以 IP 隧道模式仍具有三角传输模式的特点,即负载均衡转发器转发进来的请求,真实服务器去响应请求,IP 隧道模式从请求到响应的过程如图所示。 <div align="center"> <img src="https://wiki.waringid.me/download/attachments/5636343/balancer4-tunnel.svg" width = "550" align=center /> <p>图4-2 tunnel 模式</p> </div> IP 隧道模式相当于 DR 模式的升级(支持了跨网),不过由于使用隧道模式,所以要求真实服务器支持隧道协议,真实服务器只局限在部分 Linux 系统上(不过笔者也没见过四层负载均衡使用非 Linux 系统的案例);其次,只要是三角模式(LVS 的 DR 模式或者 Tunnel 模式)必须要保证真实服务器与负载均衡服务器有相同的虚拟 IP 地址,因为回复客户端时,必须使用这个虚拟的 IP 作为数据包的源地址,这样客户端收到数据包之后才能正常解析;最后,因为真实服务器和客户端对接,所以真实服务器得能访问外网。 ### 2.2 NAT 模式 IP 数据包的另外一种改写方式是直接改变 IP 数据包的 Header 内的目的地(Destination address)地址(改为真实服务器的地址),修改之后原本用户发送给负载均衡器的数据包会被三层交换机转发至真实服务器的网卡上。这么解释似乎不太好理解,但相信每一个读者都配置过这种原理的操作(配置路由器),这种模式和一台路由器带着一群内网机器上网的“网络地址转换”(Network Address Translation)一个原理。因此,这种负载均衡的模式被称为 NAT 模式,此模式请求到到响应的过程如图所示。 <div align="center"> <img src="https://wiki.waringid.me/download/attachments/5636343/balancer4-NAT.svg" width = "550" align=center /> <p>图4-2 NAT 模式负载均衡</p> </div> 这种类型中,TCP 连接在负载均衡器建立连接跟踪和网络地址转换(NAT)之后直接转发给选中的后端。例如,假设客户端正在和负载均衡器 1.1.1.1:80 通信,选中的后端是 10.0.0.2:8080。当客户端的 TCP 包到达负载均衡器时,负载均衡器会将包的目的 IP/port (从 1.1.1.1:80)换成 10.0.0.2:8080,以及将源 IP/port 换成负载均衡器自己的 IP/port。当应答包回来的时候,负载均衡器再做相反的转换。 不过 NAT 模式的缺陷也显而易见,因为负载均衡器代表整个服务集群接收/应答,当流量压力比较大的时候,整个系统的瓶颈就很容易出现在负载均衡器上。 # 4.3.2 四层负载均衡高可用设计 到目前为止,我们讨论的都是单个四层负载均衡器的设计以及工作模式,假如这个负载均衡器在生产环境中宕机了呢?事实上,四层负载均衡器大都作为大型分布式系统的唯一对外入口,如果四层负载均衡器是单节点部署,一旦宕机,对于整个系统而言绝对是毁灭级别的故障。 载均衡器高可用设计有多种方案,我们先来看目前最常见(也是逐渐被淘汰的)的**主备方式**。如图所示,这种高可用方案典型的设计是一对主备边缘路由器提供若干 VIP(virtual IP,虚拟 IP),并通过 BGP 协议通告 VIP,主边缘路由器的 BGP 权重比备边缘路由器的高,正常情况下处理所有流量;类似地,主四层负载均衡器向边缘路由器宣告它的权重比备用四层负载均衡器大,正常情况下主四层负载均衡器处理所有流量;主备四层负载均衡器之间交叉连接, 共享所有的连接跟踪状态,假如主挂了,备可以马上接管所有活动连接。 <div align="center"> <img src="https://wiki.waringid.me/download/attachments/5636343/balancer-ha.svg" width = "600" align=center /> <p>图4-4 负载均衡主备设计</p> </div> 主备方式的方案在现今的分布式系统中仍然有大量应用,但这种方式存在明显的缺陷。主备方式平稳状态下 50% 的容量是空闲的,备用服务器一直空转,**资源利用率不足**。其次,现代分布式系统设计**一直追求更高的容错性**。例如,理想情况下一个系统有多个实例同时挂掉仍能继续运行,而主备实例同时挂掉时,服务就彻底挂了。 接下来我们再看**基于集群的一致性哈希容错和可扩展设计方案**。它的工作原理如图所示。 <div align="center"> <img src="https://wiki.waringid.me/download/attachments/5636343/balancer-ha-2.svg" width = "650" align=center /> <p>图4-4 负载均衡高可用设计</p> </div> - 多个边缘路由器以相同的 BGP 权重通告所有 Anycast VIP,通过 ECMP(Equal-cost, Multi-path routing)保证每个 flow 的所有包都会到达同一个边缘路由器。 - 多个四层负载均起以相同的 BGP 权重向所有的边缘路由器通告所有的 VIP 继续使用 ECMP 的方式为相同 flow 的包选择相同的四层负载均衡器。 - 每个四层负载均衡器实例会做部分连接跟踪(connection tracking)工作,然后使用一致性哈希为每个 flow 选择 一个后端。通过 GRE 封装将包从负载均衡器发送到后端。 - 然后使用 DSR 将应答包从后端直接发送到边缘路由器,最后到客户端。 我们可以看到以上的设计如何避免主备方式的不足:边缘路由器和负载均衡器实例可以按需添加。因为每一层都用到了 ECMP,当新实例加入的时候,能最大程度地减少受影响的 flow 数量;在预留足够的突发量和容错的前提下,系统的资源利用率想达到多高就可以到多高。 各类云商中的 SLB,以及绝大部分的现代四层负载均衡系统都在朝着这种设计演进。 # 4.3.3 四层负载均衡小结 典型情况下,四层负载均衡器只工作在传输层 TCP/UDP connection/session 中,不论采用何种的负载均衡算法,都是力求同一 session 的字节永远落到同一后端。由于 L4LB 不感知其转发字节所属应用的任何细节,这些字节可能是 HTTP、Redis、MongoDB,或者 任何其他应用层协议,所以四层负载均衡器的应用范围非常广。 四层负载均衡的的特点也是其缺点,设想如下特殊场景: 1. 两个 gRPC/HTTP2 客户端们通过 L4LB 建立连接到后端。 2. L4LB 为每个进来(从客户端)的连接建立一个出去的(到后端)的连接,因此有两个进来的连接和两个出去的连接。 3. 客户端 A 的连接每分钟发送 1 个请求,而客户端 B 的连接每秒发送 50 个请求。 由于四层负载均衡器的同一个 session 的字节永远落到同一后端,选中的处理客户端 A 请求的后端比选中的处理客户端 B 请求的后端负载要相差很多倍,这就与负载均衡的目的背道而驰。由于性能考虑(创建 TCP 连接的开销非常大,尤其连接使用 TLS 加密的时候),以及所有现代协议都在演进以支持 multiplexing(多路复用) 和 kept-alive(连接保活),因此四层负载均衡器的阻抗不匹配问题随时间越来越彰显,不过这个问题在四层负载均衡器之后再加一级七层负载均衡器就能得到解决。 |