Docekr风暴:负载均衡那点事儿
程序员文章站
2022-07-15 12:45:39
...
最近我注意到,针对负载均衡和代理这两项现代网络技术,有教育意义的介绍性材料相当稀缺。这引起我的思考:为什么会这样?在可靠的分布系统的架构中,负载均衡是核心概念之一,这一地位要求有对应的高质量信息。
1
网络负载均衡和代理是什么?
Wikipedia 对负载均衡的定义 是:
In computing, load balancing improves the distribution of workloads across multiple computing resources, such as computers, a computer cluster, network links, central processing units, or disk drives. Load balancing aims to optimize resource use, maximize throughput, minimize response time, and avoid overload of any single resource. Using multiple components with load balancing instead of a single component may increase reliability and availability through redundancy. Load balancing usually involves dedicated software or hardware, such as a multilayer switch or a Domain Name System server process.
中文版:
负载平衡(Load balancing)是一种计算机网络技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软件和硬件来完成。
上面的定义不仅包含了网络,还包含了计算的所有方面。操作系统、网络以及容器编排器等都有各自的负载均衡技术,用于使用各自的资源进行各自的任务调度。本文仅就网络负载均衡进行探讨。
对网络负载均衡进行了一个高层次的概括。多个客户端向多个后端发起资源请求,负载均衡器处于客户端和后端之间,简单来说完成如下任务:
服务发现:系统中有哪些后端可用?这些后端的地址(也就是:负载均衡器如何同这些后端通信)?
健康检查:哪些后端是健康的可以用于接收请求?
负载均衡:用什么算法来把独立的请求分发给健康的后端?
在分布式系统中合理的使用负载均衡能带来很多好处:
命名抽象:每个客户端不再需要知道每一个后端(服务发现),客户端可以通过预定义的机制来找到负载均衡器,然后让负载均衡器完成命名解析功能。这些机制包括内置库,以及路人皆知的 DNS/IP/端口 地址,后面会深入讨论。
错误隔离:通过健康检查以及一些其他的算法和技术,负载均衡器的路由方法能够有效的绕过瘫痪或过载的后端。这样运维人员在面对系统故障时,就可以更加从容的进行错误处理。
成本和性能收益:分布式系统的网络的一致性比较差。系统通常要跨越多个网络区域。同一区域内,网络资源通常是低售的;而在跨区域的情况下,超售则是常态(超售和低售的鉴别,是通过网卡上可消耗的带宽和路由器之间的可用带宽进行比对得出的结论)。智能的负载均衡会尽可能保证通信在同一区域内进行,从而提高性能(降低延迟)并降低总体系统成本(降低区域间的带宽和光纤需求)。
负载均衡器 vs 代理服务器
业内谈到网络负载均衡器,Load Balancer 以及 Proxy 这两个术语经常会同样对待,本文中也把这两个词条等价处理(卖弄一下:并非所有的代理都是负载均衡器,但是负载均衡是主流代理的首要功能)。
有人可能会问,有的负载均衡功能是作为客户端库的内置功能完成的,这种负载均衡器就不是代理服务器。这一话题本就容易混淆,这一质问更加让人糊涂。文中会详述这种负载均衡器的拓扑,这种嵌入的负载均衡方式只是代理的一种特例,应用通过内嵌的库来完成代理职能,跟典型的负载均衡器的区别仅在于进程内外而已,其整体抽象是一致的。
四层(连接/会话)负载均衡
业界在讨论负载均衡技术的时候,经常会分为两类:L4 和 L7。这一分类来源于 OSI 模型的四层和七层的定义。OSI 模型无法很好的描述负载均衡方案中的复杂性,一个四层负载均衡在完成传统的四层协议任务例如 UDP 和 TCP 之外,往往还会加入了其他层次的内容。例如一个四层的 TCP 负载均衡还支持 TLS 功能,这算七层负载均衡了么?
展示了一个传统的四层 TCP 负载均衡器。这个例子中,客户端向负载均衡器发起了一个 TCP 连接,负载均衡器 终结 了这一连接(也就是说直接响应了 SYN),接下来选择一个后端,然后创建了到后端的新的 TCP 连接(就是发起了新的 SYN)。图中的细节不需太过关注,后面的四层负载均衡章节会进行详细讨论。
本节的关键点是四层负载均衡一般只在四层的 TCP/UDP 进行操作。笼统的说,负载均衡器负责操作这些字节,保证同一会话的字节们只跟同一个后端打交道。四层负载均衡对通信中的应用细节是一无所知的。通信内容可以是 HTTP、Redis、MongoDB 或者任何应用协议。
七层(应用)负载均衡
四层负载均衡很简单,目前还在大面积使用。四层负载均衡有什么短处,以至于需要七层(应用)负载均衡呢?例如下面几个四层的案例:
两个 gRPC/HTTP2 客户端要连接到后端,所以通过四层负载均衡器来完成这一过程。
四层负载均衡器为每个接入的 TCP 连接创建一个外发的 TCP 连接,这样就有了两个接入、两个外发的连接。
然而客户端 A 的请求频率是每分钟 1 请求,而客户端 B 的频率是每秒钟 50 请求。
在上述场景中,为客户端 A 服务的后端,其负载水平仅相当于为客户端 B 服务的后端的约 1/3000 左右,这明显违背了负载均衡器的初衷。在所有多工、保持连接的协议中都会发生这样的情况(多工意思是在单一四层连接中并行发送应用请求;保持连接则意味着在没有活动请求的情况下,也不会关闭连接)。出于性能方面的考虑(创建连接的成本通常较高,尤其是当使用 TLS 对连接进行加密的情况下),所有的现代协议都包含这两个特性,所以随着时间的推移,四层负载均衡的负载不均的情况会越发明显。七层负载均衡能够解决这一问题。
描述了七层的 HTTP/2 负载均衡。这里客户端发起了对负载均衡的单个连接。负载均衡器连接到了两个后端。当客户端向负载均衡器发送两个 HTTP/2 流的时候,两个流会分别送往不同后端。这样多工客户端的大量不同的请求会被高效的分发给多个后端。这就是七层负载均衡对现代协议的重要性的来由(因为对应用流量的洞察能力,七层负载均衡还有很多其他好处,后面会详细描述)。
七层负载均衡和 OSI 模型
之前四层负载均衡章节中说过,用 OSI 模型来描述负载均衡是有困难的。OSI 描述的第七层,涵盖了负载均衡的多方面功能的臭显。比如对 HTTP 来说,有以下几个低级一些的层次:
可选的 TLS。网络界对 TLS 究竟应该属于 OSI 模型的哪一层素有争议。为讨论方便,我们放在七层。
物理的 HTTP 协议(HTTP/1或 HTTP/2)。
逻辑 HTTP 协议(Header、Body 和 Trailer)。
消息协议(gRPC、REST 等)。
一个成熟的的七层负载均衡器应该照顾到上面描述的每一层次。另一个七层负载均衡器可能只支持七层分类中的特性的一个子集。总而言之,七层负载均衡器的范畴包含了远超四层的为数众多的功能(例子中只提到了了 HTTP;Redis、Kafka、MongoDB 等也都是七层应用协议的例子,也都应受益于七层负载均衡)。
2
负载均衡器特性
下面简单汇总一下负载均衡器的高层功能。并非所有负载均衡器都提供了所有功能。
服务发现
服务发现就是负载均衡器用于决定可用后台列表的过程。这一功能的实现方式花样百出,下面举一些例子:
静态配置文件。
DNS。
Zookeeper、Etcd、Consul 等。
Envoy 的 统一数据平面 API。
健康检查
负载均衡器使用健康检查功能,来检测后端是否可用。健康检查有两种实现思路:
主动式:负载均衡器周期性的向后端发送 ping (例如一个发送到/healthcheck端点的 HTTP 请求),以此判断后端的健康情况。
被动式:负载均衡器通过对主数据流的分析来确定健康情况。比如一个四层负载均衡器,在发现连续三个连接错误的情况下,就会判定一个后端不可用;七层负载均衡可能会在连续三个 HTTP 503 响应之后判定这一后端为不健康状态。
负载均衡
是的,负载均衡器必须为负载做均衡。有了一系列的健康后端,如何选择后端来响应一个请求或者连接呢?负载均衡算法是一个活跃的研究领域,有 Round Robin 这样的简单办法,也有通过延迟和后端负载情况进行判断的复杂方案。 Power of 2 least request load balancing 一文,介绍了最流行的兼顾性能和简易的负载均衡算法之一。
随机选择两个后端,进一步选择其中负载较低的一个。
Session 粘连
在某些应用中有一个重要需求:同一会话的请求需要到达同一后端。对于缓存、临时复杂状态等场景来说这是很必要的。对于“同一会话”的定义有多种形式,可能包括 HTTP Cookie、客户端连接的属性以及其他属性。很多七层负载均衡器具有一些对会话粘连的支持。然而我认为会话粘连是一种天然易碎的情况(处理会话的后端可能会瘫痪),所以对于依赖会话的系统设计应该多加小心。
TLS 终端
TLS 这一话题,不管是边缘服务还是服务间通讯,都值得大书特书。因此很多七层负载均衡器在 TLS 处理方面都做了大量工作,其中包含终端、证书校验和绑定,使用 SNI 提供证书等功能。
观测性
我常说:“观测性、观测性、观测性。”,网络是一个天生不可靠的东西,负载均衡器应该提供状态、跟踪以及日志,协助运维人员甄别故障,从而进行修复。负载均衡器的检测输出差距很大。高级的负载均衡器供应包含数字统计、分布式跟中和自定义日志的大量输出。我认为增强的观测性并不是从天而降的,负载均衡器需要做出很多附加工作来完成这一任务。对性能造成的负面影响,相对于这一系列数据的好处来说,不值一提。
安全性和拒绝服务攻击防范
在边缘部署拓扑(后面会讲解)中,负载均衡器经常需要实现各种包含频率限制、认证以及 DoS 防范(例如 IP 地址的标记和辨识、Tarpitting等方式)等在内的安全功能。
配置和控制平面
负载均衡器应该是可配置的。在大规模部署中,这是一个重要的工作量。通常来说,用来配置负载均衡器的系统被称为“控制平面”,会有多种实现。拙作 Service Mesh Data Plan vs Control Plan 中对这一部分内容作了更深入的探讨。
还有很多
这部分只是对于负载均衡器的功能层面作了一些介绍。下面还会在七层负载均衡器方面做更多的深入讨论。
3
负载均衡器的拓扑分类
我们已经对负载均衡器的概念作了一些概括的介绍,四层和七层负载均衡器的区别,以及负载均衡器特性的汇总,接下来我们会针对分布式系统中的负载均衡器部署拓扑进行一些探讨(下面的每一种拓扑都是适用于四层和七层负载均衡器的)。
中间代理
描述的这种拓扑对多数读者来说都是最熟悉的。这一类别包括了 Cisco、Juniper、F5 等硬件产品;云软件解决方案例如 Amazone 的 ALB 和 NLB 以及 Google 的 Cloud Load Balancer,还有 HAProxy、NGINX 以及 Envoy 这样的纯软件自主方案。中间代理方案的优势在于对用户提供的简便性。
一般情况下用户只要通过 DNS 连接到负载均衡器即可,无需担心其他情况;弱势在于,负载均衡器存在单点失败的风险,同时也是可能的性能瓶颈。中间代理通常是一个不便运维的黑盒子。问题出现在哪里?是客户端还是物理网络?是中间代理还是后端?很难界定。
边缘代理
实际上是中间代理的一种变体,这种负载均衡器可以通过 Internet 进行访问。在这一场景下,负载均衡器通常需要提供一些附加的 “API 网关”类功能,例如 TLS 终端、频率限制、认证以及流量路由等。优势和劣势跟中间服代理类似。在一个大的面向 Internet 的分布式系统中,边缘服务器通常是一个必要的组成部分。
客户端通常会使用某个不受服务提供商控制的网络库,通过 DNS 来访问这一系统(后面将会讨论的嵌入客户库或者 Sidecar 代理拓扑都不适合直接运行在客户端)。另外为了安全方面的考虑,为所有的面向 Internet 的流量使用单一的网关来提供入站流量是一个普遍要求。
嵌入式客户库
为了克服随中间代理而出现的单点失败以及性能问题,很多成熟架构把负载均衡直接植入如图 6 所示的客户端库中。不同的库所支持的功能差别很大,此类产品中最知名的功能丰富的包括 Finagle、Eureka/Ribbon/Hystrix 以及 gRPC(大致基于 Google 的一个称为 Stubby 的内部系统)。这种方式的好处是把所有负载均衡特性完全分布到每个客户端,从而避免了前面说到的单点失败和性能瓶颈。
这种做法的弱势也很明显,一个组织使用的所有语言,都需要实现这种客户端库。分布式架构下,这种多语言支持的要求会越来越多。这种环境里,每种语言的网络库实现造成的成本会让人望而却步。最后,在大的服务架构中进行库升级也是一个很大的挑战,而在生产环境中并行运行不同的版本,又会给运维造成更大压力。
结合上面的优劣势分析,可以知道,在能够限制编程语言使用,并克服升级痛苦的情况下,这种架构是可以获得成功的。
Sidecar 代理
嵌入式客户库的一个变体就是图 7 中的 Sidecar 代理拓扑。近年来,这一拓扑以 “Service Mesh” 的概念日益流行。Sidecar 代理背后的思路是,以进程间通信造成的些许延迟为代价,在无需顾虑编程语言锁定的情况下获得嵌入客户端库的各种优点。目前流行的 Sidecar 负载均衡包括 Envoy、NGINX、HAProxy 以及 Linkerd,我的两篇文章:Introducing Envoy 和 Service Mesh Data Plan vs Control Plan 对这种结构进行了更细致的描写。
不同负载均衡器拓扑的总结和优劣势
中间代理拓扑是最简单的最典型的方式。他的弱点在于:故障单点、伸缩瓶颈以及黑箱操作。
边缘代理拓扑和中间代理类似,通常无法忽略。
嵌入客户端库的方式提供了最好的性能和伸缩性,不过面向多种语言的开发,和升级所有服务的库都是很大的挑战。
Sidecar 代理拓扑比嵌入式客户端库要弱,但也避免了这种方式的两大难点。
总的来说,我认为在服务对服务的情况下,Sidecar 代理拓扑(Service Mesh)会逐渐代替所有其他拓扑形式。为了处理进入 Service Mesh 之前的流量,边缘代理拓扑会长期存在。
4
四层负载均衡的现状
四层负载均衡器还有用么?
本文已经谈论了很多七层负载均衡器对现代协议的好处,后面还会谈到七层负载均衡的功能细节。这是否意味着四层负载均衡器无需存在了?不是的。虽然我认为最终七层负载均衡会完全在服务对服务的场景中取代四层负载均衡器,但四层负载均衡器对边缘通信还是非常有意义的,这是因为所有现代的大型分布式架构都是用了两层的四层/七层负载均衡架构来处理互联网流量。在七层负载均衡器之前部署独立的四层负载均衡器的好处是:
七层负载均衡器要在应用层面进行更多的精密分析、变换以及路由工作,对于原始流量(每秒数据包或每秒字节数)的负载,其能力要弱于优化过的四层负载均衡器。这一现实使得四层负载均衡器更便于应对拒绝服务攻击(例如 SYN flood、通用包 flood 攻击等)。
相对于四层负载均衡器,七层负载均衡器的开发更加活跃,部署更加频繁,也会有更多 Bug。有了前置的四层负载均衡器,就可以在七层负载均衡器升级期间进行健康检查和排空,现代四层负载均衡器通常使用 BGP 和 ECMP(后续章节会详细讲解),七层负载均衡的部署通常会较为简单。最后因为七层负载均衡器相对来说复杂度较高,因此也更容易出现问题,有了前端的四层负载均衡,就可以在出现问题的时候利用路由功能绕过故障节点,提高系统的总体稳定性。
下文中我会讲到集中不同设计的中间/边缘四层负载均衡器。后面提到的设计通常是无法用在客户库或 Sidecar 拓扑中的。
TCP/UDP 终端负载均衡器
还在应用的第一种四层负载均衡器是图 8中的终端负载均衡器。这和我们介绍四层负载均衡的时候讲到的内容是一致的。这种类型的负载均衡器中,使用了两个的独立的 TCP 连接:一个用于客户端和负载均衡器之间;另一个用在负载均衡器和后端之间。
四层终端负载均衡器还有两个原因:
实现相对简单。
靠近客户端的连接终端对性能有显著影响。尤其是如果客户使用有损网络(例如蜂窝网)的话,在进入稳定的有线传输到最终目的之前,是容易发生重传的。换句话说,这种负载均衡器适用于在存在点(Point of Presence )场景下的原始 TCP 连接。
TCP/UDP 透传负载均衡器
四层负载均衡器的另一种类型就是图 9所说的透传负载均衡器。这种类型的负载均衡过程中,TCP连接没有被负载均衡器终结,每个链接的数据包,在经过连接分析和网络地址转换(NAT)过程之后,都被转发给一个选中的后端。首先让我们定义一下连接跟踪和 NAT:
连接跟踪:跟踪全部活动 TCP 连接的过程。这里包括很多内容,例如握手是否完成,是否收到 FIN,连接发呆了多久,这个连接转发给哪一个后端等。
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 连接时,数据包就会返回给负载均衡器,负载均衡器中的链接跟踪和 NAT 再次发挥作用,反向送回数据包。
为什么会使用这一种负载均衡器,而不是前面提到的终端型呢?
性能和资源消耗:透传型的负载均衡器没有终结 TCP 连接,无需缓存任何的 TCP 连接窗口。每个连接的状态存储非常小,通常使用高效的哈希表查询即可。正因如此,相对终端负载均衡器来说,透传型负载均衡器能够处理更大数量的活动链接,单位时间内处理更多的数据包。
允许后端进行拥塞控制:TCP 拥塞控制 是一种机制,让 Internete 端点对外发数据进行流量控制,防止超量使用带宽和缓冲。
为直接服务器返回(Direct Server Return = DSR)做基线,以及四层负载均衡集群:透传负载均衡也是高级四层负载均衡(例如后面将要说到的 DSR 和使用分布式一致性哈希的集群)的基础。
Direct Server Return (DSR)
DSR 构建在前文提到的透传负载均衡器的基础之上。DSR 是一种优化方案,只有入站/请求数据包通过负载均衡;出站/响应数据包绕过负载均衡器直接返回给客户端。使用 DSR 方案的有趣之处在于,很多负载的响应比请求数据量大很多(比如典型的 HTTP 请求和响应)。假设 10% 的流量是请求,另外 90% 是响应,如果使用了 DSR 负载均衡,仅需要 1/10 的容量就能够满足系统需要。从前的负载均衡非常昂贵,这一方案能够显著降低系统成本并提高可靠性(更少就是更好)。DSR 负载均衡器用下面的方式扩展了透传负载均衡的概念:
由于响应包不再经过负载均衡器,所以连接跟踪仅有部分数据,负载均衡器无法知道完整的 TCP 连接状态。然而还是可以通过对客户数据包的观察,对发呆超时的情况来推断具体状态。
负载均衡器通常使用 GRE 来封装 IP 包,并从负载均衡器发送到后端。这样当后端接收到封装后的数据包,可以解包并获得客户端的端口和地址。这样后端就能直接跨过负载均衡器直接发送响应包给客户端了。
DSR 负载均衡器的一个重要部分就是:后端部分的参与了负载均衡过程。后端需要合理的配置 GRE 隧道,并且需要根据网络的低级细节来配置自己的连接跟踪以及 NAT 等。
注意不管是 DSR 还是透传负载均衡器,其中的连接跟踪、NAT、GRE 等组成部分都有很多不同设计方式。这些设置从负载均衡器到后端都会涉及。这部分内容超出了本文的范围,就不再深入进行了。
使用高可用配对方式实现容错
到现在,我们要考虑四层负载均衡的容错设计了。不管是 DSR 还是透传负载均衡都需要一定数量的链接跟踪和状态数据存储在负载均衡器中。负载均衡器宕机了会怎样?——所有经过这一负载均衡器的连接都会断掉。可能对应用产生显著影响。
历史上,四层负载均衡器是一种从典型供应商(Cisco、Juniper、F5 等)采购的硬件。这些设备非常昂贵,能够处理大量通信。为了防止单点失败导致的所有连接中断,引发应用故障,负载均衡器通常会使用高可用配对的方式进行部署,如图 11 所示。典型的高可用负载均衡器配置会满足如下设计:
一对高可用边缘路由器提供一系列的虚拟 IP(VIP)。这些边缘路由器使用边界网关协议(BGP)来发布虚拟 IP。主要边缘路由器的 BGP 优先级高于备用边缘路由器,所以正常情况下他会处理所有流量(BGP 是一个超级复杂的协议;为了行文方便,可以理解 BGP 是一种机制,这种机制让网络设备可以宣称自身能够处理来自其他网络设备的流量,并且每个连接都会有一个权重,从而影响连接的通信)。
类似的,主要四层负载均衡向 BGP 权重较高的边缘路由器宣告可用,所以在通常情况下,他会处理所有流量。
两个边缘路由器和两个负载均衡器是交叉连接的,这意味着如果一个边缘路由器或者一个负载均衡器宕机,或者因为某些原因 BGP 宣布失效,备用设备就会接入,承载所有流量。
上面的设置是目前很多互联网上的高流量应用还在持续使用的方式,但是这种方案有一些副作用:
VIP 必须通过高可用负载均衡器对流量进行正确的分配。如果单一 VIP 的容量超过了 HA 对,则 VIP 需要分割为多个 VIP。
系统资源使用率很低。通常会有一半的容量在闲置。过去的负载均衡器非常昂贵,所以这一闲置成本也是很高的。
现代分布式系统设计要求比主备模式更好的容错设计。例如一个系统需要在多个同时出现故障的情况下保持运行。高可用负载均衡对如果遭遇同时故障,就会导致完全瘫痪。
供应商提供的硬件设备价格昂贵,会导致对特定厂商的依赖。使用支持水平扩展的软件方案对商业硬件设施进行替换,是目前普遍存在的迫切需要。
使用分布式一致性哈希进行容错和伸缩
前文介绍了通过高可用配对的方式来进行负载均衡器的容错设置,以及随之而来的问题。千禧年初,一些大的互联网基础设施提供商开始设计和部署新的图 12 模式的并行四层负载均衡系统。这类系统的目标是:
解决使用成对 HA 模式设计的负载均衡方案带来的所有问题。
从厂商专属的专利硬件模式的负载均衡器中迁移出来,使用标准服务器和网卡配合商业软件方案进行替代。
四层负载均衡器的设计是 fault tolerance and scaling via clustering and distributed consistent hashing 的最佳引用,具体工作模式是:
N 个边缘路由器在同一 BGP 权重上,声明所有的 任播 VIP。使用 ECMP(等价多路径路由) 来保证所有单一 Flow 能够到达同一个边缘路由器。一个 Flow 通常是一个由源 IP/端口和目的 IP/端口 构成的元组。(简单说,ECMP 是一个在使用一致性哈希连接的独立加权网络上分发数据包的方式)。边缘路由器并不在意哪些数据包去了哪里。一般会倾向于把来自于一个 Flow 所有数据包发送给同一套连接,以此避免乱序包导致的性能损失。
N 个四层负载均衡器向边缘路由器在同一个 BGP 权重上声明所有的 VIP。再次借助 ECMP,边缘路由器会选择为同一个 Flow 选择同一个负载均衡器。
每个四层负载均衡器会进行部分的连接跟踪,为这一 Flow 使用一致性哈希选择一个后端。从负载均衡器到后端的数据包会使用 GRE 进行封装。
接下来使用 DSR 将相应包直接从后端通过边缘路由器发送会客户端。
四层负载均衡器使用的一致性哈希算法是一个活跃的研究领域,其中涉及合理分配、减小延迟、后端变更成本以及最小化内存开支等各方面要素。这方面的完整讨论也超出了本文范围。
接下来看看这种设计如何克服 HA 配对方式的缺点:
新的边缘路由器和负载均衡器可以按需加入。一致性哈希会在各个层次上使用,在加入新机器的时候,尽可能降低受影响的 Flow 数量。
在保持对突发消耗的支持能力以及容错能力的同时,可以提高资源的使用率。
边缘路由器和负载均衡都可以使用使用普通硬件,仅是传统硬件负载均衡成本的几分之一(下文会进一步说明)。
行文至此,有读者可能会问:“为什么不让边缘路由器直接通过 ECMP 和后端进行通信?为什么我们还需要负载均衡器?”。答案是 DoS 防范以及后端运维难度。如果没有负载均衡器支持,每个后端都需要加入 BGP,而且难于进行滚动更新。
所有的现代四层负载均衡系统都在向着这一方案(或其变体)迁移。两个最为公众所知的例子就是 Google 的 Maglev 以及 Amazon 的 Network Load Balancer (NLB)。目前还没有任何开源负载均衡器实现了这一设计,然而据我所知,有公司要在 2018 年发布一个这样的产品。我很期待他的出现,这一产品将会填补开源网络组件的重要空白。
当前七层负载均衡的技术状态
Current state of the art in L7 load balancing The proxy wars in tech currently is quite literally the proxy wars. Or the "war of the proxies". Nginx plus, HAProxy, linkerd, Envoy all quite literally killing it. And proxy-as-a-service/routing-as-a-service SaaS vendors raising the bar as well. Very interesting times!
是的,的确是这样。近几年我们看到七层负载均衡/代理技术的开发工作开始复兴。随着分布式微服务系统的不断推进,这方面也会不断进步。从基础上看,目前对于靠不住的网络的依赖越发严重,带来更高的运维难度。从长远看,自动伸缩、容器编排等技术的大量使用,意味着静态 IP、静态文件的时代已经过去了。系统不仅更多的使用网络,而且更加动态,需要新的负载均衡功能。在本节中我们简单归纳一下现在七层负载均衡器的功能。
协议支持
现代七层负载均衡器加入了很多不同协议的显式支持。负载均衡器对应用通讯认识越多,就越能做好监控输出、高级负载均衡和路由等功能。例如目前 Envoy 显式的支持七层协议解析和路由的范围包括 HTTP/1、HTTP/2、gRPC、Redis、MongoDB 以及 DynamoDB。包括 MySQL 和 Kafka 在内的更多协议会逐渐添加进来。
动态配置
如上文所述,分布式系统的大行其道,需要有动态配置能力来提高系统的动态和控制能力。Istio就是一个这种系统的例子。请参考 Service Mesh Data Plan vs Control Plan 来获取更多这方面的内容。
高级负载均衡
七层负载均衡器一般都有内置的高级负载均衡支持,例如超时、重试、频率控制、断路器、Shadow、缓冲、基于内容的路由等。
可观测性
上文中也提到过,负载均衡的一个常见功能,越来越多的动态系统被部署,调试难度也水涨船高。健壮的协议规范观测输出可能是未来七层负载均衡器要提供的最重要功能之一。统计数字、分布式跟踪以及可以定义日志的输出,目前已经成为对器层负载均衡解决方案的必须要求。
扩展性
现代七层负载均衡器需要能够简单的加入定制功能。这一功能可以通过编写可插接过滤器,并载入到负载均衡器的方式来实现。很多负载均衡器还支持脚本扩展,例如 Lua。
容错
在讲四层负载均衡的容错时颇费了一番口舌。七层负载均衡的容错又怎样呢?一般来说我们认为七层负载均衡器(的工作负载)是无状态的可抛弃的。使用普通软件就能够简单的对七层负载均衡器进行水平扩展。另外七层负载均衡的流量处理和状态跟踪的复杂度要远超四层。设置七层负载均衡器的 HA 配对在技术上是可行的,但会非常繁杂。
总的说来,不管是四层还是七层的负载均衡,业界都在尝试走出 HA 配对模式,转向一致性哈希为基础的水平扩展方式。
更多
七层负载均衡器正在高速发展。可以参考 Envoy 架构概览。
5
全局负载均衡和中心控制平面
未来会有越来越多的独立负载均衡器以商品设备的面目呈现。我认为真正的创新和商业机会来自于控制平面。图 13展示了一个全局负载均衡系统。在本例中有一些不同的东西:
每个 Sidecar 代理和三个不同区域的后端进行通信。
如图,90% 的流量会被发送到 C 区,A B 两区分别得到 5%。
Sidecar 代理和后端都会周期性的向全局负载均衡器进行汇报。这样全局负载均衡器就可以根据延迟、成本、负载、失败率等数据进行精密决策。
全局负载均衡周期性的使用当前的路由信息配置每个 Sidecar 代理。
全局负载均衡能做一些单一负载均衡器做不到的事情,例如:
对分区故障的自动检测和路由绕行。
应用全局的安全和路由策略。
使用机器学习和神经网络,对异常流量进行检测和防范,包括分布式拒绝服务攻击。
提供*界面和可视化支持,让工程师能够以聚合的角度,对整个分布式系统进行监控和运维。
全局负载均衡器要成为现实,他的数据平面必须具有强大的动态配置能力。请参考我的博客:Envoy's universal data plane API,以及 Service Mesh Data Plan vs Control Plan 。
6
结论,以及负载均衡器的未来
综上所述,本文的主旨:
负载均衡器是现代分布式系统的关键组件。
有两种负载均衡器:四层和七层。
四层和七层负载均衡器都与现代架构相关。
四层负载均衡器正在朝着基于一致性哈希的水平扩展方案的方向进行迁移。
由于动态微服务架构的成长,七层负载均衡器得以快速发展。
控制平面和数据平面的拆分,和全局负载均衡,是负载均衡的未来发展方向和机会来源。
业界正激进的迈向标准硬件和软件的开源解决方案。我相信传统的像 F5 这样的负载均衡厂商会被开源软件和云供应商取代。传统的路由器、交换机厂商,例如 Arista/Cumulus 等,短期内会有更好的发展,但最终也会被共有云提供商及其自研物理网络所取代。
总的说来,我认为这是计算器网络的奇迹年代。多数系统开始向开源和软件方向转变,极大的提高了迭代速度。未来,分布式系统又向无服务器计算进军,底层网络和负载均衡系统的复杂性也势必齐头并进,水涨船高。
1
网络负载均衡和代理是什么?
Wikipedia 对负载均衡的定义 是:
In computing, load balancing improves the distribution of workloads across multiple computing resources, such as computers, a computer cluster, network links, central processing units, or disk drives. Load balancing aims to optimize resource use, maximize throughput, minimize response time, and avoid overload of any single resource. Using multiple components with load balancing instead of a single component may increase reliability and availability through redundancy. Load balancing usually involves dedicated software or hardware, such as a multilayer switch or a Domain Name System server process.
中文版:
负载平衡(Load balancing)是一种计算机网络技术,用来在多个计算机(计算机集群)、网络连接、CPU、磁盘驱动器或其他资源中分配负载,以达到最优化资源使用、最大化吞吐率、最小化响应时间、同时避免过载的目的。使用带有负载平衡的多个服务器组件,取代单一的组件,可以通过冗余提高可靠性。负载平衡服务通常是由专用软件和硬件来完成。
上面的定义不仅包含了网络,还包含了计算的所有方面。操作系统、网络以及容器编排器等都有各自的负载均衡技术,用于使用各自的资源进行各自的任务调度。本文仅就网络负载均衡进行探讨。
对网络负载均衡进行了一个高层次的概括。多个客户端向多个后端发起资源请求,负载均衡器处于客户端和后端之间,简单来说完成如下任务:
服务发现:系统中有哪些后端可用?这些后端的地址(也就是:负载均衡器如何同这些后端通信)?
健康检查:哪些后端是健康的可以用于接收请求?
负载均衡:用什么算法来把独立的请求分发给健康的后端?
在分布式系统中合理的使用负载均衡能带来很多好处:
命名抽象:每个客户端不再需要知道每一个后端(服务发现),客户端可以通过预定义的机制来找到负载均衡器,然后让负载均衡器完成命名解析功能。这些机制包括内置库,以及路人皆知的 DNS/IP/端口 地址,后面会深入讨论。
错误隔离:通过健康检查以及一些其他的算法和技术,负载均衡器的路由方法能够有效的绕过瘫痪或过载的后端。这样运维人员在面对系统故障时,就可以更加从容的进行错误处理。
成本和性能收益:分布式系统的网络的一致性比较差。系统通常要跨越多个网络区域。同一区域内,网络资源通常是低售的;而在跨区域的情况下,超售则是常态(超售和低售的鉴别,是通过网卡上可消耗的带宽和路由器之间的可用带宽进行比对得出的结论)。智能的负载均衡会尽可能保证通信在同一区域内进行,从而提高性能(降低延迟)并降低总体系统成本(降低区域间的带宽和光纤需求)。
负载均衡器 vs 代理服务器
业内谈到网络负载均衡器,Load Balancer 以及 Proxy 这两个术语经常会同样对待,本文中也把这两个词条等价处理(卖弄一下:并非所有的代理都是负载均衡器,但是负载均衡是主流代理的首要功能)。
有人可能会问,有的负载均衡功能是作为客户端库的内置功能完成的,这种负载均衡器就不是代理服务器。这一话题本就容易混淆,这一质问更加让人糊涂。文中会详述这种负载均衡器的拓扑,这种嵌入的负载均衡方式只是代理的一种特例,应用通过内嵌的库来完成代理职能,跟典型的负载均衡器的区别仅在于进程内外而已,其整体抽象是一致的。
四层(连接/会话)负载均衡
业界在讨论负载均衡技术的时候,经常会分为两类:L4 和 L7。这一分类来源于 OSI 模型的四层和七层的定义。OSI 模型无法很好的描述负载均衡方案中的复杂性,一个四层负载均衡在完成传统的四层协议任务例如 UDP 和 TCP 之外,往往还会加入了其他层次的内容。例如一个四层的 TCP 负载均衡还支持 TLS 功能,这算七层负载均衡了么?
展示了一个传统的四层 TCP 负载均衡器。这个例子中,客户端向负载均衡器发起了一个 TCP 连接,负载均衡器 终结 了这一连接(也就是说直接响应了 SYN),接下来选择一个后端,然后创建了到后端的新的 TCP 连接(就是发起了新的 SYN)。图中的细节不需太过关注,后面的四层负载均衡章节会进行详细讨论。
本节的关键点是四层负载均衡一般只在四层的 TCP/UDP 进行操作。笼统的说,负载均衡器负责操作这些字节,保证同一会话的字节们只跟同一个后端打交道。四层负载均衡对通信中的应用细节是一无所知的。通信内容可以是 HTTP、Redis、MongoDB 或者任何应用协议。
七层(应用)负载均衡
四层负载均衡很简单,目前还在大面积使用。四层负载均衡有什么短处,以至于需要七层(应用)负载均衡呢?例如下面几个四层的案例:
两个 gRPC/HTTP2 客户端要连接到后端,所以通过四层负载均衡器来完成这一过程。
四层负载均衡器为每个接入的 TCP 连接创建一个外发的 TCP 连接,这样就有了两个接入、两个外发的连接。
然而客户端 A 的请求频率是每分钟 1 请求,而客户端 B 的频率是每秒钟 50 请求。
在上述场景中,为客户端 A 服务的后端,其负载水平仅相当于为客户端 B 服务的后端的约 1/3000 左右,这明显违背了负载均衡器的初衷。在所有多工、保持连接的协议中都会发生这样的情况(多工意思是在单一四层连接中并行发送应用请求;保持连接则意味着在没有活动请求的情况下,也不会关闭连接)。出于性能方面的考虑(创建连接的成本通常较高,尤其是当使用 TLS 对连接进行加密的情况下),所有的现代协议都包含这两个特性,所以随着时间的推移,四层负载均衡的负载不均的情况会越发明显。七层负载均衡能够解决这一问题。
描述了七层的 HTTP/2 负载均衡。这里客户端发起了对负载均衡的单个连接。负载均衡器连接到了两个后端。当客户端向负载均衡器发送两个 HTTP/2 流的时候,两个流会分别送往不同后端。这样多工客户端的大量不同的请求会被高效的分发给多个后端。这就是七层负载均衡对现代协议的重要性的来由(因为对应用流量的洞察能力,七层负载均衡还有很多其他好处,后面会详细描述)。
七层负载均衡和 OSI 模型
之前四层负载均衡章节中说过,用 OSI 模型来描述负载均衡是有困难的。OSI 描述的第七层,涵盖了负载均衡的多方面功能的臭显。比如对 HTTP 来说,有以下几个低级一些的层次:
可选的 TLS。网络界对 TLS 究竟应该属于 OSI 模型的哪一层素有争议。为讨论方便,我们放在七层。
物理的 HTTP 协议(HTTP/1或 HTTP/2)。
逻辑 HTTP 协议(Header、Body 和 Trailer)。
消息协议(gRPC、REST 等)。
一个成熟的的七层负载均衡器应该照顾到上面描述的每一层次。另一个七层负载均衡器可能只支持七层分类中的特性的一个子集。总而言之,七层负载均衡器的范畴包含了远超四层的为数众多的功能(例子中只提到了了 HTTP;Redis、Kafka、MongoDB 等也都是七层应用协议的例子,也都应受益于七层负载均衡)。
2
负载均衡器特性
下面简单汇总一下负载均衡器的高层功能。并非所有负载均衡器都提供了所有功能。
服务发现
服务发现就是负载均衡器用于决定可用后台列表的过程。这一功能的实现方式花样百出,下面举一些例子:
静态配置文件。
DNS。
Zookeeper、Etcd、Consul 等。
Envoy 的 统一数据平面 API。
健康检查
负载均衡器使用健康检查功能,来检测后端是否可用。健康检查有两种实现思路:
主动式:负载均衡器周期性的向后端发送 ping (例如一个发送到/healthcheck端点的 HTTP 请求),以此判断后端的健康情况。
被动式:负载均衡器通过对主数据流的分析来确定健康情况。比如一个四层负载均衡器,在发现连续三个连接错误的情况下,就会判定一个后端不可用;七层负载均衡可能会在连续三个 HTTP 503 响应之后判定这一后端为不健康状态。
负载均衡
是的,负载均衡器必须为负载做均衡。有了一系列的健康后端,如何选择后端来响应一个请求或者连接呢?负载均衡算法是一个活跃的研究领域,有 Round Robin 这样的简单办法,也有通过延迟和后端负载情况进行判断的复杂方案。 Power of 2 least request load balancing 一文,介绍了最流行的兼顾性能和简易的负载均衡算法之一。
随机选择两个后端,进一步选择其中负载较低的一个。
Session 粘连
在某些应用中有一个重要需求:同一会话的请求需要到达同一后端。对于缓存、临时复杂状态等场景来说这是很必要的。对于“同一会话”的定义有多种形式,可能包括 HTTP Cookie、客户端连接的属性以及其他属性。很多七层负载均衡器具有一些对会话粘连的支持。然而我认为会话粘连是一种天然易碎的情况(处理会话的后端可能会瘫痪),所以对于依赖会话的系统设计应该多加小心。
TLS 终端
TLS 这一话题,不管是边缘服务还是服务间通讯,都值得大书特书。因此很多七层负载均衡器在 TLS 处理方面都做了大量工作,其中包含终端、证书校验和绑定,使用 SNI 提供证书等功能。
观测性
我常说:“观测性、观测性、观测性。”,网络是一个天生不可靠的东西,负载均衡器应该提供状态、跟踪以及日志,协助运维人员甄别故障,从而进行修复。负载均衡器的检测输出差距很大。高级的负载均衡器供应包含数字统计、分布式跟中和自定义日志的大量输出。我认为增强的观测性并不是从天而降的,负载均衡器需要做出很多附加工作来完成这一任务。对性能造成的负面影响,相对于这一系列数据的好处来说,不值一提。
安全性和拒绝服务攻击防范
在边缘部署拓扑(后面会讲解)中,负载均衡器经常需要实现各种包含频率限制、认证以及 DoS 防范(例如 IP 地址的标记和辨识、Tarpitting等方式)等在内的安全功能。
配置和控制平面
负载均衡器应该是可配置的。在大规模部署中,这是一个重要的工作量。通常来说,用来配置负载均衡器的系统被称为“控制平面”,会有多种实现。拙作 Service Mesh Data Plan vs Control Plan 中对这一部分内容作了更深入的探讨。
还有很多
这部分只是对于负载均衡器的功能层面作了一些介绍。下面还会在七层负载均衡器方面做更多的深入讨论。
3
负载均衡器的拓扑分类
我们已经对负载均衡器的概念作了一些概括的介绍,四层和七层负载均衡器的区别,以及负载均衡器特性的汇总,接下来我们会针对分布式系统中的负载均衡器部署拓扑进行一些探讨(下面的每一种拓扑都是适用于四层和七层负载均衡器的)。
中间代理
描述的这种拓扑对多数读者来说都是最熟悉的。这一类别包括了 Cisco、Juniper、F5 等硬件产品;云软件解决方案例如 Amazone 的 ALB 和 NLB 以及 Google 的 Cloud Load Balancer,还有 HAProxy、NGINX 以及 Envoy 这样的纯软件自主方案。中间代理方案的优势在于对用户提供的简便性。
一般情况下用户只要通过 DNS 连接到负载均衡器即可,无需担心其他情况;弱势在于,负载均衡器存在单点失败的风险,同时也是可能的性能瓶颈。中间代理通常是一个不便运维的黑盒子。问题出现在哪里?是客户端还是物理网络?是中间代理还是后端?很难界定。
边缘代理
实际上是中间代理的一种变体,这种负载均衡器可以通过 Internet 进行访问。在这一场景下,负载均衡器通常需要提供一些附加的 “API 网关”类功能,例如 TLS 终端、频率限制、认证以及流量路由等。优势和劣势跟中间服代理类似。在一个大的面向 Internet 的分布式系统中,边缘服务器通常是一个必要的组成部分。
客户端通常会使用某个不受服务提供商控制的网络库,通过 DNS 来访问这一系统(后面将会讨论的嵌入客户库或者 Sidecar 代理拓扑都不适合直接运行在客户端)。另外为了安全方面的考虑,为所有的面向 Internet 的流量使用单一的网关来提供入站流量是一个普遍要求。
嵌入式客户库
为了克服随中间代理而出现的单点失败以及性能问题,很多成熟架构把负载均衡直接植入如图 6 所示的客户端库中。不同的库所支持的功能差别很大,此类产品中最知名的功能丰富的包括 Finagle、Eureka/Ribbon/Hystrix 以及 gRPC(大致基于 Google 的一个称为 Stubby 的内部系统)。这种方式的好处是把所有负载均衡特性完全分布到每个客户端,从而避免了前面说到的单点失败和性能瓶颈。
这种做法的弱势也很明显,一个组织使用的所有语言,都需要实现这种客户端库。分布式架构下,这种多语言支持的要求会越来越多。这种环境里,每种语言的网络库实现造成的成本会让人望而却步。最后,在大的服务架构中进行库升级也是一个很大的挑战,而在生产环境中并行运行不同的版本,又会给运维造成更大压力。
结合上面的优劣势分析,可以知道,在能够限制编程语言使用,并克服升级痛苦的情况下,这种架构是可以获得成功的。
Sidecar 代理
嵌入式客户库的一个变体就是图 7 中的 Sidecar 代理拓扑。近年来,这一拓扑以 “Service Mesh” 的概念日益流行。Sidecar 代理背后的思路是,以进程间通信造成的些许延迟为代价,在无需顾虑编程语言锁定的情况下获得嵌入客户端库的各种优点。目前流行的 Sidecar 负载均衡包括 Envoy、NGINX、HAProxy 以及 Linkerd,我的两篇文章:Introducing Envoy 和 Service Mesh Data Plan vs Control Plan 对这种结构进行了更细致的描写。
不同负载均衡器拓扑的总结和优劣势
中间代理拓扑是最简单的最典型的方式。他的弱点在于:故障单点、伸缩瓶颈以及黑箱操作。
边缘代理拓扑和中间代理类似,通常无法忽略。
嵌入客户端库的方式提供了最好的性能和伸缩性,不过面向多种语言的开发,和升级所有服务的库都是很大的挑战。
Sidecar 代理拓扑比嵌入式客户端库要弱,但也避免了这种方式的两大难点。
总的来说,我认为在服务对服务的情况下,Sidecar 代理拓扑(Service Mesh)会逐渐代替所有其他拓扑形式。为了处理进入 Service Mesh 之前的流量,边缘代理拓扑会长期存在。
4
四层负载均衡的现状
四层负载均衡器还有用么?
本文已经谈论了很多七层负载均衡器对现代协议的好处,后面还会谈到七层负载均衡的功能细节。这是否意味着四层负载均衡器无需存在了?不是的。虽然我认为最终七层负载均衡会完全在服务对服务的场景中取代四层负载均衡器,但四层负载均衡器对边缘通信还是非常有意义的,这是因为所有现代的大型分布式架构都是用了两层的四层/七层负载均衡架构来处理互联网流量。在七层负载均衡器之前部署独立的四层负载均衡器的好处是:
七层负载均衡器要在应用层面进行更多的精密分析、变换以及路由工作,对于原始流量(每秒数据包或每秒字节数)的负载,其能力要弱于优化过的四层负载均衡器。这一现实使得四层负载均衡器更便于应对拒绝服务攻击(例如 SYN flood、通用包 flood 攻击等)。
相对于四层负载均衡器,七层负载均衡器的开发更加活跃,部署更加频繁,也会有更多 Bug。有了前置的四层负载均衡器,就可以在七层负载均衡器升级期间进行健康检查和排空,现代四层负载均衡器通常使用 BGP 和 ECMP(后续章节会详细讲解),七层负载均衡的部署通常会较为简单。最后因为七层负载均衡器相对来说复杂度较高,因此也更容易出现问题,有了前端的四层负载均衡,就可以在出现问题的时候利用路由功能绕过故障节点,提高系统的总体稳定性。
下文中我会讲到集中不同设计的中间/边缘四层负载均衡器。后面提到的设计通常是无法用在客户库或 Sidecar 拓扑中的。
TCP/UDP 终端负载均衡器
还在应用的第一种四层负载均衡器是图 8中的终端负载均衡器。这和我们介绍四层负载均衡的时候讲到的内容是一致的。这种类型的负载均衡器中,使用了两个的独立的 TCP 连接:一个用于客户端和负载均衡器之间;另一个用在负载均衡器和后端之间。
四层终端负载均衡器还有两个原因:
实现相对简单。
靠近客户端的连接终端对性能有显著影响。尤其是如果客户使用有损网络(例如蜂窝网)的话,在进入稳定的有线传输到最终目的之前,是容易发生重传的。换句话说,这种负载均衡器适用于在存在点(Point of Presence )场景下的原始 TCP 连接。
TCP/UDP 透传负载均衡器
四层负载均衡器的另一种类型就是图 9所说的透传负载均衡器。这种类型的负载均衡过程中,TCP连接没有被负载均衡器终结,每个链接的数据包,在经过连接分析和网络地址转换(NAT)过程之后,都被转发给一个选中的后端。首先让我们定义一下连接跟踪和 NAT:
连接跟踪:跟踪全部活动 TCP 连接的过程。这里包括很多内容,例如握手是否完成,是否收到 FIN,连接发呆了多久,这个连接转发给哪一个后端等。
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 连接时,数据包就会返回给负载均衡器,负载均衡器中的链接跟踪和 NAT 再次发挥作用,反向送回数据包。
为什么会使用这一种负载均衡器,而不是前面提到的终端型呢?
性能和资源消耗:透传型的负载均衡器没有终结 TCP 连接,无需缓存任何的 TCP 连接窗口。每个连接的状态存储非常小,通常使用高效的哈希表查询即可。正因如此,相对终端负载均衡器来说,透传型负载均衡器能够处理更大数量的活动链接,单位时间内处理更多的数据包。
允许后端进行拥塞控制:TCP 拥塞控制 是一种机制,让 Internete 端点对外发数据进行流量控制,防止超量使用带宽和缓冲。
为直接服务器返回(Direct Server Return = DSR)做基线,以及四层负载均衡集群:透传负载均衡也是高级四层负载均衡(例如后面将要说到的 DSR 和使用分布式一致性哈希的集群)的基础。
Direct Server Return (DSR)
DSR 构建在前文提到的透传负载均衡器的基础之上。DSR 是一种优化方案,只有入站/请求数据包通过负载均衡;出站/响应数据包绕过负载均衡器直接返回给客户端。使用 DSR 方案的有趣之处在于,很多负载的响应比请求数据量大很多(比如典型的 HTTP 请求和响应)。假设 10% 的流量是请求,另外 90% 是响应,如果使用了 DSR 负载均衡,仅需要 1/10 的容量就能够满足系统需要。从前的负载均衡非常昂贵,这一方案能够显著降低系统成本并提高可靠性(更少就是更好)。DSR 负载均衡器用下面的方式扩展了透传负载均衡的概念:
由于响应包不再经过负载均衡器,所以连接跟踪仅有部分数据,负载均衡器无法知道完整的 TCP 连接状态。然而还是可以通过对客户数据包的观察,对发呆超时的情况来推断具体状态。
负载均衡器通常使用 GRE 来封装 IP 包,并从负载均衡器发送到后端。这样当后端接收到封装后的数据包,可以解包并获得客户端的端口和地址。这样后端就能直接跨过负载均衡器直接发送响应包给客户端了。
DSR 负载均衡器的一个重要部分就是:后端部分的参与了负载均衡过程。后端需要合理的配置 GRE 隧道,并且需要根据网络的低级细节来配置自己的连接跟踪以及 NAT 等。
注意不管是 DSR 还是透传负载均衡器,其中的连接跟踪、NAT、GRE 等组成部分都有很多不同设计方式。这些设置从负载均衡器到后端都会涉及。这部分内容超出了本文的范围,就不再深入进行了。
使用高可用配对方式实现容错
到现在,我们要考虑四层负载均衡的容错设计了。不管是 DSR 还是透传负载均衡都需要一定数量的链接跟踪和状态数据存储在负载均衡器中。负载均衡器宕机了会怎样?——所有经过这一负载均衡器的连接都会断掉。可能对应用产生显著影响。
历史上,四层负载均衡器是一种从典型供应商(Cisco、Juniper、F5 等)采购的硬件。这些设备非常昂贵,能够处理大量通信。为了防止单点失败导致的所有连接中断,引发应用故障,负载均衡器通常会使用高可用配对的方式进行部署,如图 11 所示。典型的高可用负载均衡器配置会满足如下设计:
一对高可用边缘路由器提供一系列的虚拟 IP(VIP)。这些边缘路由器使用边界网关协议(BGP)来发布虚拟 IP。主要边缘路由器的 BGP 优先级高于备用边缘路由器,所以正常情况下他会处理所有流量(BGP 是一个超级复杂的协议;为了行文方便,可以理解 BGP 是一种机制,这种机制让网络设备可以宣称自身能够处理来自其他网络设备的流量,并且每个连接都会有一个权重,从而影响连接的通信)。
类似的,主要四层负载均衡向 BGP 权重较高的边缘路由器宣告可用,所以在通常情况下,他会处理所有流量。
两个边缘路由器和两个负载均衡器是交叉连接的,这意味着如果一个边缘路由器或者一个负载均衡器宕机,或者因为某些原因 BGP 宣布失效,备用设备就会接入,承载所有流量。
上面的设置是目前很多互联网上的高流量应用还在持续使用的方式,但是这种方案有一些副作用:
VIP 必须通过高可用负载均衡器对流量进行正确的分配。如果单一 VIP 的容量超过了 HA 对,则 VIP 需要分割为多个 VIP。
系统资源使用率很低。通常会有一半的容量在闲置。过去的负载均衡器非常昂贵,所以这一闲置成本也是很高的。
现代分布式系统设计要求比主备模式更好的容错设计。例如一个系统需要在多个同时出现故障的情况下保持运行。高可用负载均衡对如果遭遇同时故障,就会导致完全瘫痪。
供应商提供的硬件设备价格昂贵,会导致对特定厂商的依赖。使用支持水平扩展的软件方案对商业硬件设施进行替换,是目前普遍存在的迫切需要。
使用分布式一致性哈希进行容错和伸缩
前文介绍了通过高可用配对的方式来进行负载均衡器的容错设置,以及随之而来的问题。千禧年初,一些大的互联网基础设施提供商开始设计和部署新的图 12 模式的并行四层负载均衡系统。这类系统的目标是:
解决使用成对 HA 模式设计的负载均衡方案带来的所有问题。
从厂商专属的专利硬件模式的负载均衡器中迁移出来,使用标准服务器和网卡配合商业软件方案进行替代。
四层负载均衡器的设计是 fault tolerance and scaling via clustering and distributed consistent hashing 的最佳引用,具体工作模式是:
N 个边缘路由器在同一 BGP 权重上,声明所有的 任播 VIP。使用 ECMP(等价多路径路由) 来保证所有单一 Flow 能够到达同一个边缘路由器。一个 Flow 通常是一个由源 IP/端口和目的 IP/端口 构成的元组。(简单说,ECMP 是一个在使用一致性哈希连接的独立加权网络上分发数据包的方式)。边缘路由器并不在意哪些数据包去了哪里。一般会倾向于把来自于一个 Flow 所有数据包发送给同一套连接,以此避免乱序包导致的性能损失。
N 个四层负载均衡器向边缘路由器在同一个 BGP 权重上声明所有的 VIP。再次借助 ECMP,边缘路由器会选择为同一个 Flow 选择同一个负载均衡器。
每个四层负载均衡器会进行部分的连接跟踪,为这一 Flow 使用一致性哈希选择一个后端。从负载均衡器到后端的数据包会使用 GRE 进行封装。
接下来使用 DSR 将相应包直接从后端通过边缘路由器发送会客户端。
四层负载均衡器使用的一致性哈希算法是一个活跃的研究领域,其中涉及合理分配、减小延迟、后端变更成本以及最小化内存开支等各方面要素。这方面的完整讨论也超出了本文范围。
接下来看看这种设计如何克服 HA 配对方式的缺点:
新的边缘路由器和负载均衡器可以按需加入。一致性哈希会在各个层次上使用,在加入新机器的时候,尽可能降低受影响的 Flow 数量。
在保持对突发消耗的支持能力以及容错能力的同时,可以提高资源的使用率。
边缘路由器和负载均衡都可以使用使用普通硬件,仅是传统硬件负载均衡成本的几分之一(下文会进一步说明)。
行文至此,有读者可能会问:“为什么不让边缘路由器直接通过 ECMP 和后端进行通信?为什么我们还需要负载均衡器?”。答案是 DoS 防范以及后端运维难度。如果没有负载均衡器支持,每个后端都需要加入 BGP,而且难于进行滚动更新。
所有的现代四层负载均衡系统都在向着这一方案(或其变体)迁移。两个最为公众所知的例子就是 Google 的 Maglev 以及 Amazon 的 Network Load Balancer (NLB)。目前还没有任何开源负载均衡器实现了这一设计,然而据我所知,有公司要在 2018 年发布一个这样的产品。我很期待他的出现,这一产品将会填补开源网络组件的重要空白。
当前七层负载均衡的技术状态
Current state of the art in L7 load balancing The proxy wars in tech currently is quite literally the proxy wars. Or the "war of the proxies". Nginx plus, HAProxy, linkerd, Envoy all quite literally killing it. And proxy-as-a-service/routing-as-a-service SaaS vendors raising the bar as well. Very interesting times!
是的,的确是这样。近几年我们看到七层负载均衡/代理技术的开发工作开始复兴。随着分布式微服务系统的不断推进,这方面也会不断进步。从基础上看,目前对于靠不住的网络的依赖越发严重,带来更高的运维难度。从长远看,自动伸缩、容器编排等技术的大量使用,意味着静态 IP、静态文件的时代已经过去了。系统不仅更多的使用网络,而且更加动态,需要新的负载均衡功能。在本节中我们简单归纳一下现在七层负载均衡器的功能。
协议支持
现代七层负载均衡器加入了很多不同协议的显式支持。负载均衡器对应用通讯认识越多,就越能做好监控输出、高级负载均衡和路由等功能。例如目前 Envoy 显式的支持七层协议解析和路由的范围包括 HTTP/1、HTTP/2、gRPC、Redis、MongoDB 以及 DynamoDB。包括 MySQL 和 Kafka 在内的更多协议会逐渐添加进来。
动态配置
如上文所述,分布式系统的大行其道,需要有动态配置能力来提高系统的动态和控制能力。Istio就是一个这种系统的例子。请参考 Service Mesh Data Plan vs Control Plan 来获取更多这方面的内容。
高级负载均衡
七层负载均衡器一般都有内置的高级负载均衡支持,例如超时、重试、频率控制、断路器、Shadow、缓冲、基于内容的路由等。
可观测性
上文中也提到过,负载均衡的一个常见功能,越来越多的动态系统被部署,调试难度也水涨船高。健壮的协议规范观测输出可能是未来七层负载均衡器要提供的最重要功能之一。统计数字、分布式跟踪以及可以定义日志的输出,目前已经成为对器层负载均衡解决方案的必须要求。
扩展性
现代七层负载均衡器需要能够简单的加入定制功能。这一功能可以通过编写可插接过滤器,并载入到负载均衡器的方式来实现。很多负载均衡器还支持脚本扩展,例如 Lua。
容错
在讲四层负载均衡的容错时颇费了一番口舌。七层负载均衡的容错又怎样呢?一般来说我们认为七层负载均衡器(的工作负载)是无状态的可抛弃的。使用普通软件就能够简单的对七层负载均衡器进行水平扩展。另外七层负载均衡的流量处理和状态跟踪的复杂度要远超四层。设置七层负载均衡器的 HA 配对在技术上是可行的,但会非常繁杂。
总的说来,不管是四层还是七层的负载均衡,业界都在尝试走出 HA 配对模式,转向一致性哈希为基础的水平扩展方式。
更多
七层负载均衡器正在高速发展。可以参考 Envoy 架构概览。
5
全局负载均衡和中心控制平面
未来会有越来越多的独立负载均衡器以商品设备的面目呈现。我认为真正的创新和商业机会来自于控制平面。图 13展示了一个全局负载均衡系统。在本例中有一些不同的东西:
每个 Sidecar 代理和三个不同区域的后端进行通信。
如图,90% 的流量会被发送到 C 区,A B 两区分别得到 5%。
Sidecar 代理和后端都会周期性的向全局负载均衡器进行汇报。这样全局负载均衡器就可以根据延迟、成本、负载、失败率等数据进行精密决策。
全局负载均衡周期性的使用当前的路由信息配置每个 Sidecar 代理。
全局负载均衡能做一些单一负载均衡器做不到的事情,例如:
对分区故障的自动检测和路由绕行。
应用全局的安全和路由策略。
使用机器学习和神经网络,对异常流量进行检测和防范,包括分布式拒绝服务攻击。
提供*界面和可视化支持,让工程师能够以聚合的角度,对整个分布式系统进行监控和运维。
全局负载均衡器要成为现实,他的数据平面必须具有强大的动态配置能力。请参考我的博客:Envoy's universal data plane API,以及 Service Mesh Data Plan vs Control Plan 。
6
结论,以及负载均衡器的未来
综上所述,本文的主旨:
负载均衡器是现代分布式系统的关键组件。
有两种负载均衡器:四层和七层。
四层和七层负载均衡器都与现代架构相关。
四层负载均衡器正在朝着基于一致性哈希的水平扩展方案的方向进行迁移。
由于动态微服务架构的成长,七层负载均衡器得以快速发展。
控制平面和数据平面的拆分,和全局负载均衡,是负载均衡的未来发展方向和机会来源。
业界正激进的迈向标准硬件和软件的开源解决方案。我相信传统的像 F5 这样的负载均衡厂商会被开源软件和云供应商取代。传统的路由器、交换机厂商,例如 Arista/Cumulus 等,短期内会有更好的发展,但最终也会被共有云提供商及其自研物理网络所取代。
总的说来,我认为这是计算器网络的奇迹年代。多数系统开始向开源和软件方向转变,极大的提高了迭代速度。未来,分布式系统又向无服务器计算进军,底层网络和负载均衡系统的复杂性也势必齐头并进,水涨船高。
上一篇: android 实现3d旋转
下一篇: 开源软件商业模式的探讨
推荐阅读