什么是负载均衡
如今,生产环境中的应用服务几乎都以集群方式部署。无论是单体架构的多副本,还是微服务架构,也无论目的是高可用还是高性能,都需要借助多台机器来扩展服务能力,并期望用户的请求不论落到哪台机器上,都能得到相同的处理。
另一方面,集群的构建与调度又必须对用户保持透明:哪怕背后是一千台、一万台机器在共同响应,用户也只需要记住一个域名。负责调度后方多台机器、并以统一入口对外提供服务的技术组件,就是负载均衡(Load Balancing)。
真正的大型系统,其负载均衡往往是多级的。例如,在各地建有多个机房、或机房有多个网络入口的大型网站,会从 DNS 解析开始,沿着“域名 → CNAME → 负载调度服务 → 就近的数据中心入口”的路径,先根据客户端 IP(或其他条件)把用户分配到合适的数据中心,之后才进入数据中心内部的各级负载均衡。
四层与七层
无论内部分了多少级,从工作形式上看,负载均衡都可以归为四层负载均衡与七层负载均衡两类。这里的“层”指的是 OSI 七层模型:
| 层 | 名称 | 数据单元 | 功能 |
|---|---|---|---|
| 七 | 应用层(Application Layer) | 数据(Data) | 为应用软件提供服务的接口,用于与其他应用软件之间的通信。典型协议有 HTTP、HTTPS、Telnet、SSH、SMTP、POP3 等 |
| 六 | 表示层(Presentation Layer) | 数据(Data) | 把数据转换为能与接收者系统格式兼容、并适合传输的格式 |
| 五 | 会话层(Session Layer) | 数据(Data) | 负责在数据传输中设置和维护两台计算机之间的通信连接 |
| 四 | 传输层(Transport Layer) | 数据段(Segment) | 在数据前加上传输头部,其中包含所使用的协议等信息。典型协议有 TCP、UDP、RDP、SCTP、FCP 等 |
| 三 | 网络层(Network Layer) | 数据包(Packet) | 提供数据的路径选择和转发功能,将网络头部附加到数据段之前,形成数据包。典型协议有 IPv4/IPv6、IGMP、ICMP、EGP、RIP 等 |
| 二 | 数据链路层(Data Link Layer) | 数据帧(Frame) | 负责点对点的网络寻址、错误侦测和纠错。数据包加上头部和尾部后形成数据帧。典型协议有 Wi-Fi(802.11)、Ethernet(802.3)、PPP 等 |
| 一 | 物理层(Physical Layer) | 比特流(Bit) | 在局域网上传送比特流,负责管理通信设备与网络介质之间的互通,涉及针脚、电压、线缆规范、集线器、中继器、网卡、主机接口卡等 |
两类负载均衡各有所长,在多级混合使用时也有约定俗成的顺序:
- 四层负载均衡的优势是性能高,七层负载均衡的优势是功能强。
- 多级混合部署时,通常是低层的负载均衡在前,高层的在后。
需要说明的是,通常所说的“四层负载均衡”其实是多种工作模式的统称。“四层”指的是这些模式的共同特点——始终维持同一条 TCP 连接,并不是说它们只工作在第四层。事实上,它们主要工作在第二层(数据链路层,改写 MAC 地址)和第三层(网络层,改写 IP 地址)。
只处理第四层(传输层,改写 TCP、UDP 等协议的内容和端口)的数据,是无法完成负载均衡转发的:OSI 的下三层属于媒体层(Media Layer),上四层属于主机层(Host Layer),数据一旦到达主机层,流量已经抵达目标主机,也就谈不上“转发”,最多只能做“代理”。
数据链路层负载均衡
数据链路层传输的内容是数据帧(Frame),例如常见的以太网帧、ADSL 宽带的 PPP 帧等。按照 IEEE 802.3 标准,最典型的 1500 字节 MTU 的以太网帧结构如下:
| 数据项 | 取值 |
|---|---|
| 前导码 | 10101010,7 字节 |
| 帧开始符 | 10101011,1 字节 |
| MAC 目标地址 | 6 字节 |
| MAC 源地址 | 6 字节 |
| 802.1Q(可选) | 4 字节 |
| 以太类型 | 2 字节 |
| 有效负载 | 46~1500 字节 |
| 冗余校验 | 4 字节 |
| 帧间距 | 12 字节 |
每块网卡都有独立的 MAC 地址,交换机正是通过帧中的这两个地址,得知该帧从哪个端口的网卡发出、又应送往哪块网卡。
工作原理
数据链路层负载均衡所做的,是修改请求数据帧中的 MAC 目标地址:让原本发给负载均衡器的数据帧,被二层交换机按新的 MAC 目标地址转发到集群中某台服务器(后文称“真实服务器”,Real Server)的网卡上。这样,真实服务器就收到了一个“原本不是发给它”的数据帧。
由于二层负载均衡器只改了帧的 MAC 目标地址,没有触碰更上层的内容(Payload 未改动),所以在第三层看来,数据完全没有变化。IP 数据包中包含源(客户端)与目标(均衡器)的 IP 地址,只有真实服务器的 IP 与数据包中的目标 IP 一致,数据包才能被正确处理。因此,这种模式要求把集群中所有真实服务器的虚拟 IP 地址(Virtual IP Address,VIP)配置成与负载均衡器的 VIP 相同。
也正因为处理请求的真实服务器的 IP 与请求中的目标 IP 一致,响应就无需再经过负载均衡器做地址转换,而是可以由真实服务器直接返回给客户端。这避免了负载均衡器网卡带宽成为瓶颈,所以数据链路层负载均衡的效率非常高。
三角传输模式
在这种“只有请求经过负载均衡器,响应不需要原路返回”的模式中,请求、转发、响应的链路构成了一个三角形,因此被形象地称为三角传输模式(Direct Server Return,DSR),也叫单臂模式(Single Legged Mode)或直接路由(Direct Routing)。
优势与局限
数据链路层负载均衡效率很高,但并非适用于所有场合:
- 它无法用于需要感知应用层协议信息的场景;
- 它在网络上的约束也很大。直接改写目标 MAC 地址的原理,决定了它与真实服务器之间必须二层可达,通俗地说就是必须位于同一个子网,无法跨 VLAN。
“效率高”与“不能跨子网”这一对优劣势,决定了数据链路层负载均衡最适合充当数据中心的第一级均衡设备,再由它连接下一级的负载均衡器。
网络层负载均衡
按照 OSI 七层模型,网络层传输的单位是分组数据包(Packet),这是分组交换网络(Packet Switching Network,PSN)中传输的结构化数据单位。以 IP 协议为例,一个 IP 数据包由头部(Header)和载荷(Payload)两部分组成。Header 最长 60 字节,包括 20 字节的固定部分和最长 40 字节的可选部分。按照 IPv4 标准,Header 的典型结构如下:
| 长度 | 存储信息 |
|---|---|
| 1~4 字节 | 版本号(4 位)、头部长度(4 位)、区分服务(8 位)、总长度(16 位) |
| 5~8 字节 | 标识(16 位)、标志位(3 位)、片偏移(13 位) |
| 9~12 字节 | TTL 生存时间(8 位)、上层协议代号(8 位)、头部校验和(16 位) |
| 13~16 字节 | 源地址(32 位) |
| 17~20 字节 | 目标地址(32 位) |
| 21~60 字节 | 可选字段和填充 |
网络层负载均衡要把数据包送到真实服务器,有两种思路:在原数据包外再封装一层,或者直接修改原数据包的目标地址。
方式一:IP 隧道
保持原数据包不变,新建一个数据包,把原数据包的 Header 和 Payload 整体作为新包的 Payload,并在新包的 Header 中把真实服务器的 IP 写为目标地址,然后发送出去。数据包经三层交换机转发到真实服务器后,服务器必须在入口处有一套针对性的拆包机制,丢掉负载均衡器添加的那层 Header,还原出原数据包。这样,真实服务器同样拿到了一个原本目标 IP 不是它的数据包,达到了流量转发的目的。这种方式称为IP 隧道(IP Tunnel)。
虽然要多封装一层,IP 隧道的效率低于直接路由,但它没有修改原数据包的任何信息,因此仍然具备三角传输的特性:请求由负载均衡器转发,响应则由真实服务器直接返回客户端。并且由于工作在网络层,它可以跨 VLAN,摆脱了直接路由在网络侧的约束。
这种模式有两个缺点:
- 真实服务器必须支持“IP 隧道协议”(IP Encapsulation),也就是得学会自己拆包、丢掉一层 Header。不过现在几乎所有 Linux 系统都支持。
- 必须通过专门配置,保证所有真实服务器与负载均衡器拥有相同的虚拟 IP。因为回复数据包时,需要用这个 VIP 作为响应包的源地址,客户端才能正确解析。这与“透明”的原则相冲突,需要系统管理员介入。
方式二:NAT 模式
给服务器配置虚拟 IP 并非在任何情况下都可行,尤其是多个服务共用一台物理服务器的时候。此时就要考虑第二种方式:直接修改数据包 Header 中的目标地址。修改后,原本发给负载均衡器的数据包同样会被三层交换机转发到真实服务器的网卡上,而且没有额外包装,也就无需拆包。
问题在于,既然目标 IP 被改成了真实服务器的 IP,如果真实服务器直接把应答返回给客户端,应答包的源 IP 就是真实服务器的 IP,也就是均衡器改写之后的地址,客户端并不认识这个 IP,自然无法正常处理。因此,应答流量必须回到负载均衡器,由它把应答包的源 IP 改回自己的 IP,再发给客户端。
这和我们在家里、学校、公司上网时,由一台路由器带着一群内网机器上网的“网络地址转换”(Network Address Translation,NAT)如出一辙,所以这种模式就叫 NAT 模式,此时的负载均衡器就相当于上网路由器。NAT 模式的运维也很简单:只需把服务器的网关地址设置为均衡器的地址即可。
NAT 模式的代价是性能。流量压力较大时,它会带来明显的性能损失,与直接路由、IP 隧道相比甚至可能相差一个数量级:所有响应都由均衡器代表整个集群发出,会争抢均衡器的出口带宽,就像在家里用 NAT 上网时,有人下载、你打游戏就会卡顿一样,整个系统的瓶颈很容易出现在负载均衡器上。
更彻底的 NAT:SNAT
还有一种更彻底的 NAT 模式:负载均衡器在转发时,不仅修改目标 IP,还修改源 IP,把它改成负载均衡器自己的地址,称为 Source NAT(SNAT)。
好处是真实服务器无需配置网关,应答流量就能经由正常的三层路由回到负载均衡器,做到彻底透明。缺点是真实服务器拿不到客户端的真实 IP,在它看来所有流量都来自负载均衡器,因此依赖客户端 IP 的业务逻辑(如按来源 IP 做访问控制)就无法实现了。
应用层负载均衡
前面介绍的四层负载均衡模式都属于转发:直接把承载 TCP 报文的底层数据(IP 数据包或以太网帧)转发给真实服务器,客户端与响应请求的真实服务器之间维持着同一条 TCP 通道。而工作在四层之上的负载均衡就无法再转发,只能代理,此时客户端、负载均衡器、真实服务器三者之间由两条独立的 TCP 通道维持通信。
代理的三种类型
“代理”按照“哪一方能感知到”的原则,可以分为三类:
- 正向代理:即通常所说的“代理”,设置在客户端一侧,代表客户端与服务器通信。客户端知道它的存在,服务器则感知不到。
- 反向代理:设置在服务器一侧,代表真实服务器与客户端通信。对客户端透明。
- 透明代理:对双方都透明,通常配置在网络中间设备上,例如架设在路由器上的透明代理。
按此定义,七层负载均衡器属于反向代理。
七层负载均衡的取舍
单论网络性能,七层负载均衡器肯定不如四层:它至少多一轮 TCP 握手,存在与 NAT 模式相同的带宽问题,而且由于可用的解析规则远比四层丰富,通常还要消耗更多 CPU。因此,用它来支撑下载站、视频站这类流量型应用并不合适,至少不适合作为第一级均衡器。
但如果网站的瓶颈不在网络性能,而在整个服务集群对外体现出的服务能力,七层负载均衡器就有了用武之地。它工作在应用层,能感知通信的具体内容,往往可以做出更明智的决策,玩出更多花样。
七层负载均衡能做什么
- 缓存与加速。 CDN 能做的缓存类工作(按物理位置就近返回这类链路优化除外),七层负载均衡器基本都能做,比如静态资源缓存、协议升级、安全防护、访问控制等。
- 更智能的路由。 例如根据 Session 路由,实现亲和性集群;根据 URL 路由,实现专职化服务(此时相当于网关的职责);甚至根据用户身份路由,为部分用户提供特殊服务(如某些站点的贵宾服务器)。
- 抵御部分安全攻击。 以常见的 DDoS 手段 SYN Flood 为例:攻击者控制大量客户端,用虚假 IP 向同一目标发送海量 SYN 报文。四层负载均衡器无法感知上层协议内容,这些攻击会被原样转发到后端真实服务器;而在七层负载均衡器下,这些 SYN 攻击会在均衡器上被过滤掉,不会影响后端服务器的正常运行。类似地,还可以在七层设置多种策略,过滤特定报文,防御 SQL 注入等应用层攻击。
- 链路治理。 在许多微服务架构中,服务降级、熔断、异常注入等治理措施都需要在七层完成。例如,服务器只有出现物理或系统层面的故障、无法应答 TCP 请求时,才会被四层负载均衡器感知并剔除出集群;而如果服务器能应答、只是一直返回 500 错误,四层负载均衡器就完全无能为力,只能由七层负载均衡器来处理。
参考
- 周志明. 《凤凰架构:构建可靠的大型分布式系统》. 第 4 章 “流量治理”之“负载均衡”.