版本比较

标识

  • 该行被添加。
  • 该行被删除。
  • 格式已经改变。
Markdown
allowHtmltrue
# 第四章:负载均衡概论

负载均衡是构建可靠的分布式系统最核心的概念之一。几乎所有的大型分布式系统都是用四层负载均衡(例如 LVS、DPVS)基于 ECMP(等价多路径路由)实现多活入口,入口后方为七层负载均衡(例如 OpenResty、Kong、APISIX 等)实现的网关服务。负载均衡系统与基础架构层组件,例如弹性伸缩、服务治理等协同工作,实现业务系统高可用、高性能、高并发目标。

不过无论负载均衡器以何种形式存在,核心的职责都是“选择谁来处理用户请求”和“将用户请求转发过去”。

# 4.1 负载均衡概述

业内讨论网络负载均衡器的时候,负载均衡器(load balancer)和代理(proxy) 两个术语经常无差别混用,本文也将沿用这种惯例,认为二者整体上对等(严格地讲并非所有代理都是负载均衡器,但绝大多数代理都有负载均衡功能)。

如图 4-2 所示的负载均衡高层架构图,若干客户端正在访问若干后端服务,中间的负载均衡器完成以下功能:

- 服务发现:系统中哪些后端可用、它们的地址是什么(负载均衡器如何能够联系上它们)。
- 健康检查:哪些后端是健康的,可以正常接收请求。
- 负载均衡:用哪种算法来均衡请求至健康的后端。

<div  align="center">
	<img src="https://wiki.waringid.me/download/attachments/5636334/balancer.svg" width = "450"  align=center />
	<p>图4-2 负载均衡架构</p>
</div>

广义的负载均衡包括操作系统使用负载均衡来跨物理处理器调度任务、容器编排器使用负载均衡来跨集群调度任务、网络负载中跨可用后端调度网络任务等等,本章我们讨论的内容主要为网络负载均衡。网络类型所有解决方案通常分为两类:四层负载均衡和七层负载均衡,这两者分别对应 OSI 模型的 4 层和 7 层。
# 4.2 负载均衡拓扑类型

前面我们简单介绍了负载均衡的概览以及特点,接下来介绍它的几种分布式部署拓扑。

首先介绍的是**中间代理拓扑**,这也是大家最最熟悉的负载均衡方式,这一类型的硬件方案包括 Cisco、Juniper、F5 等公司的产品,软件方案包括 HAProxy、Nginx、Envoy 等。中间代理模式的优点是简单,用户只需要通过 DNS 连接到负载均衡器,其他的事情就不用再关心。缺点是这种模式下负载均衡器(即使后端已经做了集群)是单点的,而且横向扩展有瓶颈。

<div  align="center">
	<img src="https://wiki.waringid.me/download/attachments/5636337/balancer.svg" width = "400"  align=center />
	<p>图 4-3 中间代理拓扑</p>
</div>

第二种**边缘代理拓扑**是中间代理拓扑的一个变种,一般采用分级的方式部署,如图 4-4 所示。第一级是网络边缘部署四层均衡器,第二级是能识别应用协议的七层代理负载均衡,现代分布式的架构还要求二级的负载均衡器提供额外的 “API 网关”功能,例如 TLS termination、限速、鉴权,以及复杂的流量路由等等。现在,几乎所有的现代大型分布式系统都是在公网流量入口使用 L4/L7 两级负载均衡架构。

<div  align="center">
	<img src="https://wiki.waringid.me/download/attachments/5636337/balancer-edge-proxy.svg" width = "600"  align=center />
	<p>图 4-4 边缘分级负载均衡拓扑</p>
</div>


第三种**客户端嵌入**的方式相信读者们也非常熟悉,这种方式将负载均衡器以函数库/SDK的形式内嵌到客户端,如图 4-5 所示,知名的库包括 Finagle、 Eureka/Ribbon/Hystrix、gRPC。这种解决方案的主要优点是它完全将负载均衡器的所有功能分布到每个客户端上,从而消除了单点故障和扩展问题。缺点是每种使用的语言都要实现一遍。分布式架构越来越“多语言”,在大型服务架构上部署一次升级可能非常痛苦,生产环境中可能同时运行许多不同版本的库,增加操作的心智负担。

<div  align="center">
	<img src="https://wiki.waringid.me/download/attachments/5636337/balancer-sdk.svg" width = "400"  align=center />
	<p>图 4-5 客户端嵌入类型</p>
</div>


客户端内嵌库拓扑的一个变种是 **Sidecar 拓扑**(没错,笔者在 1.5.3 节就提过 Sidecar 本质是一种代理),如图 4-6 所示。近年来这种拓扑非常流行,被称为服务网格(service mesh)。sidecar 代理模式背后的思想是: 将流量导到另一个进程,牺牲一点(延迟)性能,实现客户端内嵌库模式的所有好处,而无任何语言绑定。

