http://www.infoq.com/cn/articles/modern-network-load-balancing-and-proxying-1
译者:
最近我注意到,关于当代网络负载均衡和代理的入门教材非常匮乏。我心想:为什么会这样?负载均衡是构建可靠的分布式系统所需要的核心概念之一。应当可以获取到一些有用的信息的吧?我在网上搜了搜,却没有得到多少有用的信息。维基百科上关于负载均衡和代理服务器的文章包含了一些概念,但是对于这个课题没有一个完美的解答,更别提与现代微服务架构的联系了。Google上关于负载均衡的搜索结果主要是一些供应商的网页,显示的主要是一些流行术语,细节方面的内容很少。
在本文中,我将尝试改变这种信息匮乏的情况,详细介绍现代网络负载均衡和代理。坦率地讲,这是一个非常大的话题,围绕它写一本书都不为过。为了在一定程度上控制本篇文章的篇幅,我试着化繁为简,对这一系列复杂的话题进行了提炼;根据读者的兴趣和反馈,我会考虑在后续帖子中详细介绍一些单独的话题。
介绍了一点我写作本文的背景之后,让我们开始正文吧!
什么是网络负载均衡与代理?
维基百科将负载均衡定义为:
在计算机领域,负载均衡优化了多个计算资源间的工作量分布。这些计算资源包括计算机、计算机集群、网络连接、CPU或者硬盘等。负载均衡的目的是优化资源利用、最大化吞吐量、最小化响应时间,并且避免任何单个资源过载。通过冗余机制,使用多个负载均衡组件而不是单一组件可以增加可靠性和可用性。负载均衡通常涉及专门的软件或硬件,例如多层交换机或者域名系统服务器进程。
上述定义适用于所有计算机领域,而不仅仅是网络。操作系统使用负载均衡来调度物理处理器间的任务,容器编排工具(例如Kubernetes)使用负载均衡来调度计算机集群内的任务,网络负载均衡器使用负载均衡来调度可用后端间的网络任务。本文剩余部分将只涵盖网络负载均衡相关的内容。
图1高度概括了网络负载均衡。一些客户端从一些后端请求资源。负载均衡器位于客户端和后端之间,执行一些非常重要的任务:
- 服务发现:系统中有哪些后端可用?它们的地址是什么?(例如,负载均衡器如何和它们通信?)
- 状态检查:哪些后端现在可以接受请求?
- 负载均衡:应该使用什么算法来均衡后端间的请求?
在分布式系统中合理使用负载均衡可以带来以下好处:
- 抽象命名:客户端可以通过一个预定义的机制寻址到负载均衡器,然后将名称解析委托给负载均衡器,而不是每一个客户端都需要知道每一个后端的(有关服务发现的)信息。预定义机制包括内置库和众所周知的DNS/IP/端口地址,这些稍后都会详细讨论。
- 容错性:通过状态检查和各种算法技术,负载均衡器可以有效绕开挂掉的或者过载的后端。这意味着,运维人员通常可以在他们空闲时修复挂掉的后端而不是必须将这作为一个紧急事件来处理。
- 成本和性能方面的好处:分布式系统网络复杂多变。系统可能会包含多个网络区域和行政区。在一个区域内,网络可能相对过载。而不同区域间,网络通常又会有空余。(这里,网络过载或空余是指通过网卡消耗的带宽数量占不同路由器间的可用带宽的百分比)。智能负载均衡可以尽可能多地维持一个区域内的请求数量,这提高了性能(延迟降低)并且减少了整个系统的成本(不同区域间需要更少的宽带和光纤)。
负载均衡vs代理
谈到网络负载均衡器的时候,“负载均衡器”和“代理”这两个术语通常在行业内是差不多相同的意思。本文通常也会将这两个术语作为相同意思的词。(严格来说,并不是所有的代理都是负载均衡器,但是大部分代理将负载均衡作为首要功能)。
有人也许会争论说,当用嵌入式客户端库来完成负载均衡时,负载均衡器并不是一个代理。然而,我认为这个话题本来就十分容易令人混淆,而这种区分会为这个话题增加更多不必要的复杂性。下面会详细介绍负载均衡器的各种拓扑结构,但是本文仍将嵌入式负载均衡器拓扑结构作为一种特殊的代理;应用程序通过嵌入式库进行代理,这个库提供了作为一个独立于应用进程外的负载均衡器的所有相同的抽象。
L4(连接/session)负载均衡
现在讨论行业内的负载均衡方案时,通常会归结为两类:L4和L7.这两种分类指的是OSI模型的第4层和第7层。当我讲到L7负载均衡的时候,会明显发现我们使用这些术语来分类是错误的。OSI模型是负载均衡方案的复杂性的一种非常粗劣的抽象,因为负载均衡方案中虽然包含传统的第4层协议,例如TCP和UDP,但是通常最后还会或多或少包含一些其它OSI层级的协议。例如,如果一个L4
TCP负载均衡器也支持TLS终端,那么它现在是不是一个L7负载均衡器呢?
图2:TCP L4终端负载均衡
图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负载均衡器修复。
图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应用层协议)。
负载均衡器的功能
本节中,我将简要总结负载均衡器提供的高级功能。不是所有的负载均衡器都提供所有这些功能。
服务发现
服务发现是负载均衡器判断可用后端集合的过程。服务发现的方法各不相同,包括以下一些例子:
健康检查
健康检查是负载均衡器判断后端是否服务可用的过程。健康检查通常分为两类:
- 主动的:负载均衡器以固定时间间隔向后端发送ping请求(例如,每个健康检查的端点发送一个HTTP请求),并以此来判断健康状态。
- 被动的:负载均衡器从主要的数据流中检测健康状态。例如,如果在一段时间内有三次连接错误,L4负载均衡器可能会认为这个后端是不健康的。如果在一段时间内有三次HTTP 503响应代码,L7负载均衡器可能会认为该后端是不健康的。
负载均衡
是的,负载均衡器要真的能够均衡负载!如果有一堆状态良好的后端,如何选中一个后端来服务一个连接或请求?负载均衡算法是一个热门的研究领域,从比较简化的算法,例如随机选择法和循环淘汰法,到复杂一些的考虑到延迟和后端负载变化的算法。最流行的负载均衡算法之一是power of 2 request load balancing,具有良好的性能和较低的复杂度。(这种算法的机制是:当请求到达负载均衡器时,负载均衡器会先从系统中随机选择2个后端,然后从这2个后端中选择负载最小的那个来响应请求。)
粘性session
在特定应用中,同一个session的请求到达同一个后端是非常重要的。这可能与缓存、临时创建的复杂状态等有关。session的定义很多,可能包括HTTP cookies、客户端连接属性或者其它一些属性。许多L7负载均衡器支持粘性session。另外,我要提醒你注意的是,session的粘性本质上是脆弱的(后端持有的session可能会消亡),因此建议在设计依赖粘性session的系统时保持谨慎。
TLS终端
关于TLS和它在服务和保障服务间通信中的角色的话题值得深入探讨。许多L7负载均衡器承担了许多TLS处理流程,包括终止请求、证书验证和绑定、使用SNI的证书服务等。
观测性
正如我常说的:“观测性、观测性、观测性。”网络本质上是不可靠的,负载均衡器通常有责任导出统计数据、跟踪数据和日志,这些信息可以帮助运维人员发现问题从而修复这些问题。负载均衡器在可观测性输出结果上有很大不同。最高级的负载均衡器提供丰富的输出结果,包括数值统计、分布式跟踪信息和自定义日志。我要指出的是,为了增强功能而增加的观测性不是没有代价的;负载均衡器不得不做一些额外的工作来产出观测结果。然而,这些数据带来的好处远远超过了相对较小的性能影响。
安全性和DoS防御
特别是在边缘部署拓扑结构中(见下文),负载均衡器通常实现各种安全功能,包括速率限制、认证和DoS防御(例如,IP地址标记、识别、过滤等)。
配置控制层
负载均衡需要进行配置。这对于大型部署来说,是一种非常重要的保证。通常,配置负载均衡器的系统被称为“控制层”,而且它有各种各样的实现。关于这个话题的更多信息,请查看我的关于《service mesh data plane vs. control plane》的博客。
更多内容
本节初步涉猎了负载均衡器提供的各种功能。其它的讨论可以查看下文中关于L7负载均衡器的章节。
负载均衡器拓扑结构类型
既然我已经高度概括了什么是负载均衡器、L4和L7负载均衡器的区别以及负载均衡器的功能总结,下面我将继续介绍部署了负载均衡器的各种分布式系统的拓扑结构。(下面的每种拓扑结构都适用于L4和L7负载均衡器。)
中间代理
图4所示的中间代理拓扑结构对于大部分读者来说可能是最熟悉的使用负载均衡的方式。这类负载均衡包括Cisco、Juniper、F5出产的硬件设备;云端软件解决方案,例如Amazon的ALB和NLB以及Google的云端负载均衡器(Cloud Load Balancer);自托管的纯软件解决方案,例如HAProxy、NGINX和Envoy。中间代理解决方案的优点是简单易用。通常,用户通过DNS连接到负载均衡器,然后就不需要再关心其它任何事情了。中间代理解决方案的缺点是,代理(即使使用集群)是一个故障单点,同时也是一个扩展瓶颈。中间代理通常也是一个黑盒子,运维人员难以维护。如果发生了故障,那么这是一个在客户端能够观察到的问题吗?是发生在物理网络吗?还是发生在中间代理?或者是发生在后端?这很难说的清楚。
边缘代理
图5所示的边缘代理拓扑结构只是中间代理拓扑结构的一个变种,其中负载均衡器通过Internet访问。在这个场景中,负载均衡器通常必须提供额外的“API网关”功能,例如TLS终端、速率限制、身份认证和复杂的流量路由。边缘代理的优点和缺点与中间代理一样。需要注意的是,通常在大型的面向互联网的分布式系统中部署专用的边缘代理是不可避免的。客户端通常会使用任意网络库经过DNS访问系统,但这些网络库不受客户端的控制(这使得嵌入式客户端库负载均衡器或下面章节会提到的sidecar代理拓扑结构负载均衡器直接在客户端上运行变得不切实际)。另外,为了安全原因设置一个单独的安全网关是值得的,所有面向互联网的流量都必须先经过这个安全网关才能进入系统。
嵌入式的客户端库
为了避免中间代理拓扑结构天生的单点故障和扩展问题,更复杂的基础设施朝着将负载均衡器通过一个库直接嵌入到服务中的方向发展,正如图6所示的那样。这些库支持的功能各种各样,其中最知名的功能丰富的库有Finagle、Eureka/Ribbon/Hystrix和gRPC(基于一个Google内部称作Stubby的系统)。基于库的解决方案的主要优点是,它完全将负载均衡器的所有功能分布到每一个客户端,因此消除了前面提到的单点故障和扩展问题。基于库的解决方案的主要缺点是所用的库必须用一个机构使用的每一种语言实现。分布式架构变得越来越“通晓各种语言”(使用多种语言编写的)。在这种环境下,用多种不同的语言重新实现一个非常复杂的网络库的代价会非常大。最后,跨一个大型服务架构来升级库会非常痛苦,很可能会造成许多不同版本的库同时在生产环境中运行,从而增加运维时了解网络情况的负担。
尽管如此,对于那些能够限制使用的编程语言的种类并且克服升级库的痛苦的公司来说,上面提到的各种库还是非常成功的。
Sidecar代理
图7所示的sidecar代理拓扑结构是客户端嵌入式库负载均衡拓扑结构的一个变种。最近几年,这种拓扑结构被称作“服务网格”并且非常流行。sidecar代理背后的思路是,以切换到另外一个进程造成轻微延迟的代价,获得嵌入式库方案的所有好处,而且无需局限于任何编程语言。截至本文,最流行的sidecar代理负载均衡器有Envoy、NGINX、HAProxy和Linkerd。如果想要更详细地了解sidecar代理方案的优化方法,请查看我博客中介绍Envoy的帖子和关于《service mesh data plane vs. control plane》(服务网格数据层vs控制层)的帖子。
各种负载均衡器拓扑结构的优缺点
- 中间代理拓扑结构是最容易使用的负载均衡拓扑结构。它的弊端是单点故障、扩展限制和黑箱操作端。
- 边缘代理拓扑结构和中间代理类似,但通常还免不了会用到。
- 嵌入式客户端库拓扑结构提供了最好的性能和可扩展性,但是需要用各种语言来实现这个库而且需要跨所有服务来升级这个库。
- sidecar代理拓扑结构执行性能虽然不如嵌入式客户端库拓扑结构,但是不会受到其中的任何限制。
总之,我认为sidecar代理拓扑结构(服务网格)正逐渐取代所有其它用来进行服务间通信的拓扑结构。在流量进入服务网格之前需要使用边缘代理拓扑结构。
查看英文原文:Introduction to modern network load balancing and proxying
本文的上半卷已经对负载均衡技术的概念、功能和拓扑结构进行了详尽的介绍,接下来将主要介绍各种负载均衡技术的现状、演变和前景。
L4负载均衡技术的现状
L4负载均衡器还有用么?
本文已经讨论了L7负载均衡器是如何适合当代网络协议,下文会更深入地介绍L7负载均衡器的功能。这是否意味着不再需要L4负载均衡器了?不!尽管我认为L7负载均衡器最终会完全取代处理服务间通信的L4负载均衡器,但L4负载均衡器在边缘层仍然非常有用,因为大多数现代大型分布式架构都使用两层的L4/L7负载均衡架构来进行互联网流量管理。边缘部署时在L7负载均衡器之前放置L4负载均衡器的好处是:
- 由于L7负载均衡器执行的是更复杂的针对应用程序流量的分析、转换和路由等工作,因此它们比优化过的L4负载均衡器处理的原始流量负载相对少一些(以每秒数据包数和每秒字节数测量)。这通常使得L4负载均衡器更适合处理特定类型的DoS攻击(例如,SYN洪水攻击、整包洪水攻击等)。
- L7负载均衡器发展得更活跃,部署得更普遍,也比L4负载均衡器有更多的bug。在L7负载均衡器前面使用L4负载均衡器,比使用现代L4负载均衡器的部署机制更容易进行健康状态检查和流量过滤,这种机制通常使用BGP和ECMP(下文会对此介绍更多细节)。而且,因为L7负载均衡器由于功能的复杂性而可能会有更多的bug,所以使用L4负载均衡器可以绕过故障和异常,从而使整个系统更稳定。
接下来的章节,我会介绍几种不同的中间/边缘代理L4负载均衡器的设计。这些设计通常不适用于客户端库和sidecar代理拓扑结构。
TCP/UDP终端负载均衡器
第一种仍在使用的L4负载均衡器是图8中所示的负载均衡器。这和上文介绍L4负载均衡时看到的负载均衡器相同。在这种负载均衡器中,使用了2个离散的TCP连接:一个用在客户端和负载均衡器之间,另一个用在负载均衡器和后端之间。
L4终端负载均衡器还在被使用是因为两个原因:
- 相对来说,它们实现起来更简单。
- 与客户端更近的连接终端有天然的性能优势(延迟更低)。特别是,如果一个负载均衡器可以放得与使用松散网络(例如蜂窝网络拓扑)的客户端更近,可以在数据被发送到可靠光纤转发器然后路由到最终目的地之前更快地进行转发。换句话说,这种负载均衡器可以用在原始TCP连接终端的入网点(Point of Presence,POP)。
TCP/UDP直连负载均衡器
第二种L4负载均衡器是图9中所示的直连负载均衡器。在这种负载均衡器中,负载均衡器不会终止TCP连接。相反的,在进行连接监测和网络地址转换(Network Address Translation,NAT)之后,每个连接的数据包
被直接发送到一个选中的后端。首先,让我们定义连接监测和NAT:
- 连接监测:是保持跟踪所有活跃的TCP连接状态的过程。这些状态包括握手是否完成、是否收到FIN、连接空闲时间、连接选中的后端等信息。
- NAT: NAT是在通过负载均衡器时,使用连接监测获取的数据来改变IP或端口信息的过程。
同时使用连接监测和NAT,负载均衡器可以让大多数原始TCP流量直接从客户端到达后端。例如,客户端向1.2.3.4:80
发起通信,选中的后端位于10.0.0.2:9000
。客户端TCP数据包会首先到达负载均衡器所在的1.2.3.4:80
。然后,负载均衡器会将目标地址的IP和端口改为10.0.0.2:9000
。它还会将源地址的IP和端口改为负载均衡器的IP地址。因此,当在TCP连接中响应时,数据包会返回到负载均衡器,然后在这里反向进行连接监测和网络地址转换。
为什么这种负载均衡器会取代上一章节介绍的终端负载均衡器,是因为它更复杂吗?其实,原因是:
- 性能和资源利用率: 因为直连负载均衡器不会终止TCP连接,所以它们不需要缓冲任何TCP连接窗口。每个连接存储的状态数量很少,而且通常从高效的hash table中查找。因此,直连负载均衡器本质上可以比终端负载均衡器处理更多数量的活跃连接和每秒数据包数(packets per second,PPS)。
- 允许后端执行自定义阻塞控制: TCP阻塞控制是一种互联网上的端点用来控制数据发送量以避免可用带宽和缓冲区超出负载承受能力的机制。由于直连负载均衡器不会终止TCP连接,所以它不会参与阻塞控制。这就允许后端根据它们的应用程序实际情况来使用不同的阻塞控制算法。这也使得更容易对阻塞控制变更进行实验(例如,最近的BBR首秀)。
- 形成服务器直接返回和集群化的L4负载均衡的基线: 更高级的L4负载均衡技术,例如DSR和使用分布式一致性哈希搭建集群(接下来的章节会讨论),都需要使用直连负载均衡器。
服务器直接返回
图10所示的是一个服务器直接返回(Direct server return,DSR)负载均衡器。DSR是在前面介绍的直连负载均衡器的基础上进行了优化,只有流入的或请求的数据包会通过负载均衡器,流出的或响应的数据包绕过负载均衡器直接返回到客户端。使用DSR的主要原因是,在许多工作负载中,响应流量比请求流量多得多(例如,经典的HTTP 请求/响应模式)。假设10%的流量是请求流量,90%的流量是响应流量,使用一个只拥有原来直连负载均衡器1/10性能的DSR负载均衡器就可以满足系统的需求。由于一直以来负载均衡器都很贵,这种优化可以很大程度上减少系统成本并增加可靠性(能少花钱总是好的)。DSR负载均衡器扩展了直连负载均衡器的以下几个概念:
- DSR负载均衡器通常执行部分连接监测。由于响应数据包不经过负载均衡器,负载均衡器就不清楚完整TCP连接的状态。然而,负载均衡器可以通过查看客户端数据包和利用各种空闲超时来准确推断出连接状态。
- DSR通常使用通用路由封装(Generic Routing Encapsulation,GRE)来将从负载均衡器发送给后端的IP数据包进行封装,而不是使用网络地址转换。因此,当后端收到封装过的数据包后将它解封,然后就知道客户端的原始IP地址和TCP端口。这就使得后端可以直接向客户端响应了,而不需要经过负载均衡器。
- DSR负载均衡器非常重要的一个概念是,后端参与负载均衡。后端需要恰当地配置一个GRE隧道,并且根据网络安装的底层细节,后端可能会需要它自己进行连接监测、网络地址转换等。
注意,在直连负载均衡器和DSR负载均衡器的设计中,有很多种方法可以设置在负载均衡器和后端之间,例如连接监测、NAT和GRE等。但是,这个话题超出了本文的讨论范围。
通过高可用对实现容错
直到现在,我们还在孤立地看待L4负载均衡器的设计。直连负载均衡器和DSR负载均衡器都需要一些连接监测和负载均衡器自身的状态。如果负载均衡器挂掉了会发生什么?如果负载均衡器的一个实例挂了,经过负载均衡器的所有连接都会中断。就应用程序来说,这也许会对应用程序的性能造成重大影响。
历史上,L4负载均衡器主要从传统供应商(Cisco、Juniper、F5等)采购。这些硬件设备非常昂贵,负责处理大量流量。为了避免单点故障中断所有连接以及造成严重的应用停用,负载均衡器通常以图11所示的高可用对的形式部署。经典的高可用对负载均衡器安装有以下设计:
- 用高可用对边缘路由器提供虚拟IP(virtual IP,VIP)。这些边缘路由器用边界网关协议(Border Gateway Protocol,BGP)来实现虚拟IP。其中,主边缘路由器比备份路由器有更高的BGP权重,因此它在稳定状态下处理所有的流量。(BGP是一个非常复杂的协议;本文中,仅仅将BGP看作是一种机制,网络设备通过这种机制可以从其它网络设备获取流量,而且每一条链路都有一个优先权重)。
- 类似地,主L4负载均衡器向边缘路由器宣称自己比备份负载均衡器有更高的BGP权重,因而它在稳定状态下处理所有流量。
- 主负载均衡器与备份负载均衡器是交叉连接的,共享所有的连接监测状态。因此,如果主负载均衡器挂了,备份负载均衡器可以接管处理所有活跃的连接。
- 这两个边缘路由器和两个负载均衡器都是交叉连接的。这意味着,如果其中一个边缘路由器或负载均衡器挂了,或者因为某些其它原因被降低了BGP权重,备份可以接管处理所有的流量。
许多高流量的互联网应用如今也是按照上面的设置运行的。然而,上面的方案有一些本质上的缺点:
- 考虑到性能利用率,虚拟IP必须在高可用负载均衡器对间正确地共享。如果单个虚拟IP发展到超出单个高可用对的处理能力,这个虚拟IP需要被分割成多个虚拟IP。
- 系统的资源利用率很低。在稳定状态下,50%的能力一直闲置。考虑到过去负载均衡器硬件非常昂贵,这导致大量的资金闲置。
- 现代的分布式系统设计比主动/备份设计提供更好的容错能力。例如,理想情况下,一个系统应该能够承受多个同时发生的故障并保持运行。如果主动和备份负载均衡器同时挂掉,高可用负载均衡器对容易受到故障影响。
- 供应商专卖的大型硬件设备非常昂贵并且出现供应商锁定的结果。通常用一些软件方案取代这些硬件设备更可取,这些软件方案可以水平扩展并且使用商用计算机服务器实现。
通过使用分布式一致性哈希集群实现容错和扩展
上一章节介绍的通过高可用对实现L4负载均衡器的容错性,以及那些在设计中固有的问题。从21世纪初期到中期,大型互联网基础设施开始设计和部署图12所示的新型的大型并行L4负载均衡系统。这些系统的目标是:
- 缓解上一章节介绍的高可用对设计的所有缺点。
- 从供应商专卖的硬件负载均衡器转向使用标准计算机服务器和NIC的商用软件解决方案。
这种L4负载均衡器设计在容错性和扩展性方面是最好的。它通过集群和分布式一致性哈希来实现这一点,其运行机制如下:
- N个边缘路由器以等量的BGP权重声明所有任播虚拟IP。用等价多路径路由(Equal-cost multi-path routing,ECMP)来确保,在大体上,来自单个flow的所有数据包到达相同的边缘路由器。flow通常是包含源IP/端口和目的IP/端口的4元组。(总之,ECMP是一种在一组使用一致性哈希的等量权重的网络链路上分发数据包的方法)。尽管边缘路由器自身不特别关心哪个数据包到哪里,但是通常来自同一个flow的所有数据包通过相同的链路集,通过这样来避免数据包乱序造成的性能下降。
- N个L4负载均衡器以等量BGP权重向边缘路由器声明所有虚拟IP。还是使用ECMP,边缘路由器通常会为flow选择同一个负载均衡器。
- 每一个L4负载均衡器通常会执行部分连接监测,然后使用一致性哈希来为flow选择后端。使用GRE来封装从负载均衡器发送到后端的数据包。
- 然后使用DSR来通过边缘路由器直接从后端向客户端发送数据包。
- L4负载均衡器实际使用的一致性哈希算法是一个热门研究领域。这些算法在平衡负载、最小化延迟、最小化后端变更时造成中断的时间以及最小化内存开销等方面进行了一些权衡。关于这个话题的完整讨论不在本文的讨论范围之内。
让我们看看上面的设计如何缓解高可用对方案的所有缺点:
- 可以按需增加新的边缘路由器和负载均衡器。在每一层使用一致性哈希来尽可能减少新增设备时受影响的flow的数量。
- 在保证足够的流量爆发边界和容错性的同时尽可能地提高系统的资源利用率。
- 边缘路由器和负载均衡器都可以使用商用硬件来构建,这只相当于使用传统硬件负载均衡器的成本的很少一部分。(更多内容详见下文)。
关于这种设计通常被问到的一个问题是“为什么边缘路由器不直接通过ECMP和后端通信?为什么我们还需要负载均衡器?”这么做的原因主要是为了缓解DoS攻击和减轻后端运维工作。没有负载均衡器,每个后端都不得不参与BGP,在执行滚动部署时要困难得多。
所有现代L4负载均衡系统都采用这种设计(或者这种设计的变种)。最突出的众所周知的两个例子是Google的Maglev和Amazon的NLB(Network Load Balancer)。目前还没有任何OSS负载均衡器实现这种设计,然而,有一家我了解的公司打算在2018年发布一款使用这种设计的面向OSS的负载均衡器。我对于这次发布非常兴奋,因为现代L4负载均衡器是OSS在网络领域非常重要的一块缺失。
L7负载均衡技术的现状
在过去几年,L7负载均衡器(代理)发展得如火如荼。这和在分布式系统中持续推动微服务架构的趋势是一致的。从根本上讲,固有故障的网络使用得越频繁越难以进行高效维护。此外,自动扩展、容器调度等技术的兴起,意味着在静态文件中提供静态IP的时代一去不复返了。现代系统不仅更多地使用网络,还变得更加动态化,这都需要在负载均衡器中提供新的功能。在本章节,我将简要总结现代L7负载均衡器中发展最快的一些领域。
协议支持
现代L7负载均衡器为多种协议增加明确支持。负载均衡器对应用程序流量了解得越多,它就能做更复杂的观测报告、高级负载均衡和路由等事情。例如,本文撰写时,Envoy明确支持对HTTP/1、HTTP/2、gRPC、Redis、MongoDB和DynamoDB的L7协议解析和路由。在将来会加入可能会加入更多的协议,包括MySQL和Kafka。
动态配置
正如上文描述的那样,分布式系统日益增强的动态化特性需要并行投入创建动态化和响应式控制系统。Istio就是这种系统的一个例子。请查看我博客上关于《service mesh data plane vs. control plane》的帖子,来了解更多关于这个话题的信息。
高级负载均衡
L7负载均衡器现在通常内置支持高级的负载均衡功能,例如超时、重试、速率限制、断路、投影、缓冲、基于内容的路由等。
观测性
正如上文关于通用负载均衡器功能的描述,日益动态化的系统变得更加难以调试。稳健的协议规格观测报告可能是现代L7负载均衡器提供的最重要的功能。实际上,现在任何L7负载均衡方案都需要生成数据统计、分布式跟踪和自定义日志信息。
扩展性
现代L7负载均衡器的用户通常希望非常容易地扩展它们来增加自定义的功能。这可以通过编写可插拔的过滤器来实现,这些过滤器将加载到负载均衡器中。许多负载均衡器还支持编制脚本(通常是用Lua)来新增自定义功能。
容错性
我在前面已经提到了不少关于L4负载均衡器容错性的内容。L7负载均衡器的容错性怎么样?大体上,我们认为L7负载均衡器应该是可扩展和无状态的。使用商业化软件允许L7负载均衡器更容易进行水平扩展。此外,L7负载均衡器的执行流程和状态监测比L4负载均衡器复杂得多。尝试去构建一个L7负载均衡器的高可用对在技术上是可行的,但是任务会比较艰巨。
总之,在L4和L7负载均衡领域,行业已经从高可用对方案转向通过一致性哈希来聚集的水平扩展系统方案。
此外
L7负载均衡器正以惊人的速度演变。如果想查看Envoy提供的功能,请看Envoy的架构概述。
全局负载均衡和中心化的控制层
未来,负载均衡会更多地将单个的负载均衡器作为商业化设备。在我看来,真正的创新和商业机会都在控制层领域。图13展示了一个全局负载均衡系统的例子。在这个例子中,发生了一些改变:
- 每一个sidecar代理都和三个不同区域的后端(A、B、C)通信。
- 如图所示,90%的流量送往区域C,而5%的流量分别送往区域A和区域B。
- sidecar代理和后端都定期向全局负载均衡器报告状态。这允许全局负载均衡器根据延迟、成本、负载、当前故障等信息来进行决策。
- 全局负载均衡器定期为每个sidecar代理配置当前路由信息。
全局负载均衡器越来越能够完成一个复杂的单个负载均衡器自身不能完成的事情。例如:
- 自动探测并绕过局部故障。
- 应用全局的安全和路由策略。
- 探测并缓解流量异常,包括使用机器学习和神经网络的DDoS攻击。
- 提供集中化的UI和视图,允许工程师在整体上了解和维护整个分布式系统。
为了实现全局负载均衡,作为数据层的负载均衡器必须有精确的动态配置能力。请查看我博客上关于Envoy的统一数据层API以及《service mesh data plane vs. control plane》的帖子,来获取更过关于这个主题的信息。
从硬件到软件的演变
到目前为止,本文只是简要地提到了硬件vs软件,主要是在提到过去的L4负载均衡器高可用对的时候。在这个领域的行业趋势是怎样的呢?
上面的推文是一种幽默的夸张,但也很好地总结了行业趋势:
- 一直以来,路由器和负载均衡器都是非常昂贵的供应商专卖硬件。
- 逐渐地,大部分供应商专卖的L3/L4网络设备被商业化服务器硬件、商业化NIC和基于像IPVS、DPDK以及fd.io这样的框架的专业软件方案取代。一台现代数据中心的花费不到5千美元的机器,使用Linux和以DPDK编写的自定义的用户空间应用程序,可以用非常小的数据包轻易饱和80Gbps的NIC。同时,能够以惊人的总带宽和数据频率完成ECMP路由的又便宜又基础的路由器/交换机 ASIC被打包成商业化路由器。
- 复杂的L7软件负载均衡器,例如NGINX、HAProxy和Envoy,也在快速迭代和蚕食之前提到的负载均衡器供应商,例如F5。因此,L7负载均衡器也在逐步迈入商业化软件解决方案。
- 同时,行业整体也在朝着IaaS、CaaS和FaaS发展。由主流云服务商提供基础设施,意味着越来越少的工程师需要了解物理网络是如何工作的(这些是上文提到的“黑魔法”和“我们不再需要了解的东西”)。
负载均衡的现状总结和前景
本文要点如下:
- 负载均衡器是现代分布式系统中非常关键的组件。
- 负载均衡器通常分为两类:L4和L7。
- L4和L7负载均衡器在现代架构中都会涉及到。
- L4负载均衡器朝着水平扩展的分布式一致性哈希解决方案发展。
- 由于动态微服务架构的兴起,最近L7负载均衡器被大量使用。
- 全局负载均衡以及控制层和数据层的分离是负载均衡器的未来趋势,也是未来主要的创新点和商业机会。
- 行业正朝着网络解决方案的商业化OSS硬件和软件方向发展。我坚信,传统的负载均衡供应商(例如F5)将会首先被OSS软件和云供应商取代。我认为,传统的路由器/交换机供应商,例如Arista/Cumulus等,在特定部署情况下还有一席之地,但是最终也会被公共云供应商和他们的本土物理网络所取代。
总之,在计算机网络领域,这是一个令人着迷的时期!面向大部分系统的OSS和软件正以惊人的速度迭代发展。此外,随着分布式系统继续通过“无服务器”化而变得动态化,基础网络和负载均衡系统将变得更加复杂。
查看英文原文:Introduction to modern network load balancing and proxying
微信公众号: 架构师日常笔记 欢迎关注!