搜索
您的当前位置:首页正文

现代网络负载均衡与代理(上)

来源:二三娱乐

在本文中,我将尝试改变这种信息匮乏的情况,详细介绍现代网络负载均衡和代理。坦率地讲,这是一个非常大的话题,围绕它写一本书都不为过。为了在一定程度上控制本篇文章的篇幅,我试着化繁为简,对这一系列复杂的话题进行了提炼;根据读者的兴趣和反馈,我会考虑在后续帖子中详细介绍一些单独的话题。

介绍了一点我写作本文的背景之后,让我们开始正文吧!

什么是网络负载均衡与代理?

在计算机领域,负载均衡优化了多个计算资源间的工作量分布。这些计算资源包括计算机、计算机集群、网络连接、CPU或者硬盘等。负载均衡的目的是优化资源利用、最大化吞吐量、最小化响应时间,并且避免任何单个资源过载。通过冗余机制,使用多个负载均衡组件而不是单一组件可以增加可靠性和可用性。负载均衡通常涉及专门的软件或硬件,例如多层交换机或者域名系统服务器进程。

上述定义适用于所有计算机领域,而不仅仅是网络。操作系统使用负载均衡来调度物理处理器间的任务,容器编排工具(例如Kubernetes)使用负载均衡来调度计算机集群内的任务,网络负载均衡器使用负载均衡来调度可用后端间的网络任务。本文剩余部分将只涵盖网络负载均衡相关的内容。


<small style="margin: 0px; border: 0px; padding: 0px;">图1: 网络负载均衡概览</small>

图1高度概括了网络负载均衡。一些客户端从一些后端请求资源。负载均衡器位于客户端和后端之间,执行一些非常重要的任务:

  • 服务发现:系统中有哪些后端可用?它们的地址是什么?(例如,负载均衡器如何和它们通信?)
  • 状态检查:哪些后端现在可以接受请求?
  • 负载均衡:应该使用什么算法来均衡后端间的请求?

在分布式系统中合理使用负载均衡可以带来以下好处:

  • 抽象命名:客户端可以通过一个预定义的机制寻址到负载均衡器,然后将名称解析委托给负载均衡器,而不是每一个客户端都需要知道每一个后端的(有关服务发现的)信息。预定义机制包括内置库和众所周知的DNS/IP/端口地址,这些稍后都会详细讨论。
  • 容错性:通过状态检查和各种算法技术,负载均衡器可以有效绕开挂掉的或者过载的后端。这意味着,运维人员通常可以在他们空闲时修复挂掉的后端而不是必须将这作为一个紧急事件来处理。
  • 成本和性能方面的好处:分布式系统网络复杂多变。系统可能会包含多个网络区域和行政区。在一个区域内,网络可能相对过载。而不同区域间,网络通常又会有空余。(这里,网络过载或空余是指通过网卡消耗的带宽数量占不同路由器间的可用带宽的百分比)。智能负载均衡可以尽可能多地维持一个区域内的请求数量,这提高了性能(延迟降低)并且减少了整个系统的成本(不同区域间需要更少的宽带和光纤)。

负载均衡vs代理

谈到网络负载均衡器的时候,“负载均衡器”和“代理”这两个术语通常在行业内是差不多相同的意思。本文通常也会将这两个术语作为相同意思的词。(严格来说,并不是所有的代理都是负载均衡器,但是大部分代理将负载均衡作为首要功能)。

有人也许会争论说,当用嵌入式客户端库来完成负载均衡时,负载均衡器并不是一个代理。然而,我认为这个话题本来就十分容易令人混淆,而这种区分会为这个话题增加更多不必要的复杂性。下面会详细介绍负载均衡器的各种拓扑结构,但是本文仍将嵌入式负载均衡器拓扑结构作为一种特殊的代理;应用程序通过嵌入式库进行代理,这个库提供了作为一个独立于应用进程外的负载均衡器的所有相同的抽象。

L4(连接/session)负载均衡


<small style="margin: 0px; border: 0px; padding: 0px;">图2:TCP L4终端负载均衡</small>