<div  align="center">
	<img src="https://wiki.waringid.me/download/attachments/5636337/balancer-sidecar.svg" width = "380"  align=center />
	<p>图 4-6 Sidecar 拓扑类型</p>
</div>

总体上中间代理类型正在逐渐升级为功能更为庞大的网关方案,而在处理东西向流量(Service-to-Service)中,随着云原生架构的流行,Sidecar(服务网格)也在逐渐取代客户端嵌入拓扑类型。

# 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(连接保活),因此四层负载均衡器的阻抗不匹配问题随时间越来越彰显,不过这个问题在四层负载均衡器之后再加一级七层负载均衡器就能得到解决。

# 4.4 七层负载均衡 

前面介绍四层负载均衡的工作模式都属于“转发”,此时客户端与响应请求的真实服务器维持着同一条 TCP 通道,但七层的负载均衡模式就无法再转发了,由于需要识别应用层协议,就要把 TCP 数据传送到用户态用户程序处理,之后再用代理的方式新建一个请求到真实服务器。如图 4-4 所示,一个七层 HTTP/2 负载均衡器,此时客户端、负载均衡器、真实服务器的工作模式。

<div  align="center">
	<img src="https://wiki.waringid.me/download/attachments/5636351/balancer7.svg" width = "600"  align=center />
	<p>图4-4 L7负载均衡</p>
</div>

这种情况下,客户端与七层负载均衡器建立一个 HTTP /2 TCP 连接,后续负载均衡器根据负载均衡策略和两个后端建立了连接。当客户端向负载均衡器发送两个 HTTP/2 流(streams )时,stream 1 会被发送到 backend-1,而 stream 2 会被发送到 backend-2。因此,即使不同客户端的请求数量差异巨大,这些请求也可以被高效平衡地分发到后端。七层负载均衡具备检测应用层流量的能力,就能做出更多、更明智的决策,也能玩出更多的花样。


## 4.1.4  是否还需要 L4 负载均衡

我们前面解释了 L7 负载均衡器对现代协议的重要性,那么是否意味着 L4LB 已经没有用了?肯定不!虽然在 service-to-service 通信中 L7 负载均衡最终会完全取代 L4 负载均衡,但 L4 负载均衡在边缘仍然是非常有用的,因为几乎所有的现代大型分布式架构都是在公网流量入口使用 L4/L7 两级负载均衡架构。在边缘 L7 负载均衡器之前部署 L4 负载均衡器的原因:

- L7LB 承担的更多工作是复杂的分析、变换、以及应用流量路由,他们处理原始流量的能 力(按每秒处理的包数和字节数衡量)比经过优化的 L4 负载均衡器要差。这使得 L4LB 更适合处理特定类型的攻击,例如 SYN 泛洪、通用包(generic packet)泛洪攻击等
- L7LB 部署的更多更频繁,bug 也比 L4LB 多。在 L7 之前加一层 L4LB,可以在调整 L7 部署的时候,对其做健康检查和流量排除(drain),这比(单纯使用)现代 L4LB 要简单的多,后者通常使用 BGP 和 ECMP(后面会介绍)。
- 最后,因为 L7 功能更复杂, 它们的 bug 也会比 L4 多,在前面有一层 L4LB 能及时将有问题的 L7LB 拉出。

# 4.4.1 Nginx 代理指南

七层负载均衡的实现相信读者们已经非常熟悉,没错,它就是 Nginx。使用 Nginx 七层负载均衡能识别应用层协议,可以通过对 HTTP 头、URL、Cookie 做复杂的逻辑处理,实现更灵活的控制。互联网技术架构中,通常 7 层负载均衡的核心是 Nginx,结合 Lua 插件技术,如 OpenResty,能扩展实现功能丰富且性能较高的网关方案。



## Nginx 配置指导

Nginx 的主配置文件是 nginx.conf,这个配置文件一共由三部分组成,分别为全局块、events 块和 http 块。

在 http 块中又包含 http 全局块、多个 server 块。每个 server 块中可以包含 server 全局块和多个 location 块,在同一配置块中嵌套的配置块,各个之间不存在次序关系。

<div  align="center">
	<img src="https://wiki.waringid.me/download/attachments/5636351/nginx-conf.png" width = "450"  align=center />
</div>

配置文件支持大量可配置的指令,绝大多数指令不是特定属于某一个块的。

同一个指令放在不同层级的块中,其作用域也不同,一般情况下,高一级块中的指令可以作用于自身所在的块和此块包含的所有低层级块。如果某个指令在两个不同层级的块中同时出现,则采用“就近原则”,即以较低层级块中的配置为准。

在本节,将讲解部分重要的配置,以便读者了解 Nginx 性能优化相关的操作。


