本文原作者为Matt Klein,为envoy的工程师。发表于2017年12月。因原文英文写作和其它原因,本人觉得有必要分享。但翻译后期发现该文已于2018年1月被人翻译。思考再三,决定发布自己的翻译版本。原文较长,我分为三个部分便于大家阅读。此为第一篇
什么是网络负载均衡与代理
负载均衡是构建可靠的分布式系统所需要的核心概念之一。维基百科将负载均衡定义为:
在计算机中,负载均衡可改善跨越多个计算资源(例如计算机与集群、网络链接、中央处理单元(cpu)或磁盘驱动器)的工作负载分配。负载均衡旨在优化资源l利用率,最大化吞吐量,最小化响应时间,并避免任何单点资源过载。通过冗余机制,使用负载均衡组建可以提高可靠性和可用性。负载均衡通常涉及专用软件或硬件,例如多层交换机或域名系统服务进程。
以上定义适用于计算机的所有领域(包括网络)。操作系统使用负载均衡来跨越物理处理器调度任务。容器协调器(如Kubernetes,k8s)使用负载均衡来跨越计算集群调度任务,网络负载均衡器使用负载均衡技术来跨越可用后端调度网络任务。本文仅涵盖(介绍)网络负载均衡技术。
图1 网络负载均衡概述
图1描述了网络负载均衡的核心用途。一些客户端正在向后端(服务器)请求资源。负载均衡器位于客户端和后端之间,并执行几项关键任务:
- 服务发现:系统中有哪些后端可用?它们的地址是什么?(即负载均衡器应该如何与后端服务器通信及协调调度)
- 健康检查:目前哪些后端健康且可以接受请求?
- 负载均衡:应该使用什么样的算法来分配请求资源给健康的后端
在分布式系统中,正确的使用负载均衡可以带来如下的好处:抽象命名:
客户端并不需要知道或了解负载均衡器代理的后端,由负载均衡器来管理和分配任务给后端(服务发现)。负载均衡通过预定义机制来解决。(预定义机制包括内置库和众所周知的DNS / IP /端口)池化,然后可以将名称解析行为委托给负载均衡器。(下文更详细地讨论)。译注:可以理解为VIP+NAT技术处理机制。
容错:
通过健康检查机制和各种算法技术,负载均衡器可以有效地屏蔽故障或系统过载的后端(译注: 在资源池中挂起或剔除)。这意味着系统(网络)管理员通常可以在空闲时修复异常的后端,而无需紧急处理故障后端。
成本和性能优势:
分布式系统网络很少是同质的(即复杂多变)。该系统可能跨越多个网段和区域。在一个区域内,网络流量相对宽松。而在另一个区域内,超额认购(流量超标)成为常态。(这里,网络过载或空余是指通过网卡消耗的带宽数量占不同路由器间的可用带宽的百分比)。智能负载均衡尽可能地保持一个区域内的请求流量,从而提高性能(减少延迟)并降低整体系统成本(区域之间所需的带宽和光纤更少)。译注:后端A在X区,带宽100M,后端B在Y区,带宽1000M。
负载均衡器与代理
按照当前业界分类,负载均衡通常根据OSI模型分为4层和7层负载。简称L4&&L7。但这种分类并不准确。例如L4负载均衡也支持TLS termination,那么它属于L4还是L7负载设备呢?不过,我们无需刻意较真,目前仍然按照当前流行的分类继续讨论。本文中,负载均衡与代理在用途上理解为等同的。可能有些人会争辩说,当负载均衡作为嵌入式客户端库的一部分时负载均衡器实际上并非用于代理功能。但是,我认为这种区分会给已经令人困惑的话题带来不必要的复杂性。负载均衡器拓扑的类型将在下面详细讨论,这篇文章将嵌入式负载均衡器拓扑视为代理的特殊情况; 应用程序通过嵌入式库进行代理,该库提供了应用程序进程之外的负载均衡器相同的抽象。
L4(连接/会话)负载均衡
图2:TCP L4终端负载均衡
图2显示了传统的L4 TCP负载均衡器。在这种情况下,客户端与负载均衡器建立TCP连接。负载均衡器直接响应SYN,然后选择后端应答,并与后端建立新的TCP连接(即发送新的SYN)。该图的细节并不重要,将在下面专门讨论L4负载平衡的部分中详细讨论。这里的关键点是L4负载均衡器通常仅在L4 TCP / UDP层 (连接/会话)级别运行。因此,负载均衡器在前后端分别传输数据,并确保同一会话的数据在同一后端结束。L4负载均衡器不知道(关心)数据的具体内容。这些数据可以是HTTP,Redis,MongoDB或任何其他应用程序协议。
L7(应用程序)负载均衡
L4负载均衡因逻辑简单被广泛使用。那么是不是L4负载平衡就满足所有需求了?当时不是,L7(应用程序)负载平衡用于处理L4不具备的功能,以下面L4特定案例为例:
两个GRPC 和 HTTP2客户端希望通过一个L4负载均衡器连接与后端通信。
L4负载均衡器为每个传入的TCP连接建立单个传出TCP连接,从而产生两个传入和两个传出连接。即会话不可复用。资源消耗较高。
然而,客户端A通过其连接每分钟发送1个请求(RPM),而客户端B通过其连接发送每秒50个请求(RPS)。
这时候,选择处理客户端A的后端将处理比选择处理客户端B的后端大约少3000倍的负载。这种问题显然破坏了负载均衡存在的意义。还要注意,这个问题发生在多路复用、会话保持(长连接)时产生的影响。(多路复用意味着通过单个L4连接、发送并行应用程序请求。而会话保持意味着在没有活动请求时不关闭连接)。所有现代协议(新应用)为提高效率都在支持多路复用和会话保持(创建连接成本很高,特别是当使用TLS加密连接时),因此L4负载均衡处理明显不足。此问题均由L7负载均衡器来解决。
图3:HTTP/2 L7终端负载均衡
图3显示了L7 HTTP / 2负载均衡器。在这种情况下,客户端(client)与负载均衡器(Load balancer)建立单个HTTP / 2 TCP连接。然后负载均衡器与两个后端建立连接。当客户端向负载均衡器发送两个HTTP / 2请求数据时,请求数据流1被发送到后端1(backend1),请求数据流2被发送到后端2(backend2).因此,即使多路复用具有截然不同的请求,负载响应客户端后也将在后端进行有效地均衡分配。这就是L7负载平衡对现代协议如此重要的原因。(L7负载均衡由于能够检查应用程序流量而产生巨大的额外优势,将在下面详细介绍)。
L7负载均衡和OSI模型
如前文所述,使用OSI模型来描述和归类负载均衡功能是有问题的。原因是L7,至少如OSI模型所描述的那样,本身包含多个离散的负载均衡抽象层。例如,对于HTTP协议的流量,请考虑以下子层:
- 可选的传输层安全性(TLS)。请注意,网络人员会争论TLS到底属于哪个OSI层。为了便于讨论,我们将考虑TLS为 L7。
- HTTP物理协议(HTTP / 1或HTTP / 2)。
- HTTP逻辑协议(HTTP标头,HTTP数据内容和Trailers即附加header)。
- 消息传递协议(gRPC,REST等)。
L7负载均衡器的复杂性造成可以提供与上述每个子层相关的特征。另外一点,L7负载均衡器可能只有一小部分功能将其置于L7层中。简而言之,从功能比较的角度来看,L7负载均衡器的模型逻辑要比L4层复杂得多。(当然,上文刚刚介绍了HTTP; Redis,Kafka,MongoDB等都是受益于L7负载平衡的L7应用程序协议的例子)。
负载均衡器功能
在本节中,我将简要总结负载均衡器提供的高级功能。但并非所有负载均衡器(译注:具体到某软硬件如F5,NGINX)都提供所有功能。
服务发现
服务发现是负载均衡器确定可用后端集的过程。方法多种多样,一些例子包括:
- 静态配置文件。
- DNS。
- Zookeeper,Etcd,Consul等
- Envoy的universal data plane API(译注:统一数据平台接口)
健康检查
健康检查是负载均衡器确定后端是否可用并分配资源的过程。健康检查通常分为两类:
- 主动:负载均衡器定期发送ping(例如,对健康检查端点的HTTP请求)到后端,并使用它来衡量后端运行情况。
- 被动:负载均衡器从主数据流中检测健康状态。例如,如果发现一行(译注:原文为in a row,前译者翻译为一段时间)存在三次连接错误,则L4负载均衡器会判定后端是不健康的。如果一行中有三个HTTP 503响应代码,则L7负载均衡器会判断后端是否运行状况不佳。
负载均衡
是的,负载均衡器必须真实平衡负载!提供一组健康的后端,如何选择后端来提供连接或请求?要依靠负载均衡核心算法。从简单的算法:如随机选择和循环(轮询、加权轮询),到考虑可变延迟和后端负载分析的更复杂算法。考虑到其性能和简单性,最受欢迎的负载平衡算法之一被称为2个最小请求负载平衡功能。
粘性会话
某些应用程序(业务场景),同一会话的请求到达相同的后端非常重要。这可能与缓存,临时复杂构造状态等有关。会话的定义各不相同,可能包括HTTP cookie,客户端连接的属性或其他属性等。许多L7负载均衡器都支持粘性会话。值得一提的是,粘性会话本质上很脆弱(被托管会话的后端可能会宕机),因此在设计依赖它们的系统时要小心。
译注:典型L7负载的会话保持功能。TLS截止
TLS的主题及其在边界服务和保护服务到服务通信中的作用值得一提。据说,许多L7负载均衡器进行大量的TLS处理,包括终止,证书验证和一致性,使用SNI的证书服务等。
观测(可视化)
网络本质上是不可靠的,负载均衡器通常需要负责导出统计数据,跟踪和输出日志,帮助分析人员找出问题所在,以便他们能够解决问题。负载均衡器的可观察性输出差异很大。最先进的负载均衡器提供丰富的输出,包括数字统计,分布式跟踪和可自定义的日志记录。需要指出的是,可视化数据越丰富, 负载均衡器就必须消耗自己的部分资源来完成其工作。如果数据输出(展示)的好处大大超过了相对较小的性能损失,那么是值得的。
安全和DoS防御
尤其是在边界部署拓扑(见下文),负载均衡器通常实现的各种安全功能,包括速率限制,认证和DoS防御(例如,IP地址标记和识别,tarpitting(过滤)等)。
配置和控制面板
在大型项目的部署和实施中,配置负载均衡器,这可能成为一项重大任务。通常,配置负载平衡器的系统称为“控制面板”,并且在其实现中变化很大。
负载均衡器拓扑的类型以下每种拓扑适用于L4和L7负载均衡
中间代理
图4:中间代理负载均衡拓扑
图4中所示的中间代理拓扑可能负载均衡最常用的方法。此类别包括Cisco,Juniper,F5等硬件设备; 亚马逊的ALB和NLB以及谷歌的云负载均衡器等云软件解决方案; 和纯软件自托管解决方案,如HAProxy,NGINX和Envoy。中间代理解决方案好处使用方便。通常,用户通过DNS(译注:或者配置VIP)连接到负载均衡器,无需担心其他任何问题。中间代理解决方案的缺点是代理(即使是群集)是单点故障,还有扩展瓶颈问题。中间代理通常也是黑盒模式,使维护变得困难。一旦出现问题,故障是否在客户端?在物理网络?在中间代理负载均衡本身?甚至在后端?这很难说。边界代理
图5:边界代理负载均衡拓扑
边界代理拓扑如图5所示,实际上只是中间代理拓扑的一种变体,可以通过Internet访问负载均衡器。在这种情况下,负载均衡器通常必须提供额外的“API网关”功能,例如TLS终端,速率限制,身份验证和复杂的流量路由。边界代理的优缺点与中间代理相同。需要注意的是,在面向Internet的大型分布式系统中部署专用边界代理通常是不可避免的。客户端通常需要使用服务所有者无法控制的任意网络(通常指互联网)通过DNS访问系统(以下部分中描述的嵌入式客户端库或跨斗车代理拓扑不能直接在客户端上运行)。另外,为了安全因素设置一个单独的安全网关是必要的,所有面向互联网的流量都必须先经过这个安全网关才能进入系统。如防火墙
嵌入式客户端库
图6:通过嵌入式客户端库进行负载均衡
为了避免中间代理拓扑中固有的单点故障和扩展问题,复杂的基础架构已经转向将负载均衡器通过库直接嵌入到服务中,如图6所示。在支持的特征中,库差别很大,但在这一类中最为人熟知和特征丰富的产品是Finagle,Eureka / Ribbon / Hystrix和gRPC(基于一个Google内部称作Stubby的系统)。基于库的解决方案的主要优势在于它将负载均衡器的所有功能完全分配给每个客户端,从而消除了之前描述的单点故障和扩展问题。基于库的解决方案的主要缺点是库必须以项目组织使用的每种语言实现。分布式架构正变得越来越“多语言”。在这种环境下,以多种不同语言重新实现极其复杂的网络库的成本可能会变得令人望而却步。最后,在大型服务架构中部署库升级可能会非常痛苦,因此很有可能在生产中同时运行库的许多不同版本。
挎斗车代理
译注: Sidecar-中文为(摩托车的)跨斗。如下图
图7:通过sidecar代理进行负载均衡
嵌入式客户端库负载均衡器拓扑的变体是图7中所示的挎斗车代理拓扑。近年来,这种拓扑结构已经被普及为“服务网格”。挎斗车代理背后的想法是,通过跳转到不同的进程而导致轻微的延迟损失,嵌入式方法的好处是在没有任何编程语言锁定的情况下获得。最受欢迎的挎斗车代理负载均衡器是Envoy,NGINX,HAProxy和Linkerd。
不同负载均衡器拓扑的总结和优缺点
- 中间代理拓扑通常是最容易实现和使用的负载均衡拓扑。缺点:易单点、扩展性差、黑盒操作。
- 边界代理拓扑类似于中间代理,优缺点雷同。
- 嵌入式客户端库拓扑提供了最佳性能和可伸缩性,但是需要以每种语言实现库以及需要跨所有服务升级库。
- 挎斗车代理拓扑的性能不如嵌入式客户端库拓扑,但不受任何限制。
总的来说,我认为挎斗车代理拓扑(服务网格)正在逐步取代所有其他拓扑以进行服务到服务的通信。但在流量进入服务网格之前,始终需要边界代理拓扑。
未完,待续