图2展示了一个传统的L4 TCP负载均衡器。在这个例子中,客户端与负载均衡器建立TCP连接。负载均衡器终止连接(例如,直接响应SYN),选择一个后端,与这个后端建立一个新的TCP连接(例如,发送一个新的SYN)。示意图中的细节并不重要,会在后面关于L4负载均衡的章节详细讨论。

本章节的要点是,L4负载均衡器通常只操作L4层的TCP/UDP连接或session。因此,负载均衡器只是来回地搬运bytes,并确保来自同一个session的bytes发送到相同的后端。L4负载均衡器不清楚这些搬运的bytes组成的应用的任何细节。这些bytes可以是HTTP、Redis、MongoDB或者其他任何应用层协议。

L7(应用层)负载均衡

L4负载均衡很简单并且仍被广泛使用。L4负载均衡的哪些不足导致需要使用L7(应用层)负载均衡?可以参考下列L4具体例子:

  • 两个gRPC/HTTP2客户端通过一个L4负载均衡器来和后端通信。
  • L4负载均衡器针对每个收到的TCP连接都会建立一个单独外出的TCP连接,导致会有2个收到的连接和2个外出的连接。
  • 然而,客户端A通过它建立的连接每分钟发送1个请求(1 RPM),而客户端B通过它建立的连接每秒发送50个请求(50 RPS)。

在上述情景中,处理客户端A请求的后端比处理客户端B请求的后端处理的请求少了大约3000倍!这是一个非常重大的问题,也是违背负载均衡目标的首要问题。另外要注意到,这个问题在任何多路复用的长连接的协议中都会发生。(多路复用的意思是,通过单个L4连接并行发送应用请求。长连接的意思是,即使没有活跃请求也不关闭连接。)所有现代网络协议为了效率都会采用多路复用和长连接(通常建立连接的成本非常大,特别是连接使用TLS加密的时候),因此,随着时间的推移,L4负载均衡器调节负载失败的现象变得越来越明显。这个问题可以被L7负载均衡器修复。


<small style="margin: 0px; border: 0px; padding: 0px;">图3:HTTP/2 L7终端负载均衡</small>

图3展示了一个L7 HTTP/2负载均衡器。在这个例子中,客户端与负载均衡器建立了单个HTTP/2 TCP连接。负载均衡器然后建立了两个后端连接。当客户端向负载均衡器发送了两个HTTP/2信息流时,信息流1被送到后端1,而信息流2被送到后端2。因此,即使多路复用的客户端会发送各种不同的请求,后端的负载也会被很高效地均衡分配。这就是为什么L7负载均衡对于现代网络协议是这么的重要。(L7负载均衡能够监测应用流量,由此产生了非常多额外的好处,这些会在后面讲到)。

L7负载均衡和OSI模型

正如我在上面关于L4负载均衡的章节讲到的,使用OSI模型来描述负载均衡的特性是有问题的。至少如OSI模型描述的那样,L7自身就包含了其它层的负载均衡。例如,对于HTTP流量需要考虑以下子层:

  • 可选的传输层安全(Transport Layer Security,TLS)。网络领域人士对于TLS归属于OSI模型哪一层存在争议。在本文的讨论中,我们认为TLS属于L7。
  • HTTP物理层协议(HTTP/1或HTTP/2)。
  • HTTP逻辑层协议(协议头、数据体、协议尾)。
  • 通信协议(gRPC、REST等等)。

一个复杂的L7负载均衡器会提供与上述每一个子层相关的功能。而另外一个L7负载均衡器可能只会包含L7范畴内的一小部分功能。总之,从功能比较的视角来看,L7负载均衡器比L4复杂得多。(当然,本章节只涉及了HTTP;Redis、Kafka、MongoDB等都是受益于L7负载均衡的L7应用层协议)。

负载均衡器的功能

本节中,我将简要总结负载均衡器提供的高级功能。不是所有的负载均衡器都提供所有这些功能。

服务发现

服务发现是负载均衡器判断可用后端集合的过程。服务发现的方法各不相同,包括以下一些例子:

  • 静态配置文件。
  • NDS。
  • 、、等。
  • Envoy的。

健康检查