### 缓冲(buffer)/缓存(cache)

作为反向代理,缓冲主要是解决后端 Server 与用户网络不对等的情况,比如 Nginx 到 Server 是 100KiB/s, 用户到 Nginx 是 10Kib/s, 这种情况下,如果没有启用 buffer,会导致 Nginx 使用较长的时间处理 用户端与后端 Server 的连接,在高并发的环境下会出现大量的连接积压。

开启代理缓冲后 Nginx 可以用较快的速度尽可能将响应体读取并缓冲到本地内存或磁盘中,然后同时根据客户端的网络质量以合适的网速将响应传递给客户端。
这样既解决了 server 端连接过多的问题也保证了能持续稳定地向客户端传递响应。


Nginx 使用 proxy_buffering 指令启用和禁用缓冲,proxy_buffers 指令设置每个连接读取响应的缓冲区的大小和数量,默认情况下缓冲区大小等于一个内存页,4K 或 8K,具体取决于操作系统。

proxy_buffer_size 可以用来设置后端服务器响应的第一部分存储在单独的缓冲区,此部分通常是相对较小的响应 headers,通常将其设置成小于默认值。

```plain
location / {
    proxy_buffers 16 4k;
    proxy_buffer_size 2k;
    proxy_pass http://localhost:8080;
}
```
### 缓存 Cache

启用缓存后,Nginx 将响应保存在磁盘中,返回给客户端的数据首先从缓存中获取,这样子相同的请求不用每次都发送给后端服务器,减少到后端请求的数量。

启用缓存,需要在 http 上下文中使用 proxy_cache_path 指令,定义缓存的本地文件目录,名称和大小。

缓存区可以被多个 server 共享,使用 proxy_cache 指定使用哪个缓存区。
```plain
http {
    proxy_cache_path /data/nginx/cache keys_zone=mycache:10m;
    server {
        proxy_cache mycache;
        location / {
            proxy_pass http://localhost:8000;
        }
    }
}
```

缓存目录的文件名是 proxy_cache_key 的 MD5 值, proxy_cache_key 默认设置如下

```plain
proxy_cache_key $scheme$proxy_host$uri$is_args$args;
```

当然也可以自定义缓存 key
```plain
proxy_cache_key "$host$request_uri$cookie_user";
```

缓存不应该设置的太敏感,可以使用 proxy_cache_min_uses 设置相同的 key 的请求,访问次数超过指定数量才会被缓存。
```plain
proxy_cache_min_uses 5;
```

缓存设置的示例

```plain
http {
	proxy_cache_path /var/cache/nginx/data keys_zone=mycache:10m;
	server {
 		location = /html/demo.html {
	        proxy_cache mycache;
	        proxy_cache_valid 200 302 10m;
	        proxy_cache_valid 404      1m;
	        proxy_cache_valid any 5m;

	        proxy_pass http://localhost:8088;  
    	}
 	}
}
```

### 负载均衡

跨多个应用程序实例的负载平衡是一种常用技术,用于优化资源利用率、最大化吞吐量、减少延迟和确保容错配置,Nginx 支持 6 种负载均衡模式

|模式|介绍|
|:--|:--|
|轮循机制|默认机制,以轮循机制方式分发|
|最小连接|将下一个请求分配给活动连接数最少的服务器|
|ip-hash |客户端的 IP 地址将用作哈希键,来自同一个 ip 的请求会被转发到相同的服务器|
|hash|通用 hash,允许用户自定义 hash 的 key,key 可以是字符串、变量或组合|
|随机‎‎|每个请求都将传递到随机选择的服务器|
|权重|按照 weight 参数进行分配 |

在反向代理中,如果后端服务器在某个周期内响应失败次数超过规定值,Nginx 会将此服务器标记为失败,并在之后的一个周期不再将请求发送给这台服务器。

在 upstream 配置中,通过 fail_timeout‎‎来设置检查周期,默认为 10 秒。通过 max_fails‎来设置检查失败次数,默认为 1 次。‎

如:
```plain
upstream backend {
  server backend.example.domain max_fails=3 fail_timeout=30s; 
} 
```

# 4.4.2 从负载均衡到网关

随着微服务架构、容器编排调度等技术的崛起,现代分布式系统已经越来越动态,这就意味着通过静态文件配置方式早已过时。作为分布式系统的入口,负载均衡器就需要转换角色,不仅仅作为一个代理,而是要承担提供更多现代化的功能。把这些功能统一前移某一层独立支持,实现 API Gateway as a Service,让各个服务直接接入,在管理平台上管理,可视化配置等等,这样就实现了一个全局的视图统一管理这些功能。

而这就是七层负载均衡的升级 -- **网关(API Gateway)**。

API Gateway 在业内已经有非常多的成熟开源方案,如果按实现语言以及成熟度来讲,主流有以下几个方案。

