简介
Spring Cloud 不仅提供了使用 Ribbon 进行 客户端负载均衡 ,还提供了 Spring Cloud LoadBalancer 。目前 Netiflix 已经停止维护 Ribbon,最新的开源替代是 LoadBalancer。相比较于 Ribbon,Spring Cloud LoadBalancer 不仅能够支持 RestTemplate ,还支持 WebClient 。WeClient 是 Spring Web Flux 中提供的功能,可以实现响应式异步请求。
这两大开源组件都是基于客户端的负载均衡组件 ,实现了轮询、随机和权重请求等负载均衡算法。
那什么是服务端与客户端负载均衡呢? (以Nginx与Ribbon为例)
- 服务端负载均衡 :客户端所有请求都会交给nginx,然后由nginx实现转发请求,即负载均衡是由服务端实现
- 客户端负载均衡 :Ribbon在调用接口的时候从注册中心上获取注册信息服务列表,获取之后缓存在jvm本地,使用本地实现rpc远程技术进行调用;
目前业界主流的 负载均衡 方案可分成两类:
- 集中式负载均衡 :即在 consumer 和 provider 之间使用独立的负载均衡组件,由该设施负责把访问请求通过某种策略转发至 provider;
- 进程内负载均衡 :将负载均衡逻辑集成到 consumer,consumer 从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的 provider。
因此,服 务端负载均衡也就是集中式负载均衡,客户端负载均衡也就是进程内负载均衡 。
服务器负载均衡
集中式负载均衡器
在互联网时代的早期,网站流量还相对较小,并且业务也比较简单,单台服务器便有可能满足访问需要,但时至今日,互联网应用也好,企业级应用也好,一般实际用于生产的系统,几乎都离不开集群部署了。信息系统不论是采用单体架构多副本部署还是微服务架构,不论是为了实现高可用还是为了获得高性能,都需要利用到多台机器来扩展服务能力,希望用户的请求不管连接到哪台机器上,都能得到相同的处理。另一方面,如何构建和调度服务集群这事情,又必须对用户一侧保持足够的透明,即使请求背后是由一千台、一万台机器来共同响应的,也绝非用户所关心的事情,用户需记住的只有一个域名地址而已。调度后方的多台机器,以统一的接口对外提供服务,承担此职责的技术组件被称为“负载均衡”(Load Balancing)。
真正大型系统的负载均衡过程往往是多级的。譬如,在各地建有多个机房,或机房有不同网络链路入口的大型互联网站,会从 DNS 解析开始,通过“域名” → “CNAME” → “负载调度服务” → “就近的数据中心入口”的路径,先将来访地用户根据 IP 地址(或者其他条件)分配到一个合适的数据中心中,然后才到稍后将要讨论的各式负载均衡。在 DNS 层面的负载均衡与前面介绍的 DNS 智能线路、内容分发网络等,在工作原理上是类似的,其差别只是数据中心能提供的不仅有缓存,而是全方位的服务能力。由于这种方式此前已经详细讲解过,后续我们所讨论的“负载均衡”就只聚焦于网络请求进入数据中心入口之后的其他级次的负载均衡。
无论在网关内部建立了多少级的负载均衡,从形式上来说都可以分为两种:四层负载均衡和七层负载均衡。在详细介绍它们是什么以及如何工作之前,我们先来建立两个总体的、概念性的印象。
- 四层负载均衡的优势是性能高,七层负载均衡的优势是功能强。
- 做多级混合负载均衡,通常应是低层的负载均衡在前,高层的负载均衡在后(想一想为什么?)。
我们所说的“四层”、“七层”,指的是经典的OSI 七层模型中第四层传输层和第七层应用层,表 4-1 是来自于维基百科上对 OSI 七层模型的介绍(笔者做了简单的中文翻译),这部分属于网络基础知识,这里就不多解释了。后面我们会多次使用到这张表,如你对网络知识并不是特别了解的,可通过维基百科上的连接获得进一步的资料。
表 4-1 OSI 七层模型
层 | 数据单元 | 功能 | |
---|---|---|---|
7 | 应用层 Application Layer | 数据 Data | 提供为应用软件提供服务的接口,用于与其他应用软件之间的通信。典型协议:HTTP、HTTPS、FTP、Telnet、SSH、SMTP、POP3 等 |
6 | 表达层 Presentation Layer | 数据 Data | 把数据转换为能与接收者的系统格式兼容并适合传输的格式。 |
5 | 会话层 Session Layer | 数据 Data | 负责在数据传输中设置和维护计算机网络中两台计算机之间的通信连接。 |
4 | 传输层 Transport Layer | 数据段 Segments | 把传输表头加至数据以形成数据包。传输表头包含了所使用的协议等发送信息。典型协议:TCP、UDP、RDP、SCTP、FCP 等 |
3 | 网络层 Network Layer | 数据包 Packets | 决定数据的传输路径选择和转发,将网络表头附加至数据段后以形成报文(即数据包)。典型协议:IPv4/IPv6、IGMP、ICMP、EGP、RIP 等 |
2 | 数据链路层 Data Link Layer | 数据帧 Frame | 负责点对点的网络寻址、错误侦测和纠错。当表头和表尾被附加至数据包后,就形成数据帧(Frame)。典型协议:WiFi(802.11)、Ethernet(802.3)、PPP 等。 |
1 | 物理层 Physical Layer | 比特流 Bit | 在物理网络上传送数据帧,它负责管理电脑通信设备和网络媒体之间的互通。包括了针脚、电压、线缆规范、集线器、中继器、网卡、主机接口卡等。 |
现在所说的“四层负载均衡”其实是多种均衡器工作模式的统称,“四层”的意思是说这些工作模式的共同特点是维持着同一个 TCP 连接,而不是说它只工作在第四层。事实上,这些模式主要都是工作在二层(数据链路层,改写 MAC 地址)和三层(网络层,改写 IP 地址)上,单纯只处理第四层(传输层,可以改写 TCP、UDP 等协议的内容和端口)的数据无法做到负载均衡的转发,因为 OSI 的下三层是媒体层(Media Layers),上四层是主机层(Host Layers),既然流量都已经到达目标主机上了,也就谈不上什么流量转发,最多只能做代理了。但出于习惯和方便,现在几乎所有的资料都把它们统称为四层负载均衡,笔者也同样称呼它为四层负载均衡,如果读者在某些资料上看见“二层负载均衡”、“三层负载均衡”的表述,应该了解这是在描述它们工作的层次,与这里说的“四层负载均衡”并不是同一类意思。下面笔者来介绍几种常见的四层负载均衡的工作模式。
数据链路层负载均衡
参考上面 OSI 模型的表格,数据链路层传输的内容是数据帧(Frame),譬如常见的以太网帧、ADSL 宽带的 PPP 帧等。我们讨论的具体上下文里,目标必定就是以太网帧了,按照IEEE 802.3标准,最典型的 1500 Bytes MTU 的以太网帧结构如表 4-2 所示。
表 4-2 最典型的 1500 Bytes MTU 的以太网帧结构说明
数据项 | 取值 |
---|---|
前导码 | 10101010 7 Bytes |
帧开始符 | 10101011 1 Byte |
MAC 目标地址 | 6 Bytes |
MAC 源地址 | 6 Bytes |
802.1Q标签(可选) | 4 Bytes |
以太类型 | 2 Bytes |
有效负载 | 1500 Bytes |
冗余校验 | 4 Bytes |
帧间距 | 12 Bytes |
帧结构中其他数据项的含义在本节中可以暂时不去理会,只需注意到“MAC 目标地址”和“MAC 源地址”两项即可。我们知道每一块网卡都有独立的 MAC 地址,以太帧上这两个地址告诉了交换机,此帧应该是从连接在交换机上的哪个端口的网卡发出,送至哪块网卡的。
数据链路层负载均衡所做的工作,是修改请求的数据帧中的 MAC 目标地址,让用户原本是发送给负载均衡器的请求的数据帧,被二层交换机根据新的 MAC 目标地址转发到服务器集群中对应的服务器(后文称为“真实服务器”,Real Server)的网卡上,这样真实服务器就获得了一个原本目标并不是发送给它的数据帧。
由于二层负载均衡器在转发请求过程中只修改了帧的 MAC 目标地址,不涉及更上层协议(没有修改 Payload 的数据),所以在更上层(第三层)看来,所有数据都是未曾被改变过的。由于第三层的数据包,即 IP 数据包中包含了源(客户端)和目标(均衡器)的 IP 地址,只有真实服务器保证自己的 IP 地址与数据包中的目标 IP 地址一致,这个数据包才能被正确处理。因此,使用这种负载均衡模式时,需要把真实物理服务器集群所有机器的虚拟 IP 地址(Virtual IP Address,VIP)配置成与负载均衡器的虚拟 IP 一样,这样经均衡器转发后的数据包就能在真实服务器中顺利地使用。也正是因为实际处理请求的真实物理服务器 IP 和数据请求中的目的 IP 是一致的,所以响应结果就不再需要通过负载均衡服务器进行地址交换,可将响应结果的数据包直接从真实服务器返回给用户的客户端,避免负载均衡器网卡带宽成为瓶颈,因此数据链路层的负载均衡效率是相当高的。整个请求到响应的过程如图 4-8 所示。
图 4-8 数据链路层负载均衡
上述只有请求经过负载均衡器,而服务的响应无须从负载均衡器原路返回的工作模式,整个请求、转发、响应的链路形成一个“三角关系”,所以这种负载均衡模式也常被很形象地称为“三角传输模式”(Direct Server Return,DSR),也有叫“单臂模式”(Single Legged Mode)或者“直接路由”(Direct Routing)。
虽然数据链路层负载均衡效率很高,但它并不能适用于所有的场合,除了那些需要感知应用层协议信息的负载均衡场景它无法胜任外(所有的四层负载均衡器都无法胜任,将在后续介绍七层均衡器时一并解释),它在网络一侧受到的约束也很大。二层负载均衡器直接改写目标 MAC 地址的工作原理决定了它与真实的服务器的通信必须是二层可达的,通俗地说就是必须位于同一个子网当中,无法跨 VLAN。优势(效率高)和劣势(不能跨子网)共同决定了数据链路层负载均衡最适合用来做数据中心的第一级均衡设备,用来连接其他的下级负载均衡器。
网络层负载均衡
根据 OSI 七层模型,在第三层网络层传输的单位是分组数据包(Packets),这是一种在分组交换网络(Packet Switching Network,PSN)中传输的结构化数据单位。以 IP 协议为例,一个 IP 数据包由 Headers 和 Payload 两部分组成, Headers 长度最大为 60 Bytes,其中包括了 20 Bytes 的固定数据和最长不超过 40 Bytes 的可选的额外设置组成。按照 IPv4 标准,一个典型的分组数据包的 Headers 部分具有如表 4-3 所示的结构。
表 4-3 分组数据包的 Headers 部分说明
长度 | 存储信息 |
---|---|
0-4 Bytes | 版本号(4 Bits)、首部长度(4 Bits)、分区类型(8 Bits)、总长度(16 Bits) |
5-8 Bytes | 报文计数标识(16 Bits)、标志位(4 Bits)、片偏移(12 Bits) |
9-12 Bytes | TTL 生存时间(8 Bits)、上层协议代号(8 Bits)、首部校验和(16 Bits) |
13-16 Bytes | 源地址(32 Bits) |
17-20 Bytes | 目标地址(32 Bits) |
20-60 Bytes | 可选字段和空白填充 |
在本节中,无须过多关注表格中的其他信息,只要知道在 IP 分组数据包的 Headers 带有源和目标的 IP 地址即可。源和目标 IP 地址代表了数据是从分组交换网络中哪台机器发送到哪台机器的,我们可以沿用与二层改写 MAC 地址相似的思路,通过改变这里面的 IP 地址来实现数据包的转发。具体有两种常见的修改方式。
第一种是保持原来的数据包不变,新创建一个数据包,把原来数据包的 Headers 和 Payload 整体作为另一个新的数据包的 Payload,在这个新数据包的 Headers 中写入真实服务器的 IP 作为目标地址,然后把它发送出去。经过三层交换机的转发,真实服务器收到数据包后,必须在接收入口处设计一个针对性的拆包机制,把由负载均衡器自动添加的那层 Headers 扔掉,还原出原来的数据包来进行使用。这样,真实服务器就同样拿到了一个原本不是发给它(目标 IP 不是它)的数据包,达到了流量转发的目的。那时候还没有流行起“禁止套娃”的梗,所以设计者给这种“套娃式”的传输起名叫做“IP 隧道”(IP Tunnel)传输,也还是相当的形象。
尽管因为要封装新的数据包,IP 隧道的转发模式比起直接路由模式效率会有所下降,但由于并没有修改原有数据包中的任何信息,所以 IP 隧道的转发模式仍然具备三角传输的特性,即负载均衡器转发来的请求,可以由真实服务器去直接应答,无须在经过均衡器原路返回。而且由于 IP 隧道工作在网络层,所以可以跨越 VLAN,因此摆脱了直接路由模式中网络侧的约束。此模式从请求到响应的过程如图 4-9 所示。
图 4-9 IP 隧道模式的负载均衡
而这种转发方式也有缺点。第一个缺点是它要求真实服务器必须支持“IP 隧道协议”(IP Encapsulation),就是它得学会自己拆包扔掉一层 Headers,这个其实并不是什么大问题,现在几乎所有的 Linux 系统都支持 IP 隧道协议。另外一个缺点是这种模式仍必须通过专门的配置,必须保证所有的真实服务器与均衡器有着相同的虚拟 IP 地址,因为回复该数据包时,需要使用这个虚拟 IP 作为响应数据包的源地址,这样客户端收到这个数据包时才能正确解析。这个限制就相对麻烦一些,它与“透明”的原则冲突,需由系统管理员介入。
而且,对服务器进行虚拟 IP 的配置并不是在任何情况下都可行的,尤其是当有好几个服务共用一台物理服务器的时候,此时就必须考虑第二种修改方式——改变目标数据包:直接把数据包 Headers 中的目标地址改掉,修改后原本由用户发给均衡器的数据包,也会被三层交换机转发送到真实服务器的网卡上,而且因为没有经过 IP 隧道的额外包装,也就无须再拆包了。但问题是这种模式是通过修改目标 IP 地址才到达真实服务器的,如果真实服务器直接将应答包返回客户端的话,这个应答数据包的源 IP 是真实服务器的 IP,也即均衡器修改以后的 IP 地址,客户端不可能认识该 IP,自然就无法再正常处理这个应答了。因此,只能让应答流量继续回到负载均衡,由负载均衡把应答包的源 IP 改回自己的 IP,再发给客户端,这样才能保证客户端与真实服务器之间的正常通信。如果你对网络知识有些了解的话,肯定会觉得这种处理似曾相识,这不就是在家里、公司、学校上网时,由一台路由器带着一群内网机器上网的“网络地址转换”(Network Address Translation,NAT)操作吗?这种负载均衡的模式的确被称为 NAT 模式,此时,负载均衡器就是充当了家里、公司、学校的上网路由器的作用。NAT 模式的负载均衡器运维起来十分简单,只要机器将自己的网关地址设置为均衡器地址,就无须再进行任何额外设置了。此模式从请求到响应的过程如图 4-10 所示。
图 4-10 NAT 模式的负载均衡
在流量压力比较大的时候,NAT 模式的负载均衡会带来较大的性能损失,比起直接路由和 IP 隧道模式,甚至会出现数量级上的下降。这点是显而易见的,由负载均衡器代表整个服务集群来进行应答,各个服务器的响应数据都会互相挣抢均衡器的出口带宽,这就好比在家里用 NAT 上网的话,如果有人在下载,你打游戏可能就会觉得卡顿是一个道理,此时整个系统的瓶颈很容易就出现在负载均衡器上。
还有一种更加彻底的 NAT 模式:即均衡器在转发时,不仅修改目标 IP 地址,连源 IP 地址也一起改了,源地址就改成均衡器自己的 IP,称作 Source NAT(SNAT)。这样做的好处是真实服务器无须配置网关就能够让应答流量经过正常的三层路由回到负载均衡器上,做到了彻底的透明。但是缺点是由于做了 SNAT,真实服务器处理请求时就无法拿到客户端的 IP 地址了,从真实服务器的视角看来,所有的流量都来自于负载均衡器,这样有一些需要根据目标 IP 进行控制的业务逻辑就无法进行。
应用层负载均衡
前面介绍的四层负载均衡工作模式都属于“转发”,即直接将承载着 TCP 报文的底层数据格式(IP 数据包或以太网帧)转发到真实服务器上,此时客户端到响应请求的真实服务器维持着同一条 TCP 通道。但工作在四层之后的负载均衡模式就无法再进行转发了,只能进行代理,此时真实服务器、负载均衡器、客户端三者之间由两条独立的 TCP 通道来维持通信,转发与代理的区别如图 4-11 所示。
图 4-11 转发与代理
“代理”这个词,根据“哪一方能感知到”的原则,可以分为“正向代理”、“反向代理”和“透明代理”三类。正向代理就是我们通常简称的代理,指在客户端设置的、代表客户端与服务器通信的代理服务,它是客户端可知,而对服务器透明的。反向代理是指在设置在服务器这一侧,代表真实服务器来与客户端通信的代理服务,此时它对客户端来说是透明的。至于透明代理是指对双方都透明的,配置在网络中间设备上的代理服务,譬如,架设在路由器上的透明翻墙代理。
根据以上定义,很显然,七层负载均衡器它就属于反向代理中的一种,如果只论网络性能,七层均衡器肯定是无论如何比不过四层均衡器的,它比四层均衡器至少多一轮 TCP 握手,有着跟 NAT 转发模式一样的带宽问题,而且通常要耗费更多的 CPU,因为可用的解析规则远比四层丰富。所以如果用七层均衡器去做下载站、视频站这种流量应用是不合适的,起码不能作为第一级均衡器。但是,如果网站的性能瓶颈并不在于网络性能,要论整个服务集群对外所体现出来的服务性能,七层均衡器就有它的用武之地了。这里面七层均衡器的底气就是来源于它工作在应用层,可以感知应用层通信的具体内容,往往能够做出更明智的决策,玩出更多的花样来。
举个生活中的例子,四层均衡器就像银行的自助排号机,转发效率高且不知疲倦,每一个达到银行的客户根据排号机的顺序,选择对应的窗口接受服务;而七层均衡器就像银行大堂经理,他会先确认客户需要办理的业务,再安排排号。这样办理理财、存取款等业务的客户,会根据银行内部资源得到统一协调处理,加快客户业务办理流程,有一些无须柜台办理的业务,由大堂经理直接就可以解决了,譬如,反向代理的就能够实现静态资源缓存,对于静态资源的请求就可以在反向代理上直接返回,无须转发到真实服务器。
代理的工作模式相信大家应该是比较熟悉的,这里不再展开,只是简单列举了一些七层代理可以实现的功能,以便读者对它“功能强大”有个直观的感受。
- 前面介绍 CDN 应用时,所有 CDN 可以做的缓存方面的工作(就是除去 CDN 根据物理位置就近返回这种优化链路的工作外),七层均衡器全都可以实现,譬如静态资源缓存、协议升级、安全防护、访问控制,等等。
- 七层均衡器可以实现更智能化的路由。譬如,根据 Session 路由,以实现亲和性的集群;根据 URL 路由,实现专职化服务(此时就相当于网关的职责);甚至根据用户身份路由,实现对部分用户的特殊服务(如某些站点的贵宾服务器),等等。
- 某些安全攻击可以由七层均衡器来抵御,譬如一种常见的 DDoS 手段是 SYN Flood 攻击,即攻击者控制众多客户端,使用虚假 IP 地址对同一目标大量发送 SYN 报文。从技术原理上看,由于四层均衡器无法感知上层协议的内容,这些 SYN 攻击都会被转发到后端的真实服务器上;而七层均衡器下这些 SYN 攻击自然在负载均衡设备上就被过滤掉,不会影响到后面服务器的正常运行。类似地,可以在七层均衡器上设定多种策略,譬如过滤特定报文,以防御如 SQL 注入等应用层面的特定攻击手段。
- 很多微服务架构的系统中,链路治理措施都需要在七层中进行,譬如服务降级、熔断、异常注入,等等。譬如,一台服务器只有出现物理层面或者系统层面的故障,导致无法应答 TCP 请求才能被四层均衡器所感知,进而剔除出服务集群,如果一台服务器能够应答,只是一直在报 500 错,那四层均衡器对此是完全无能为力的,只能由七层均衡器来解决。
均衡策略与实现
负载均衡的两大职责是“选择谁来处理用户请求”和“将用户请求转发过去”。到此我们仅介绍了后者,即请求的转发或代理过程。前者是指均衡器所采取的均衡策略,由于这一块涉及的均衡算法太多,笔者无法逐一展开,所以本节仅从功能和应用的角度去介绍一些常见的均衡策略。
- 轮循均衡 (Round Robin):每一次来自网络的请求轮流分配给内部中的服务器,从 1 至 N 然后重新开始。此种均衡算法适合于集群中的所有服务器都有相同的软硬件配置并且平均服务请求相对均衡的情况。
- 权重轮循均衡 (Weighted Round Robin):根据服务器的不同处理能力,给每个服务器分配不同的权值,使其能够接受相应权值数的服务请求。譬如:服务器 A 的权值被设计成 1,B 的权值是 3,C 的权值是 6,则服务器 A、B、C 将分别接收到 10%、30%、60%的服务请求。此种均衡算法能确保高性能的服务器得到更多的使用率,避免低性能的服务器负载过重。
- 随机均衡 (Random):把来自客户端的请求随机分配给内部中的多个服务器,在数据足够大的场景下能达到相对均衡的分布。
- 权重随机均衡 (Weighted Random):此种均衡算法类似于权重轮循算法,不过在分配处理请求时是个随机选择的过程。
- 一致性哈希均衡 (Consistency Hash):根据请求中某一些数据(可以是 MAC、IP 地址,也可以是更上层协议中的某些参数信息)作为特征值来计算需要落在的节点上,算法一般会保证同一个特征值每次都一定落在相同的服务器上。一致性的意思是保证当服务集群某个真实服务器出现故障,只影响该服务器的哈希,而不会导致整个服务集群的哈希键值重新分布。
- 响应速度均衡 (Response Time):负载均衡设备对内部各服务器发出一个探测请求(例如 Ping),然后根据内部中各服务器对探测请求的最快响应时间来决定哪一台服务器来响应客户端的服务请求。此种均衡算法能较好的反映服务器的当前运行状态,但这最快响应时间仅仅指的是负载均衡设备与服务器间的最快响应时间,而不是客户端与服务器间的最快响应时间。
- 最少连接数均衡 (Least Connection):客户端的每一次请求服务在服务器停留的时间可能会有较大的差异,随着工作时间加长,如果采用简单的轮循或随机均衡算法,每一台服务器上的连接进程可能会产生极大的不平衡,并没有达到真正的负载均衡。最少连接数均衡算法对内部中需负载的每一台服务器都有一个数据记录,记录当前该服务器正在处理的连接数量,当有新的服务连接请求时,将把当前请求分配给连接数最少的服务器,使均衡更加符合实际情况,负载更加均衡。此种均衡策略适合长时处理的请求服务,如 FTP 传输。
客户端负载均衡
分布式负载均衡、进程内负载均衡
问题
在正式开始讨论之前,我们先来明确区分清楚几个容易混淆的相似概念,分别是本章节中频繁提到的 服务发现 、 网关路由 、 负载均衡 以及在服务流量治理章节中将会介绍的 服务容错 。这几个技术名词都带有着“从服务集群中寻找到一个合适的服务来调用”的含义,笔者通过以下具体场景来说明它们之间的差别:
案例场景:
假设你身处广东,要上 Fenix’s Bookstore 购买一本书,在程序业务逻辑里,购书其中一个关键步骤是调用商品出库服务来完成货物准备,在代码中该服务的调用请求为:
PATCH https://warehouse:8080/restful/stockpile/3 {amount: -1}
又假设 Fenix’s Bookstore 是个大书店,在北京、武汉、广州的机房均部署有服务集群,你的购物请求从浏览器发出后,服务端按顺序发生了如下事件:
首先是将
warehouse
这个服务名称转换为恰当的服务地址,“恰当”是个宽泛的描述,一种典型的“恰当”便是因调用请求来自广东,优先分配给传输距离最短的广州机房来应答。其实按常理来说这次出库服务的调用应该是集群内的流量,而不是用户浏览器直接发出的请求,所以尽管结果没有不同,但更接近实际的的情况是用户访问首页时已经被 DNS 服务器分配到了广州机房,请求出库服务时,应优先选择同机房的服务进行调用,此时请求变为:PATCH https://guangzhou-ip-wan:8080/restful/stockpile/3
广州机房的服务网关将该请求与配置中的特征进行比对,由 URL 中的
/restful/stockpile/**
得知该请求访问的是商品出库服务,因此,将请求的 IP 地址转换为内网中 warehouse 服务集群的入口地址:PATCH https://warehouse-gz-lan:8080/restful/stockpile/3
集群中部署有多个 warehouse 服务,收到调用请求后,负载均衡器要在多个服务中根据某种标准——可能是随机挑选,也可能是按顺序轮询,抑或是选择此前调用次数最少那个,等等。根据均衡策略找出要响应本次调用的服务,称其为
warehouse-gz-lan-node1
。PATCH https://warehouse-gz-lan-node1:8080/restful/stockpile/3
如果访问
warehouse-gz-lan-node1
服务,没有返回需要的结果,而是抛出 500 错。HTTP/1.1 500 Internal Server Error
根据预置的故障转移(Failover)策略,重试将调用分配给能够提供该服务的其他节点,称其为
warehouse-gz-lan-node2
。PATCH https://warehouse-gz-lan-node2:8080/restful/stockpile/3
warehouse-gz-lan-node2
服务返回商品出库成功。HTTP/1.1 200 OK
以上过程从整体上看,步骤 1、2、3、5,分别对应了 服务发现 、 网关路由 、 负载均衡 和 服务容错 ,在细节上看,其中部分职责又是有交叉的,并不是服务注册中心就只关心服务发现,网关只关心路由,均衡器只关心流量负载均衡。譬如,步骤 1 服务发现的过程中,“根据请求来源的物理位置来分配机房”这个操作本质上是根据请求中的特征(地理位置)进行流量分发,这实际是一种路由行为。实际系统中,在 DNS 服务器(DNS 智能线路)、服务注册中心(如 Eureka 等框架中的 Region、Zone 概念)或者负载均衡器(可用区负载均衡,如 AWS 的 NLB,或 Envoy 的 Region、Zone、Sub-zone)中都有可能实现。
此外,你是否感觉到以上网络调用过程似乎过于烦琐了,一个从广州机房内网发出的服务请求,绕到了网络边缘的网关、负载均衡器这些设施上,再被分配回内网中另外一个服务去响应,不仅消耗了带宽,降低了性能,也增加了链路上的风险和运维的复杂度。可是,如果流量不经过这些设施,它们相应的职责就无法发挥作用,譬如不经过负载均衡器的话,连请求应该具体交给哪一个服务去处理都无法确定,这有办法简化吗?
方案
对于任何一个大型系统,负载均衡器都是必不可少的设施。以前,负载均衡器大多只部署在整个服务集群的前端,将用户的请求分流到各个服务进行处理,这种经典的部署形式现在被称为集中式的负载均衡。随着微服务日渐流行,服务集群的收到的请求来源不再局限于外部,越来越多的访问请求是由集群内部的某个服务发起,由集群内部的另一个服务进行响应的,对于这类流量的负载均衡,既有的方案依然是可行的,但针对内部流量的特点,直接在服务集群内部消化掉,肯定是更合理更受开发者青睐的办法。由此一种全新的、独立位于每个服务前端的、分散式的负载均衡方式正逐渐变得流行起来,这就是本节我们要讨论的主角:客户端负载均衡器(Client-Side Load Balancer),如图 7-4 所示:
图 7-4 客户端负载均衡器
客户端负载均衡器的理念提出以后,此前的集中式负载均衡器也有了一个方便与它对比的名字“服务端负载均衡器”(Server-Side Load Balancer)。从图中能够清晰地看到客户端负载均衡器的特点,也是它与服务端负载均衡器的关键差别所在:客户端均衡器是和服务实例一一对应的,而且与服务实例并存于同一个进程之内。这个特点能为它带来很多好处,如:
- 均衡器与服务之间信息交换是进程内的方法调用,不存在任何额外的网络开销。
- 不依赖集群边缘的设施,所有内部流量都仅在服务集群的内部循环,避免了出现前文那样,集群内部流量要“绕场一周”的尴尬局面。
- 分散式的均衡器意味着天然避免了集中式的单点问题,它的带宽资源将不会像集中式均衡器那样敏感,这在以七层均衡器为主流、不能通过 IP 隧道和三角传输这样方式节省带宽的微服务环境中显得更具优势。
- 客户端均衡器要更加灵活,能够针对每一个服务实例单独设置均衡策略等参数,访问某个服务,是不是需要具备亲和性,选择服务的策略是随机、轮询、加权还是最小连接等等,都可以单独设置而不影响其它服务。
- ……
但是,客户端均衡器也不是银弹,它得到上述诸多好处的同时,缺点同样也是不少的:
- 它与服务运行于同一个进程之内,意味着它的选型受到服务所使用的编程语言的限制,譬如用 Golang 开发的微服务就不太可能搭配 Spring Cloud Load Balancer 来使用,要为每种语言都实现对应的能够支持复杂网络情况的均衡器是非常难的。客户端均衡器的这个缺陷有违于微服务中技术异构不应受到限制的原则。
- 从个体服务来看,由于是共用一个进程,均衡器的稳定性会直接影响整个服务进程的稳定性,消耗的 CPU、内存等资源也同样影响到服务的可用资源。从集群整体来看,在服务数量达成千乃至上万规模时,客户端均衡器消耗的资源总量是相当可观的。
- 由于请求的来源可能是来自集群中任意一个服务节点,而不再是统一来自集中式均衡器,这就使得内部网络安全和信任关系变得复杂,当攻破任何一个服务时,更容易通过该服务突破集群中的其他部分。
- 服务集群的拓扑关系是动态的,每一个客户端均衡器必须持续跟踪其他服务的健康状况,以实现上线新服务、下线旧服务、自动剔除失败的服务、自动重连恢复的服务等均衡器必须具备的功能。由于这些操作都需要通过访问服务注册中心来完成,数量庞大的客户端均衡器一直持续轮询服务注册中心,也会为它带来不小的负担。
- ……
地域与区域
借助前文已经铺设好的上下文场景,笔者想再谈一个与负载均衡相关,但又不仅仅应用于负载均衡的概念: 地域 与 区域 。你是否有注意到在微服务相关的许多设施中,都带有着 Region、Zone 参数,如前文中提到过的服务注册中心 Eureka 的 Region、Zone、边车代理 Envoy 中的 Region、Zone、Sub-zone,如果你有云计算 IaaS 的使用经历,也会发现几乎所有云计算设备都有类似的概念。Region 和 Zone 是公有云计算先驱亚马逊 AWS 提出的概念,它们的含义是指:
-
Region 是 地域 的意思,譬如华北、东北、华东、华南,这些都是地域范围。面向全球或全国的大型系统的服务集群往往会部署在多个不同地域,譬如本节开头列举的案例场景,大型系统就是通过不同地域的机房来缩短用户与服务器之间的物理距离,提升响应速度,对于小型系统,地域一般就只在异地容灾时才会涉及到。需要注意,不同地域之间是没有内网连接的,所有流量都只能经过公众互联网相连,如果微服务的流量跨越了地域,实际就跟调用外部服务商提供的互联网服务没有任何差别了。所以集群内部流量是不会跨地域的,服务发现、负载均衡器默认也是不会支持跨地域的服务发现和负载均衡。
-
Zone 是 区域 的意思,它是 可用区域 (Availability Zones)的简称,区域指在地理上位于同一地域内,但电力和网络是互相独立的物理区域,譬如在华东的上海、杭州、苏州的不同机房就是同一个地域的几个可用区域。同一个地域的可用区域之间具有内网连接,流量不占用公网带宽,因此区域是微服务集群内流量能够触及的最大范围。但你的应用是只部署在同一区域内,还是部署到几个不同可用区域中,要取决于你是否有做异地双活的需求,以及对网络延时的容忍程度。
-
可用区域对应于城市级别的区域的范围,一些场景中仍是过大了一些,即使是同一个区域中的机房,也可能存在具有差异的不同子网络,所以在部分微服务框架也提供了 Group、Sub-zone 等做进一步的细分控制,这些参数的意思通常是加权或优先访问同一个子区域的服务,但如果子区域中没有合适的,仍然会访问到可用区域中的其他服务。
-
地域和区域原本是云计算中的概念,对于一些中小型的微服务系统,尤其是非互联网的企业信息系统,很多仍然没有使用云计算设施,只部署在某个专有机房内部,只为特定人群提供服务,这就不需要涉及地理上地域、区域的概念了。此时完全可以自己灵活延拓 Region、Zone 参数的含义,达到优化虚拟化基础设施流量的目的。譬如,将服务发现的区域设置与 Kubernetes 的标签、选择器配合,实现内部服务请求其他服务时,优先使用同一个 Node 中提供的服务进行应答,以降低真实的网络消耗。
服务器 vs 客户端
Summary
- 服务器静态的服务节点。需要额外的一套部署服务。需要明确的在配置中定义各种服务的配置,就比如 Nginx Documents
- 客户端的优势恰恰就是灵活,通过服务发现机制,在本地获取可用的服务,并且进行负载均衡,A 服务对 B 服务的均衡策略和 C 服务对 B 服务的均衡策略可以是完全不同的。
网关路由
网关(Gateway)这个词在计算机科学中,尤其是计算机网络中很常见,它用来表示位于内部区域边缘,与外界进行交互的某个物理或逻辑设备,譬如你家里的路由器就属于家庭内网与互联网之间的网关。
网关的职责
在单体架构下,我们一般不太强调“网关”这个概念,为各个单体系统的副本分发流量的负载均衡器实质上承担了内部服务与外部请求之间的网关角色。在微服务环境中,网关的存在感就极大地增强了,甚至成为了微服务集群中必不可少的设施之一。其中原因并不难理解:微服务架构下,每个服务节点都可能由不同团队负责,都有着自己独立的、互不相同的接口,如果服务集群缺少一个统一对外交互的代理人角色,那外部的服务消费者就必须知道所有微服务节点在集群中的精确坐标(在服务发现 中解释过“服务坐标”的概念),这样,消费者不仅会受到服务集群的网络限制(不能确保集群中每个节点都有外网连接)、安全限制(不仅是服务节点的安全,外部自身也会受到如浏览器 同源策略 的约束)、依赖限制(服务坐标这类信息不属于对外接口承诺的内容,随时可能变动,不应该依赖它),就算是调用服务的程序员,自己也不会愿意记住每一个服务的坐标位置来编写代码。由此可见,微服务中网关的首要职责就是作为统一的出口对外提供服务,将外部访问网关地址的流量,根据适当的规则路由到内部集群中正确的服务节点之上,因此,微服务中的网关,也常被称为“服务网关”或者“API 网关”,微服务中的网关首先应该是个路由器,在满足此前提的基础上,网关还可以根据需要作为流量过滤器来使用,提供某些额外的可选的功能,譬如安全、认证、授权、限流、监控、缓存,等等(这部分内容在后续章节中有专门讲解,这里不会涉及)。简而言之:
网关 = 路由器(基础职能) + 过滤器(可选职能)
针对“路由”这个基础职能,服务网关主要考量的是能够支持路由的“网络协议层次”和“性能与可用性”两方面的因素。网络协议层次是指 负载均衡 中介绍过的四层流量转发与七层流量代理,
Title
仅从技术实现角度来看,对于路由这项工作,负载均衡器与服务网关在实现上是没有什么差别的,很多服务网关本身就是基于老牌的负载均衡器来实现的,譬如基于 Nginx、HAProxy 开发的 Ingress Controller,基于 Netty 开发的 Zuul 2.0 等;但从目的角度看,负载均衡器与服务网关会有一些区别,具体在于前者是为了根据均衡算法对流量进行平均地路由,后者是为了根据流量中的某种特征进行正确地路由。
网关必须能够识别流量中的特征,这意味着网关能够支持的网络通信协议的层次将会直接限制后端服务节点能够选择的服务通信方式。如果服务集群只提供像 Etcd 这样直接基于 TCP 的访问的服务,那只部署四层网关便可满足,网关以 IP 报文中源地址、目标地址为特征进行路由;如果服务集群要提供 HTTP 服务的话,那就必须部署一个七层网关,网关根据 HTTP 报文中的 URL、Header 等信息为特征进行路由;如果服务集群还要提供更上层的 WebSocket、SOAP 等服务,那就必须要求网关同样能够支持这些上层协议,才能从中提取到特征。
举个例子,以下是一段基于 SpringCloud 实现的 Fenix’s Bookstore 中用到的 Netflix Zuul 网关的配置,Zuul 是 HTTP 网关,/restful/accounts/ **
和 /restful/pay/**
是 HTTP 中 URL 的特征,而配置中的 serviceId
就是路由的目标服务。
routes:
account:
path: /restful/accounts/**
serviceId: account
stripPrefix: false
sensitiveHeaders: "*"
payment:
path: /restful/pay/**
serviceId: payment
stripPrefix: false
sensitiveHeaders: "*"
今天围绕微服务的各种技术仍处于快速发展期,笔者不提倡针对每一种工具、框架本身去记忆配置细节,就是无须纠结上面代码清单中配置的确切写法、每个指令的含义。如果你从根本上理解了网关的原理,参考一下技术手册,很容易就能够将上面的信息改写成 Kubernetes Ingress Controller、Istio VirtualServer 或者其他服务网关所需的配置形式。
网关的另一个主要关注点是它的性能与可用性。由于网关是所有服务对外的总出口,是流量必经之地,所以网关的路由性能将导致全局的、系统性的影响,如果经过网关路由会有 1 毫秒的性能损失,就意味着整个系统所有服务的响应延迟都会增加 1 毫秒。网关的性能与它的工作模式和自身实现算法都有关系,但毫无疑问工作模式是最关键的因素,如果能够采用 DSR 三角传输模式,原理上就决定了性能一定会比代理模式来的强(DSR、IP Tunnel、NAT、代理等这些都是网络基础知识,笔者曾在介绍 负载均衡器 时详细讲解过)。不过,因为今天 REST 和 JSON-RPC 等基于 HTTP 协议的服务接口在对外部提供的服务中占绝对主流的地位,所以我们所讨论的服务网关默认都必须支持七层路由,通常就默认无法直接进行流量转发,只能采用代理模式。在这个前提约束下,网关的性能主要取决于它们如何代理网络请求,也即它们的网络 I/O 模型,下面笔者正好借这个场景介绍一下网络 I/O 的基础知识。
网络 I/O 模型
回到服务网关的话题上,有了网络 I/O 模型的知识,我们就可以在理论上定性分析不同七层网关的性能差异了。七层服务网关处理一次请求代理时,包含了两组网络操作,分别是作为服务端对外部请求的应答,和作为客户端对内部服务的请求,理论上这两组网络操作可以采用不同的模型去完成,但一般来说并没有必要这样做。
以 Zuul 网关为例,在 Zuul 1.0 时,它采用的是阻塞 I/O 模型来进行最经典的“一条线程对应一个连接”(Thread-per-Connection)的方式来代理流量,采用阻塞 I/O 意味着它会有线程休眠,就有上下文切换的成本,所以如果后端服务普遍属于计算密集型(CPU Bound,可以通俗理解为服务耗时比较长,主要消耗在 CPU 上)时,这种模式能够相对节省网关的 CPU 资源,但如果后端服务普遍都是 I/O 密集型(I/O Bound,可以理解服务都很快返回,主要消耗在 I/O 上),它就会由于频繁的上下文切换而降低性能。在 Zuul 的 2.0 版本,最大的改进就是基于 Netty Server 实现了异步 I/O 模型来处理请求,大幅度减少了线程数,获得了更高的性能和更低的延迟。根据 Netflix 官方自己给出的数据,Zuul 2.0 大约要比 Zuul 1.0 快上 20%左右。甚至还有一些网关,支持自行配置,或者根据环境选择不同的网络 I/O 模型,典型的就是 Nginx,可以支持在配置文件中指定 select、poll、epoll、kqueue 等并发模型。
网关的性能高低一般只去定性分析,要定量地说哪一种网关性能最高、高多少是很困难的,就像我们都认可 Chrome 要比 IE 快,但脱离了具体场景,快上多少就很难说的清楚。尽管笔者上面引用了 Netflix 官方对 Zuul 两个版本的量化对比,网络上也有不少关于各种网关的性能对比数据,但要是脱离具体应用场景去定量地比较不同网关的性能差异还是难以令人信服,不同的测试环境和后端服务都会直接影响结果。
网关还有最后一点必须关注的是它的可用性问题。任何系统的网络调用过程中都至少会有一个单点存在,这是由用户只通过唯一的一个地址去访问系统所决定的。即使是淘宝、亚马逊这样全球多数据中心部署的大型系统也不例外。对于更普遍的小型系统(小型是相对淘宝这些而言)来说,作为后端对外服务代理人角色的网关经常被视为整个系统的入口,往往很容易成为网络访问中的单点,这时候它的可用性就尤为重要。由于网关的地址具有唯一性,就不像之前 服务发现 那些注册中心那样直接做个集群,随便访问哪一台都可以解决问题。为此,对网关的可用性方面,我们应该考虑到以下几点:
- 网关应尽可能轻量,尽管网关作为服务集群统一的出入口,可以很方便地做安全、认证、授权、限流、监控,等等的功能,但给网关附加这些能力时还是要仔细权衡,取得功能性与可用性之间的平衡,过度增加网关的职责是危险的。
- 网关选型时,应该尽可能选择较成熟的产品实现,譬如 Nginx Ingress Controller、KONG、Zuul 这些经受过长期考验的产品,而不能一味只考虑性能选择最新的产品,性能与可用性之间的平衡也需要权衡。
- 在需要高可用的生产环境中,应当考虑在网关之前部署负载均衡器或者等价路由器 (ECMP),让那些更成熟健壮的设施(往往是硬件物理设备)去充当整个系统的入口地址,这样网关也可以进行扩展了。
BFF 网关
提到网关的唯一性、高可用与扩展,笔者顺带也说一下近年来随着微服务一起火起来的概念“BFF”(Backends for Frontends)。这个概念目前还没有权威的中文翻译,在我们讨论的上下文里,它的意思是,网关不必为所有的前端提供无差别的服务,而是应该针对不同的前端,聚合不同的服务,提供不同的接口和网络访问协议支持。譬如,运行于浏览器的 Web 程序,由于浏览器一般只支持 HTTP 协议,服务网关就应提供 REST 等基于 HTTP 协议的服务,但同时我们亦可以针对运行于桌面系统的程序部署另外一套网关,它能与 Web 网关有完全不同的技术选型,能提供出基于更高性能协议(如 gRPC)的接口来获得更好的体验。在网关这种边缘节点上,针对同一样的后端集群,裁剪、适配、聚合出适应不一样的前端的服务,有助于后端的稳定,也有助于前端的赋能。
图 7-3 BFF 网关
长连接负载均衡
长连接负载均衡粒度
与短连接每次请求都做负载均衡策略不同,长连接不光有请求粒度的负载均衡,还有连接粒度的负载均衡。
请求粒度负载均衡的实现方式是一个客户端与每个服务端都建立连接,发送请求时按照某种负载均衡策略选择一个服务端进行请求;连接粒度的负载均衡则是客户端在建立连接时按照某种负载均衡策略挑选一个节点进行建连,后续请求都发往这个节点。
如何选择主要是考量单个服务端可能的连接数量,如果连接数远不是瓶颈的时候(个人认为万级以下),可考虑请求粒度,否则连接粒度的负载均衡策略更佳。
举个例子,Dubbo 一个 Provider 节点和来自订阅 Consumer 的所有节点都建立了连接,前提是 Dubbo 一个 Provider 基本不太会可能被几万个节点消费,所以 Dubbo 可以做请求粒度的长连接负载均衡。但如果是 Nacos,所有需要服务发现的机器都要和 Nacos 服务端建立连接,长连接数量就和公司服务器数量级相关,规模大的情况,几万、上十万、百万也是有可能的,所以如果 Nacos 也像 Dubbo 那样设计,就无法支撑大规模服务发现了。
连接粒度的负载均衡
对于长连接,连接粒度的负载均衡问题遇到的更多,所以这里着重说明下。
连接数均衡
由于连接建立之后,除非异常不会断开,所以问题就来了,如果某一个节点的连接数相比较其他节点要多出很多,这种就属于不均衡了。出现这种问题的情况最常见的就是服务端发布(重启)。重启时服务不可用,该节点原先的连接会断开,找到存活的节点进行连接,当这台服务起来时,它的连接数将非常少。如果是一轮发布,最先发布的机器最后连接数最多,最后发布的连接数最少。
这种情况下,我们可以调整建连的负载均衡算法为最小连接数模式,当服务重启完成后,后续的连接就能全部连接到此节点。
但这个方法并不总是奏效,因为服务在重启时,断开的连接已经和其他节点建立了连接。
这时我们可能需要额外的均衡手段,如定时从全局视角看各个节点的连接数是否均衡,如果不均衡则要断开最多连接的节点,直到平衡。
这里我们的客户端需要对连接的断开处理特别小心,当然我觉得这是必须的。
但也要说明一点,如果连接不是长时间保持的,额外的均衡手段可能就不需要了,等一会就自然平衡了。这种发生在什么情况呢?比如公网的长连接,客户端的网络情况没内网那么好,经常断开连接,这就相当于帮我们自动平滑连接了。
如果是内网服务,连接能一直保持,额外的平衡手段就显得有必要了。
服务器规格不同
我们通常为了单机能保持更多的长连接,一般会选用物理机部署服务,有时候各个物理机规格不统一,如果我们的均衡手段一视同仁,每个节点连接数差不多,规格差的服务器可能压力就比其他机器大。
所以建连的负载均衡算法和额外的均衡手段也要考虑服务器规格,可以简单地把服务器规格与当前的连接数抽象为一个权重,客户端建连时加权再选择。
扩容无效问题
我们的长连接服务理应是可水平扩容的,连接数变多,加机器就可以,我们的设计大多也是如此。
但有时候可能不小心,导致水平扩容无效。
举个例子,还是注册中心,假设有3个节点的注册中心集群,此时有 1w 个客户端连上来,订阅了各种各样的服务,由于客户端的数量远远大于注册中心节点,所以基本可以认为每个注册中心节点订阅的服务是差不多的,近似每个服务的变更,每个注册中心节点都要处理,CPU 消耗自然就多了。如果把注册中心节点扩容为5台,其实每台服务只是少了一点连接,但依然每个注册中心节点还是近乎要处理所有的服务变更。
这种情况下就要审视长连接服务设计的是否合理,一般采取分层的思想,长连接这层服务只专注推送,一般称为通道层或者 session 层,它并不复杂复杂的计算逻辑。
如果设计有问题,短时间又没法修改,可以试试按照服务订阅者的名字路由到特定的服务端节点,保证同一个 Conusmer 只连同一个注册中心节点,这样某服务变更时,该节点只需要计算一次,就可以推送给所有 Conusmer,运气好的话,其他节点都不用计算。
Nginx 长连接负载均衡
当客户端通过浏览器访问 HTTP 服务器时,HTTP 请求会通过 TCP 协议与 HTTP 服务器建立一条访问通道,当本次访问数据传输完毕后,该 TCP 连接会立即被断开,由于这个连接存在的时间很短,所以 HTTP 连接也被称为短连接。
在 HTTP/1.1 版本中默认开启 Connection:keep-alive,实现了 HTTP 协议的长连接,可以在一个 TCP 连接中传输多个 HTTP 请求和响应,减少了建立和关闭 TCP 连接的消耗和延迟,提高了传输效率。网络应用中,每个网络请求都会打开一个 TCP 连接,基于上层的软件会根据需要决定这个连接的保持或关闭。例如,FTP 协议的底层也是 TCP,是长连接。
默认配置下,HTTP 协议的负载均衡与上游服务器组中被代理的连接都是 HTTP/1.0 版本的短连接。Nginx 的连接管理机制如下图所示。
图:Nginx 连接管理机制
相关说明如下。
Nginx 启动初始化时,每个 Nginx 工作进程(Worker Process)会生成一个由配置指令 worker_connections 指定大小的可用连接池(free_connection pool)。工作进程每建立一个连接,都会从可用连接池中分配(ngx_get_connection)到一个连接资源,而关闭连接时再通知(ngx_free_connection)可用连接池回收此连接资源。
客户端向 Nginx 发起 HTTP 连接时,Nginx 的工作进程获得该请求的处理权并接受请求,同时从可用连接池中获得连接资源与客户端建立客户端连接资源。
Nginx 的工作进程从可用连接池获取连接资源,并与通过负载均衡策略选中的被代理服务器建立代理连接。
默认配置下,Nginx 的工作进程与被代理服务器建立的连接都是短连接,所以获取请求响应后就会关闭连接并通知可用连接池回收此代理连接资源。
Nginx 的工作进程将请求响应返回给客户端,若该请求为长连接,则保持连接,否则关闭连接并通知可用连接池回收此客户端连接资源。
Nginx 能建立的最大连接数是 worker_connections×worker_processes。而对于反向代理的连接,最大连接数是 worker_connections×worker_processes/2,但是其会占用与客户端及与被代理服务器建立的两个连接。
在高并发的场景下,Nginx 频繁与被代理服务器建立和关闭连接会消耗大量资源。Nginx 的 upstream_keepalive 模块提供与被代理服务器间建立长连接的管理支持,该模块建立了一个长连接缓存,用于管理和存储与被代理服务器建立的连接。Nginx 长连接管理机制如下图所示。
图:Nginx 长连接管理机制
相关说明如下。
当 upstream_keepalive 模块初始化时,将建立按照 upstream 指令域中的 keepalive 指令设置大小的长连接缓存(Keepalive Connect Cache)池。
当 Nginx 的工作进程与被代理服务器新建的连接完成数据传输时,其将该连接缓存在长连接缓存池中。
Info
当工作进程与被代理服务器有新的连接请求时,会先在长连接缓存池中查找符合需求的连接,如果存在则使用该连接,否则创建新连接。
对于超过长连接缓存池数量的连接,将使用最近最少使用(LRU)算法进行关闭或缓存。
长连接缓存池中每个连接最大未被激活的超时时间由 upstream 指令域中 keepalive_timeout 指令设置,超过该指令值时间未被激活的连接将被关闭。
长连接缓存池中每个连接可复用传输的请求数由 upstream 指令域中 keepalive_requests 指令设置,超过该指令值复用请求数的连接将被关闭。
Nginx 与被代理服务器间建立的长连接是通过启用 HTTP/1.1 版本协议实现的。由于 HTTP 代理模块默认会将发往被代理服务器的请求头属性字段 Connection 的值设置为 Close,因此需要通过配置指令清除请求头属性字段 Connection 的内容。