健康检查是负载均衡器判断后端是否服务可用的过程。健康检查通常分为两类:

  • 主动的:负载均衡器以固定时间间隔向后端发送ping请求(例如,每个健康检查的端点发送一个HTTP请求),并以此来判断健康状态。
  • 被动的:负载均衡器从主要的数据流中检测健康状态。例如,如果在一段时间内有三次连接错误,L4负载均衡器可能会认为这个后端是不健康的。如果在一段时间内有三次HTTP 503响应代码,L7负载均衡器可能会认为该后端是不健康的。

负载均衡

粘性session

在特定应用中,同一个session的请求到达同一个后端是非常重要的。这可能与缓存、临时创建的复杂状态等有关。session的定义很多,可能包括HTTP cookies、客户端连接属性或者其它一些属性。许多L7负载均衡器支持粘性session。另外,我要提醒你注意的是,session的粘性本质上是脆弱的(后端持有的session可能会消亡),因此建议在设计依赖粘性session的系统时保持谨慎。

TLS终端

观测性

正如我常说的:“观测性、观测性、观测性。”网络本质上是不可靠的,负载均衡器通常有责任导出统计数据、跟踪数据和日志,这些信息可以帮助运维人员发现问题从而修复这些问题。负载均衡器在可观测性输出结果上有很大不同。最高级的负载均衡器提供丰富的输出结果,包括数值统计、分布式跟踪信息和自定义日志。我要指出的是,为了增强功能而增加的观测性不是没有代价的;负载均衡器不得不做一些额外的工作来产出观测结果。然而,这些数据带来的好处远远超过了相对较小的性能影响。

安全性和DoS防御

配置控制层

更多内容

本节初步涉猎了负载均衡器提供的各种功能。其它的讨论可以查看下文中关于L7负载均衡器的章节。

负载均衡器拓扑结构类型

既然我已经高度概括了什么是负载均衡器、L4和L7负载均衡器的区别以及负载均衡器的功能总结,下面我将继续介绍部署了负载均衡器的各种分布式系统的拓扑结构。(下面的每种拓扑结构都适用于L4和L7负载均衡器。)

中间代理


<small style="margin: 0px; border: 0px; padding: 0px;">图4:中间代理型负载均衡拓扑结构</small>

边缘代理


<small style="margin: 0px; border: 0px; padding: 0px;">图5:边缘代理负载均衡拓扑结构</small>

图5所示的边缘代理拓扑结构只是中间代理拓扑结构的一个变种,其中负载均衡器通过Internet访问。在这个场景中,负载均衡器通常必须提供额外的“API网关”功能,例如TLS终端、速率限制、身份认证和复杂的流量路由。边缘代理的优点和缺点与中间代理一样。需要注意的是,通常在大型的面向互联网的分布式系统中部署专用的边缘代理是不可避免的。客户端通常会使用任意网络库经过DNS访问系统,但这些网络库不受客户端的控制(这使得嵌入式客户端库负载均衡器或下面章节会提到的sidecar代理拓扑结构负载均衡器直接在客户端上运行变得不切实际)。另外,为了安全原因设置一个单独的安全网关是值得的,所有面向互联网的流量都必须先经过这个安全网关才能进入系统。

嵌入式的客户端库


<small style="margin: 0px; border: 0px; padding: 0px;">图6:通过嵌入式客户端库的负载均衡</small>

尽管如此,对于那些能够限制使用的编程语言的种类并且克服升级库的痛苦的公司来说,上面提到的各种库还是非常成功的。

Sidecar代理


<small style="margin: 0px; border: 0px; padding: 0px;">图7:通过sidecar代理的负载均衡</small>

各种负载均衡器拓扑结构的优缺点

  • 中间代理拓扑结构是最容易使用的负载均衡拓扑结构。它的弊端是单点故障、扩展限制和黑箱操作端。
  • 边缘代理拓扑结构和中间代理类似,但通常还免不了会用到。
  • 嵌入式客户端库拓扑结构提供了最好的性能和可扩展性,但是需要用各种语言来实现这个库而且需要跨所有服务来升级这个库。
  • sidecar代理拓扑结构执行性能虽然不如嵌入式客户端库拓扑结构,但是不会受到其中的任何限制。

总之,我认为sidecar代理拓扑结构(服务网格)正逐渐取代所有其它用来进行服务间通信的拓扑结构。在流量进入服务网格之前需要使用边缘代理拓扑结构

Top