|语言|网关|特点|
|:--|:--|:--|
|Nginx + LuaJIT|OpenResty| 原始 Web 开发平台 |
|Nginx + Lua| Kong| 社区活跃、成熟度高、Postgres 存储、二次开发成本高 |
|Nginx + Lua| Apache/APISIX | 云原生化、使用 etcd 存储、性能高、二次开发成本低|
|Java|Spring Cloud Alibaba| Spring Cloud 生态、社区成熟度高、国内应用广泛|

这些现代化的网关方案各有各的特点,实现的功能也非常强大,笔者不再逐一展开,下面简单列举部分实现,以便读者对它“强大功能”有个直观的感受。

- **协议支持** 现代网关系统正在显式添加对更多协议的支持。负载均衡器对应用层协议了解的越多, 就可以处理越多更复杂的事情,包括观测输出、高级负载均衡和路由等等。例如,在写作本文时,Envoy 显式支持如下七层协议的解析和路由:HTTP/1、HTTP/2、gRPC、Redis、MongoDB、DynamoDB。

- **动态配置** 服务管理的动态配置包括路由、上游服务(Upstream)、SSL证书、消费者等等,数据的替换和和更新不会产生任何中断,从而将线上流量的影响降低到最低。

- **流量治理** 在很多微服务架构中,流量治理都需要在七层中进行,譬如超时、重试、限速、熔断、流量镜像、缓存等等。现代网关系统在服务以及流量的管理上可对多业务进行收敛,统一处理,降低多套网关的运维成本。


- **可观测性**:在互联网快速发展的今天,基础设施与应用的部署构建都发生了极大变化,特别是基于分布式、微服务的体系架构,传统的监控方式已经无法适应云原生的场景。服务治理的可观测性输出是网关系统提供的最重要的特性。系统度量、分布式跟踪以及自定义日志等功能现在几乎是七层负载均衡解决方案的标配。

- **可扩展性** 例如典型的 OpenResty,通过编写可插拔的插件然后加载到负载均衡器,能够轻松地对网关系统实现各种流量处理和分发功能等自定义功能,比如流量限速、日志记录、安全检测、故障注入等等。

- **高可用以及无状态设计**:现代网关系统不仅提供数据面实现,还提供控制面实现,二者目标都在朝向无状态设计,可以轻松地实现水平扩展。网关架构整体上也默认高可用,不存在单点故障。


# 4.5 负载均衡策略

前面章节我们讲解了负载均衡的职责“将用户的请求转发过去”,这一节,我们再讲解负载均衡的另一个职责“**选择谁来处理用户请求**”,也就是负载均衡器所采用的均衡策略。

由于这一块所涉及的算法过多,笔者无法逐一展开,所以本节仅从功能和应用的角度介绍部分常见的负载均衡策略。

- **轮询均衡**(Round-Robin):按依次循环的方式将请求调度到不同的服务器上,该算法最大的特点就是实现简单。轮询算法假设所有的服务器处理请求的能力都一样的,调度器会将所有的请求平均分配给每个真实服务器。

- **最小连接均衡**(Least-Connection):该算法中调度器需要记录各个服务器已建立连接的数目,通过服务器当前活跃的连接数来估计服务器的情况。当一个请求被调度到某台服务器,其连接数加 1;当连接中断或者超时,其连接数减 1。该算法的策略把新的连接请求分配到当前连接数最小的服务器。

- **一致性哈希均衡**(Consistency Hash):将请求中的某些特征数据(例如 IP、MAC 或者更上层应用的某些信息)作为特征值来计算需要落在的节点。一致性哈希算法会保证同一个特征值每一次都会落在相同的服务器上。

- **随机均衡**(Random):此种负载均衡算法类似于轮询调度,不过在分配处理请求时是随机的过程。由概率论可以得知,随着客户端调用服务端的次数增多,其实际效果趋近于平均分配请求到服务端的每一台服务器,也就是达到轮询的效果。

以上算法假设的是所有服务器处理性能均相同,不管服务器的当前连接数和响应速度,调度器会将所有的请求平均分配给每个服务器。但如果服务器处理能力不一致呢?譬如:服务器 A 每秒可处理 10 个请求,服务器 B 每秒可处理 100 个请求,如果不考虑服务器的处理能力,负载均衡算法实际上就是一种伪均衡。

考虑服务器的处理能力不同,负载均衡策略又有了对服务器“**加权**”的优化补充(不包括一致性哈希均衡)。加权调度算法增加了按权值高低分配的因素,权值高的服务器会比权值低的服务器处理更多的连接,从而保证各个性能不一致的服务器集群整体处于均衡状态。常用的加权负载均衡策略包括加权轮询调度(Weight Round Robin)、加权最小连接调度(Weight Least-Connection)、加权随机策略(Weight Random)等。