现代 络负载平衡和代理介绍

一、 络负载均衡和代理是什么?

图1: 络负载均衡架构图

图1是 络负载均衡的高层架构图。若干客户端正在访问若干后端服务,它们中间是一个负载均衡器;从高层看,负载均衡器完成以下功能:

  • 服务发现:系统中的哪些后端是可用的?它们的地址是多少(例如,负责均衡器如何和它们通信)?
  • 健康检查:当前哪些后端是健康的,可以接收请求?
  • 负载均衡:应该用什么算法将请求平衡地转发到健康的后端服务?
  • 负载均衡使用得当可以给分布式系统带来很多好处:

  • 命名抽象(Namingabstraction):客户端可以通过预设的机制访问LB,域名解析工作交给LB,这样每个客户端就无需知道每个后端(服务发现)。预设的机制包括内置库、众所周知的DNS/IP/port,接下来会详细讨论
  • 容错:通过健康检查和多种算法,LB可以将请求有效地路由到负载过高的后端。这意味着运维人员可以从容地修复异常的后端,而不用慌张
  • 成本和性能收益:分布式系统的 络很少是同构的(homogeneous)。系统很可能跨多个 络zone(可用区)和region(相隔较远的地理区域,这两者都是云计算术语,有严格定义,想进一步了解请自行搜索)。在每个zone内部, 络的利用率相对较低;zone之间,利用率经常达到上限。(这里利用率的衡量公式: 卡带宽/路由器之间带宽)。智能负载均衡可以最大限度地将请求流量保持在zone内部,既提高了性能(延迟降低),又减少了整体的系统成本(减少跨zone带宽及光纤成本)
  • 1.1、负载均衡器vs代理

    1.2、L4(会话/连接层)负载均衡

    现在,当业内讨论负载均衡的时候,所有解决方案通常分为两类:L4和L7。这两者分别对应OSI模型的4层和7层。不过我认为使用这个术语相当不幸,在后面讨论L7负载均衡的时候会明显看到这一点。OSI模型是一个很差的对负载均衡解决方案复杂度的近似,这些解决方案包含4层协议,例如TCP和UDP,但经常又包括一些OSI其他协议层的内容。比如,如果一个L4TCPLB同时支持TLStermination,那它现在是不是一个L7LB?

    图2:TCPL4termination负载均衡

    图2是传统的L4TCP负载均衡器。这种情况下,客户端建立一个TCP连接到LB。LB终止(terminate)这个连接(例如,立即应答SYN包),选择一个后端,然后建立一个新的TCP连接到后端(例如,发送一个新的SYN包)。不要太在意图中的细节,我们在后面章节会专门讨论L4负载均衡。

    本节想说明的是,典型情况下,L4负载均衡器只工作在
    L4TCP/UDPconnection/session。因此,LB在双向来回转发字节,保证属于同一session的字节永远落到同一后端。L4LB不感知其转发字节所属应用的任何细节。这些字节可能是HTTP、Redis、MongoDB,或者任何其他应用层协议。

    1.3、L7(应用层)负载均衡

    L4负载均衡很简单,应用范围也很广。那么,相比于L7(应用层)负载均衡,L4有哪些缺点?设想如下L4特殊场景:

  • 两个gRPC/HTTP2客户端想连接到后端,因此它们通过L4LB建立连接
  • L4LB为每个(从客户端)进来的连接建立一个出去的(到后端)的连接,因此最终由两个进来的连接和两个出去的连接
  • 客户端A的连接每分钟发送1个请求,而客户端B的连接每秒发送50个请求
  • 在以上场景中,选中的处理客户端A请求的后端比选中的处理客户端B请求的后端,负载要相差3000x倍。这个问题非常严重,与负载均衡的目的背道而驰。而且要注意,对任何multiplexing,kept-alive(多路复用,保活)协议,都存在这个问题。(Multiplexing表示通过单个L4连接发送并发应用的请求,kept-alive表示当没有主动的请求时也不要关闭连接)。出于性能考虑(创建连接的开销是非常大的,尤其是连接是使用TLS加密的时候),所有现代协议都在演进以支持multiplexing和kept-alive,因此L4LB的阻抗不匹配问题(impedancemismatch)随时间越来越彰显。这个问题被L7LB解决了。

    图3:HTTP/2L7负载均衡

    图3是一个L7HTTP/2负载均衡器。这种情况下,客户端与LB只建立一个HTTP/2TCP连接。LB接下来和两个后端建立连接。当客户端向LB发送两个HTTP/2流(streams)时,stream1会被发送到后端1,而stream2会被发送到后端2。因此,即使不同客户端的请求数量差异巨大,这些请求也可以被高效地、平衡地分发到后端。这就是L7LB对现代协议如此重要的原因。L7负载均衡具备检测应用层流量的能力,这带来了大量额外的好处,我们后面会更详细看到。

    1.4、L7负载均衡和OSI7层模型

    前面讨论L4负载均衡时我说过,使用OSI模型描述负载均衡特性是有问题的。原因是,对于L7,至少按照OSI模型的描述,它本身就包括了负载均衡抽象的多个独立层级(discretelayers),例如,对于HTTP流量考虑如下子层级:

  • 物理HTTP协议(HTTP/1或者HTTP/2)
  • 逻辑HTTP协议(headers,bodydata,trailers)
  • 消息协议(gRPC,REST等等)
  • 一个复杂的L7LB可能会提供与以上全部子层级相关的特性,而另一个L7LB可能会认为其中只有一部分才属于7层的功能,因此只提供这个子集的功能。也就是说,如果要比较负载均衡器的特性(features),L7的范围比L4的复杂的多。(当然,这里我们只涉及了HTTP;Redis、Kafka、MongoDB等等都是L7LB应用层协议的例子,它们都受益于7层负载均衡。)

    2、负载均衡器特性

    本节将简要总结负载均衡器提供的高层特性(highlevelfeatures)。但并不是所有负载均衡器都提供这里的所有特性。

    2.1、服务发现

    服务发现是负载均衡器判断它有哪些可用后端的过程。用到的方式差异很大,这里给出几个例子:

  • 静态配置文件
  • DNS
  • Zookeeper,Etcd,Consul等待
  • Envoy的通用数据平面API(universaldataplaneAPI)
  • 2.2、健康检查

    健康检查是负载均衡器判断它的后端是否可以接收请求的过程。大致分为两类:

  • 主动:LB定时向后端发送ping消息(例如,向/healthcheck发送HTTP请求),以此测量后端健康状态
  • 被动:LB从数据流中检测健康状态。例如,L4LB可能会认为如果一个后端有三次连接错误,它就是不健康的;L7LB可能会认为如果后端有503错误码就是不健康的
  • 2.3、负载均衡

    LB必须保证负载是均衡的。给定一组健康的后端,如何选择哪个后端来处理一个连接或一个请求呢?负载均衡算法是一个相对活跃的研究领域,从简单的随机选择、RoundRobin,到更复杂的考虑各种延迟和后端负载状态的算法。最流行的负载均衡算法之一是幂次最少请求(powerof2leastrequest)负载均衡。

    2.4、StickySession(黏性会话)

    对于一些特定应用,保证属于同一session的请求落到同一后端非常重要。这可能需要考虑缓存、结构复杂的临时状态等问题。session的定义也并不相同,可能会包括HTTPcookies、客户端连接特性(properties),或者其他一些属性。一些L7LB支持stickysession。但这里我要说明的是,sessionstickiness本质上是脆弱的(处理/保持session的后端会挂掉),因此如果设计的系统依赖这个特性,那要额外小心。

    2.5、TLSTermination

    关于TLS以及它在边缘服务(edgeserving)和安全的service-to-service通信中扮演的角色,值得单独写一篇文章,因此这里不详细展开。许多L7LB会做大量的TLS处理工作,包括termination、证书验证和绑定(verificationandpinning)、使用SNI提供证书服务等等。

    2.6、可观测性(observability)

    我在技术分享中喜欢说:“可观测性、可观测性、可观测性。” 络在本质上是不可靠的,LB通常需要导出统计、跟踪和日志信息,以帮助运维判断出了什么问题并修复它。负载均衡器输出的可观测性数据差异很大。最高级的负载均衡器提供丰富的输出,包括数值统计、分布式跟踪以及自定义日志。需要指出的是,丰富的可观测数据并不是没有代价的,负载均衡器需要做一些额外的工作才能产生这些数据。但是,这些数据带来的收益要远远大于为产生它们而增加的那点性能损失。

    2.7、安全和DoS防御

    至少(尤其)在边缘部署拓扑(下面会看到)情况下,负载均衡器通常需要实现很多安全特性,包括限速、鉴权和DoS防御(例如,给IP地址打标签及分配标识符、tarpitting等等)。

    2.8、配置和控制平面

    负载均衡器要可以配置。在大型部署场景中,这可能是一项很大的工作。一般地,将配置负载均衡器的系统称为“控制平面”,其实现方式各异。

    2.9、其他更多特性

    本节对负载均衡器提供的功能做了一个非常浅的介绍。更多内容我们会在下面讨论L7LB的时候看到。

    3、负载均衡器的拓扑类型

    前面我们已经覆盖了负载均衡器的高层概览,L4和L7负载均衡器的区别,以及负载均衡器的功能特性等内容,接下来介绍它的分布式部署拓扑(下面介绍的每种拓扑都适用于L4和L7负载均衡器)。

    3.1、中间代理(middleproxy)

    图4:中间代理负载均衡拓扑

    图4所示的中间代理拓扑应该是大家最熟悉的负载均衡方式。这一类型的方案包括:

  • 硬件设备:Cisco、Juniper、F5等公司的产品
  • 云软件解决方案:Amazon的ALB和NLB,Google的CloudLoadBalancer
  • 纯软件方案:HAProxy、NGINX、Envoy等等
  • 中间代理模式的优点是简单,用户一般只需要通过DNS连接到LB,其他的事情就不用关心了。缺点是,这种模式下负载均衡器(即使已经做了集群)是单点的(singlepointoffailure),而且横向扩展有瓶颈。

    中间代理很多情况下都是一个黑盒子,给运维带来很多困难。例如发生故障的时候,很难判断问题是出在客户端,中间代理,还是后端。

    3.2、边缘代理(edgeproxy)

    图5:边缘代理负载均衡拓扑

    图5所示的边缘代理拓扑其实只是中间代理拓扑的一个变种,这种情况下负载均衡器是可以从因特 直接访问的。这种场景下,负载均衡器通常还要提供额外的“API 关”功能,例如TLStermination、限速、鉴权,以及复杂的流量路由等等。

    中间代理拓扑的优缺点对边缘代理也是适用的。需要说明的是,对于面向因特 的分布式系统,部署边缘代理通常是无法避免的。客户端一般通过DNS访问系统,而它使用什么 络库,服务方是控制不了的(下文会看到的客户端内嵌库或sidecar代理拓扑在此不适用)。另外,从安全的角度考虑,所有来自因特 的流量都通过唯一的 关进入系统是比较好的。

    3.3、客户端内嵌库(embeddedclientlibrary)

    图6:客户端内嵌库实现负载均衡

    为了解决中间代理拓扑固有的单点和扩展问题,出现了一些更复杂的方案,例如将负载均衡器已函数库的形式内嵌到客户端,如图6所示。这些库支持的特性差异非常大,最知名的库包括Finagle、Eureka/Ribbon/Hystrix、gRPC(大致基于一个Google内部系统Stubby)。

    这种拓扑的最大优点是:将LB的全部功能下放到每个客户端,从而完全避免了单点和扩展问题。缺点是:必须为公司使用的每种语言实现相应的库。分布式架构正在变得越来越“polyglot”(multilingual,多语言化)。在这种情况下,为多种语言实现一个复杂的 络库是非常难的(prohibitive)。最后,对大型服务架构,进行客户端升级也是一件极其痛苦的事情,最终很可能导致生产集群中同时运行多个版本的客户端,增加运维和认知(cognitive)负担。

    虽然如此,但是那些在能够限制语言数量增加(proliferation)而且能够解决客户端升级痛苦的公司,这种拓扑还是取得了成功的。

    3.4、sidecar代理

    图7:sidecar代理实现负载均衡

    3.5、不同拓扑类型的优缺点比较

  • 中间代理拓扑是最简单的负载均衡方式,缺点是单点故障、扩展性问题、以及黑盒运维
  • 边缘代理拓扑和中间代理拓扑类似,但一些场景必须得用这种模式
  • 客户端内嵌库拓扑提供了最优的性能和扩展性,但必须为每种语言实现相应的库,并且升级非常痛苦
  • sidecar代理拓扑性能不如客户端内嵌库好,但没有后者的那些缺点
  • 总体上我认为在service-to-service通信中,sidecar(servicemesh)正在逐渐取代其他所有拓扑类型。另外,在流量进入servicemesh的地方,总是需要一个边缘代理拓扑负载均衡器。

    4、当前L4负载均衡最新技术

    4.1、L4负载均衡还有用吗?

    我们前面已经解释了为什么L7负载均衡器对现代协议如此重要,接下来详细讨论L7LB的功能特性。这是否意味着L4LB没用了?不!虽然我认为在service-to-service通信中L7负载均衡最终会完全取代L4负载均衡,但L4负载均衡在边缘仍然是非常有用的,因为几乎所有的现代大型分布式架构都是在因特 流量接入处使用L4/L7两级负载均衡架构。在边缘L7负载均衡器之前部署L4负载均衡器的原因:

  • L7LB承担的更多工作是复杂的分析、变换、以及应用流量路由,他们处理原始流量的能力(按每秒处理的包数和字节数衡量)比经过优化的L4负载均衡器要差。这使得L4LB更适合处理特定类型的攻击,例如SYN泛洪、通用包(genericpacket)泛洪攻击等
  • L7LB部署的更多更频繁,bug也比L4LB多。在L7之前加一层L4LB,可以在调整L7部署的时候,对其做健康检查和流量排除(drain),这比(单纯使用)现代L4LB要简单的多,后者通常使用BGP和ECMP(后面会介绍)。最后,因为L7功能更复杂,它们的bug也会比L4多,在前面有一层L4LB能及时将有问题的L7LB拉出
  • 接下来的几节我将介绍中间/边缘代理L4LB的几种不同设计。这些设计通常不适用于客户端内嵌库和sidecar代理拓扑模式。

    4.2、TCP/UDPtermination负载均衡

    图8:TCPL4termination负载均衡

    第一种现在仍在用的L4LB是terminationLB,如图8所示。这和我们最开始介绍L4负载均衡器时看到的图是一样的(图2)。这种模式中,会使用两个独立的TCP连接:一个用于客户端和负载均衡器之间,一个用于负载均衡器和后端之间。

    L4负载均衡器仍然在用有两个原因:

    1. 他们实现相对简单
    2. 连接terminate的地方离客户端越近,客户端的性能(延迟)越好。特别地,如果在一个有丢包的 络(lossynetwork,例如蜂窝 )中将terminationLB部署的离客户端很近,重传可能就会更快的发生(retransmitsarelikelytohappenfasterpriortothedatabeingmovedtoreliablefibertransiten-routetoitsultimatelocation)。换句话说,这种负载均衡方式可能会用于入 点(POP,PointofPresence)的rawTCPconnectiontermination

    4.3、TCP/UDPpassthrough负载均衡

    图9:TCPpassthrough负载均衡

    第二种L4负载均衡是passthrough,如图9所示。在这种类型中,TCP连接不会被负载均衡器terminate,而是在建立连接跟踪和 络地址转换(NAT)之后直接转发给选中的后端。我们首先来定义连接跟踪和NAT:

  • 连接跟踪(connectiontracking):跟踪所有活动的TCP连接的状态的过程。这包括握手是否成功、是否收到FIN包、连接已经空闲多久、为当前连接选择哪个后端等
  • NAT:利用连接跟踪的数据,在包经过负载均衡器时修改包的IP/port信息
  • 使用连接跟踪和NAT技术,负载均衡器可以将大部分rawTCP流量从客户端转发到后端。例如,我们假设客户端正在和负载均衡器1.2.3.4:80通信,选中的后端是10.0.0.2:9000。当客户端的TCP包到达负载均衡器时,负载均衡器会将包的目的IP/port(从1.2.3.4:80)换成10.0.0.2:9000,以及将源IP/port换成负载均衡器自己的IP/port。当应答包回来的时候,负载均衡器再做相反的转换。

    为什么这种比terminatingLB更复杂的LB类型,会在某些场景中替换前者使用呢?几点原因:

  • 性能和资源消耗:passthroughLB不会terminateTCP连接,因此无需缓存任何TCP连接窗口。每个连接的状态数据非常小,通常可以通过哈希表直接查询。因此,passthroughLB的性能(packetspersecond,PPS,每秒处理的包数)要比terminatingLB高很多
  • 允许后端进行自主拥塞控制:TCP拥塞控制是一种避免发送太快导致超过 络带宽或缓冲区的机制。passthroughLB不terminateTCP连接,因此它不参与拥塞控制。这使得后端可以根据应用的类型自主决定采用哪种拥塞控制算法。而且,这种方式还使得验证拥塞控制的改动更容易(例如,最近的BBRrollout)
  • 是Directserverreturn(DSR)和L4LB集群化的基础:很多高级的L4负载均衡技术基于passthroughLB,例如DSR和一致性哈希集群(下面讨论)
  • 4.4、DSR(直接服务器返回)

    图10:L4Directserverreturn(DSR,直接服务器返回)

    DSRLB如图10所示,它基于passthroughLB,对后者的改进之处是:只允许进来的流量/请求(ingress/request)经过LB,而出去的流量/响应(egress/response)直接从服务器返回到客户端。

    设计DSR的主要原因是:在一些场景中,响应的流量要远远大于请求的流量(例如典型的HTTPrequest/response模式)。假设请求占10%的流量,响应占90%,使用DSR技术,只需1/10的带宽就可以满足系统需求。因为早期的负载均衡器非常昂贵,这种类型的优化可以极大地节省成本,还提高了负载均衡器的可靠性(流量越低肯定越好)。DSR在如下方面扩展了passthroughLB:

  • LB仍然做一部分连接跟踪工作。因为响应不再经过LB,LB无法知道TCP连接的完整状态。但是,它仍然可以根据客户端的包以及多种类型的idletimeout,(strongly)推测连接的状态
  • 与NAT不同,负载均衡器通常使用GRE(GenericRoutingEncapsulation)将IP包封装发送到后端。后端收到后进行解封装,就可以拿到原始的IP包,里面有客户端的IP和port信息。因此后端可以直接将应答包发给客户端,而需要经过LB
  • DSR非常的重要一点是:后端参与负载均衡过程。后端需要配置正确的GRE隧道,视 络设置的底层细节,GRE可能还需要自己的连接跟踪和NAT
  • 4.5、通过HApair实现容错

    图11:通过HApair和连接跟踪实现L4容错

    到目前为止,我们讨论的都是单个L4LB。passthrough和DSR都需要LB保存一些连接跟踪的状态。假如LB挂了呢?如果一个LB实例挂了,那所有经过这个LB的连接都会受到影响。视应用的不同,这可能会对应用性能产生很大影响。

    历史上,L4负载均衡器是从一些厂商(Cisco、Juniper、F5等等)购买的硬件设备,这些设备非常昂贵,可以处理大量的 络流量。为了避免单个负载均衡器挂掉导致应用不可用,负载均衡器通常都是以高可用对(highavailabilitypair)方式部署的,如图11所示。典型的HA负载均衡器设置包括:

  • 类似地,primaryL4LB向边缘路由器宣告它的权重比backupLB大,因此正常情况下它处理所有流量
  • primaryLB交叉连接(cross-connected)到backupLB,共享所有的连接跟踪状态。因此,假如primaryLB挂了,backupLB可以马上接管所有活动连接
  • 两个边缘路由器和两个负载均衡器都是交叉连接的。这意味着,如果一个边缘路由器或一个负载均衡器挂了,或者由于某种原因之前声明的BGP权重收回了(withdraw),backup马上可以接受所有流量
  • 以上就是许多大流量因特 应用今天仍然在使用的架构。然而,以上架构也有很大的不足:

  • VIP需要做容量规划,并正确sharding给两个负载均衡器实例。如果一个VIP(的连接数?)增长超过了单个HApair的容量,那这个VIP需要分裂成多个VIP
  • 资源利用率很低,平稳状态下50%的容量是空闲的。考虑到有史以来硬件负载均衡器都是非常昂贵的,这意味着大量的资金没有得到有效利用
  • 现代分布式系统设计追求比active/backup更高的容错(faulttolerance)性。例如,理想情况下,一个系统有多个实例同时挂掉仍能继续运行。而HALBpair的主备实例同时挂掉时,服务就彻底挂了
  • 声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

  • 上一篇 2021年10月10日
    下一篇 2021年10月10日

    相关推荐