企业级负载平衡简介[深度好文]

简介:

简介

在一个大型网站中,在线用户有时可能有几千个甚至上万个之多。如果一个用户的请求需要服务使用0.02秒来处理,那么该服务实例每秒钟将只能处理50个这样的请求,每分钟也只能处理3000个。如果该服务是一个用户非常常用的功能,如浏览网站的产品列表,那么很显然单个服务实例已经无法支持该网站的运营。在这种情况下,我们就需要对该服务进行扩容。
扩容主要分为Scale Up和Scale Out两种,分别对应着增强单个服务的服务能力以及增强服务数量。在某些情况下,Scale Up是一个较为简单的操作,例如为该服务所在的服务器添加更大的内存。但是任意一个服务器所能提供的能力实际上都受到其自身物理硬件能力的限制,尤其是具有越高性能的服务器其单位服务能力的价格越为昂贵,因此我们就需要使用Scale Out方式将工作量分摊到多个服务器之中。

422101-20160913095028664-1055617790.png

就如上图所显示的那样,当服务器的负载过多而来不及处理的时候,服务器就处于一种过载的状态。在该状态的服务常常会出现响应速度慢甚至无响应的情况。而在执行了Scale Out之后,我们将会使用多个服务器同时处理用户的请求。在该解决方案中,我们需要使用一台特定的设备将这些请求分发到各个服务器。该设备会根据其内部所包含的请求分发逻辑来决定如何对这些请求进行分发,以避免出现单个服务器过载的情况。这些用来对请求进行分发的设备实际上就是负载平衡服务器。
当然,我们不会等到服务器真正过载了才去解决这个问题。在服务的日常运维中,我们在服务器的平均负载和峰值负载达到某个特定阈值时就需要考虑是否需要为相应服务进行扩容。
一旦一个服务使用了负载平衡系统,那么它将在高可用性以及扩展性上得到极大的增强。这也是我们使用负载平衡解决方案的最重要原因。例如对于一个拥有三台服务器的负载平衡系统,如果其中一台发生了故障,那么负载平衡服务器可以通过向各个服务发送心跳等方式得知它们的异常,进而不再向这个发生了故障的服务器分发任务:
422101-20160913095214023-1667691142.png
而如果当前负载平衡系统中所负担的服务容量已经超过了阈值,那么我们可以简单地通过在负载平衡系统中添加服务器来解决这个问题:
422101-20160913095307055-1796463560.png
这样,每个服务器所需要处理的任务就相对减少了,从而减轻了单个服务器的负担。

各种负载解决方案

1.基于DNS的负载平衡

当我们通过在浏览器的地址栏中键入域名来访问某个网站时,浏览器将首先查找本地的DNS缓存是否拥有该域名所对应的IP地址。如果有,那么浏览器将尝试直接使用该IP地址访问该网站的内容。如果本地DNS缓存中没有该域名所对应的IP地址,那么它将向DNS发送一个请求,以获得该域名所对应的IP并添加到本地DNS缓存中。
而在DNS中,一个域名可能和多个IP地址绑定。不同的DNS请求所返回的结果会按照Round Robin进行轮换,进而使得不同的用户访问不同的IP地址,平衡各个服务器的负载。
虽然这种负载平衡解决方案非常容易实现,但是它有一个致命的缺点:为了减少DNS请求的次数以提高访问效率,浏览器常常缓存了DNS查询的结果。如果一个IP处的服务失效,那么浏览器可能仍会根据DNS缓存中所记录的信息向该不可用的服务发送请求(不同的浏览器可能有不同的行为)。虽然说整个服务只有一处IP所对应的服务失效了,但是从用户的角度看来该网站已经不可访问。因此基于DNS的负载平衡方案并不能作为一个独立的负载平衡解决方案来提供高可用性的保障,而是作为其它负载平衡解决方案的补充方案来使用。

2.L3/4负载平衡,也即是基于网络层的负载平衡

这里的L3/4实际上指的就是负载平衡服务器会根据OSI模型中的第三层网络层(Network Layer)和第四层传输层(Transport Layer)所包含的数据来进行负载平衡操作。
L3/4负载平衡服务器的工作原理非常简单:在数据到达时,负载平衡服务器将根据自身算法以及OSI模型三四层所包含的数据决定需要处理该数据的服务实例并将其转发。
整个负载平衡的运行包含三方面内容:负载平衡服务器需要知道当前有效的服务实例到底有哪些,并根据自身的分派算法来决定需要处理数据的服务实例,根据分派算法的计算结果将数据发送到目标服务实例上。
首先来看看负载平衡服务器是如何确定服务实例的有效性的。为了能够保证从负载平衡服务器所派发的数据包能被它后面的服务器集群正常处理,负载平衡服务器需要周期性地发送状态查询请求以探测到底哪些服务实例正在有效地工作。这种状态查询请求常常会超出很多人的认知:如果服务实例崩溃但是承载它的操作系统正常工作,那么该操作系统仍然会正常响应负载平衡服务器所发出的Ping命令,只是此时TCP连接会失败;如果服务实例并没有崩溃,而只是挂起,那么它仍然可以接受TCP连接,只是无法接收HTTP请求。
由于这种状态查询请求实际上是特定于服务实例的具体实现,因此很多负载平衡服务器都允许用户添加自定义脚本以执行特定于服务实例的查询。这些状态查询请求常常包含了很多测试,甚至会尝试从服务实例中返回数据。
一旦负载平衡服务器发现其所管理的某个服务实例不再有效,那么它就不会再将任何数据转发给该服务实例,直到该服务实例回归正常状态。在这种情况下,其它各个服务实例就需要分担失效服务器所原本承担的工作。
这里需要注意的一点是,在某个服务实例失效以后,整个系统仍应该具有足够的总容量以处理负载。举例来说,假如一个负载平衡服务器管理了三个具有相同能力的服务实例,而且这三个服务实例各自的负载都在80%左右。如果其中一个服务实例失效,那么所有的负载都需要由其它两个服务实例来完成。每个服务实例就需要承担120%的负载,远超过了它所具有的负载能力。这种情况的直接后果就是,服务显得非常不稳定,并常常有系统超时,应用无法正常工作的情况出现。

3.L7负载平衡,即基于应用层的负载平衡

顾名思义,其主要通过OSI模型中的第七层应用层中的数据决定如何分发负载。
在运行时,L7负载平衡服务器上的操作系统会将接收到的各个数据包组织成为用户请求,并根据在该请求中所包含的的数据来决定由哪个服务实例来对该请求进行处理。
422101-20160913100432242-1459878007.png
相较于L3/4负载平衡服务所使用的数据,L7负载平衡服务所使用的应用层数据更贴近服务本身,因此其具有更精确的负载平衡行为。

在前面对L3/4负载平衡的讲解中我们已经介绍过,对于某些具有关联关系的各个请求,L3/4负载平衡服务器会根据某些算法(如计算IP的哈希值)来决定处理该请求的服务实例。但是这种方法并不是很稳定。当一个服务实例失效或用户的IP发生变化的时候,用户与服务实例之间的对应关系就将发生改变。此时用户原有的会话数据在新的服务实例上并不存在,进而导致一系列问题。

其实产生这个问题的最根本原因就是用户与服务实例之间的关联关系是通过某些外部环境创建的,而并非由用户/服务实例本身来管理。因此它不能抵御外部环境变化的冲击。如果要在用户和服务实例之间建立稳定的关联关系,那么就需要一种稳定的在用户和服务实例之间传递的数据。在Web服务中,这种数据就是Cookie。

简单地说,基于Cookie的负载平衡服务实际上就是分析用户请求中的某个特定Cookie并根据其值决定需要分发到的目标地址。其主要分为两种方式:Cookie Learning以及Cookie Insertion。
Cookie Learning是不具有侵入性的一种解决方案。其通过分析用户与服务实例通讯过程中所传递的Cookie来决定如何分派负载:在用户与服务第一次通讯时,负载平衡服务将找不到相应的Cookie,因此其将会把该请求根据负载平衡算法分配到某个服务实例上。而在服务实例返回的时候,负载平衡服务器将会把对应的Cookie以及服务实例的地址记录在负载平衡服务器中。当用户再次与服务通讯时,负载平衡服务器就会根据Cookie中所记录的数据找到前一次服务该用户的服务实例,并将请求转发到该服务实例上。
422101-20160913100629242-1747728796.png
这么做的最大缺点就是对高可用性的支持很差。如果一旦负载平衡服务器失效,那么在该负载平衡服务器上所维护的Cookie和服务实例之间的匹配关系将全部丢失。这样当备份负载平衡服务器启动之后,所有的用户请求都将被定向到随机的服务实例。
而另一个问题就是会话维护功能对内存的消耗。与L3/4服务器上的会话维护不同,一个Cookie的失效时间可能非常长,至少在一次用户使用中可能会持续几个小时。对于一个访问量达到每秒上万次的系统而言,负载平衡服务器需要维护非常多的会话,甚至可能会将服务器的内存消耗殆尽。反过来,如果将负载平衡服务器中的Cookie过期时间设置得太短,那么当用户重新访问负载平衡服务器的时候,其将被导向到一个错误的服务实例。
除了Cookie Learning 之外,另一种常用的方法就是Cookie Insertion。其通过向响应中添加Cookie以记录被分派到的服务实例,并在下一次处理请求时根据该Cookie所保存的值来决定分发到的服务实例。在用户与服务器进行第一次通讯的时候,负载平衡服务器将找不到分派记录所对应的Cookie,因此其会根据负载平衡算法为该请求分配一个服务实例。在接收到服务实例所返回的数据之后,负载平衡服务器将会向响应中插入一个Cookie,以记录该服务实例的ID。当用户再次发送请求到负载平衡服务器时,其将根据该Cookie里所记录的服务实例的ID派发该请求。

相较于Cookie Learning,Cookie Insertion不需要在内存中维护Cookie与各个服务实例的对应关系,而且在当前负载平衡服务器失效的时候,备用负载平衡服务器也可以根据Cookie中所记录的信息正确地派发各个请求。
当然,Cookie Insertion也有缺陷。最常见的问题就是浏览器以及用户自身对Cookie的限制。在Cookie Insertion中,我们需要插入一个额外的Cookie 来记录分配给当前用户的服务实例。但是在某些浏览器中,特别是移动浏览器中,常常会限制Cookie的个数,甚至只允许出现一个 Cookie。为了解决这个问题,负载平衡服务器也会使用一些其它方法。如Cookie Modification,即修改一个已有的Cookie使其包含服务实例的ID。
而在用户禁用了Cookie的时候,Cookie Insertion将是完全失效的。此时负载平衡服务所能利用的将仅仅是JSESSIONID等信息。因此在一个L7负载平衡服务器中,Cookie Learning和Cookie Insertion常常同时使用:Cookie Learning会在用户启用Cookie的时候起主要作用,而在Cookie被用户禁用的情况下则使用Cookie Learning来根据JSESSIONID来保持用户与服务实例之间的关联关系。
或许您会想:L3/4负载平衡服务器在处理各个关联请求的时候是通过IP的哈希值来决定处理该请求的服务实例的。既然这些基于Cookie的解决方案能达到100%的准确,为什么我们不在L3/4负载平衡服务器中使用它们呢?答案是:由于L3/4负载平衡服务器主要关注于数据包级别的转发,而Cookie信息则藏匿于数据包之中,因此L3/4负载平衡服务器很难决定单一的数据包该如何转发。

试想一下接收所有数据包所可能发生的事情吧。在网络的一端发送多个数据包的时候,网络的另一端所接收到的数据包的顺序可能与原有的发送顺序并不一致。甚至在网络拥堵的时候,某些数据包可能会丢失,进而再次加长接收到所有数据包所需要的时间。
因此相较于将数据包直接转发的方法,等待所有的数据包到齐然后再插入Cookie的性能非常差。在后面对于解决方案的讲解中您会看到,L3/4负载平衡服务器对于性能的要求一般来说是很高的,而L7负载平衡服务器则可以通过一个集群来解决自身的性能问题。基于DNS的负载平衡,L3/4负载平衡服务器以及L7负载平衡服务器常常协同工作,以组成一个具有高可用性以及高可扩展性的系统。

其他

负载平衡系统设计时留意它的高可用性及扩展性。在一开始的讲解中,我们就已经提到过通过使用负载平衡,由众多服务器实例所组成的服务具有很高的可用性及扩展性。当其中一个服务实例失效的时候,其它服务实例可以帮助它分担一部分工作。而在总服务容量显得有些紧张的时候,我们可以向服务中添加新的服务实例以扩展服务的总容量。
但是由于所有的数据传输都需要经过负载平衡服务器,因此负载平衡服务器一旦失效,那么整个系统就将无法使用。也就是说,负载平衡服务器的可用性影响着整个系统的高可用性。
解决这个问题的方法要根据负载平衡服务器的类型来讨论。对于L3/4负载平衡服务器而言,为了能够让整个系统不失效,业界中的常用方法是在系统中使用一对负载平衡服务器。当其中一个负载平衡服务器失效的时候,另一个还能够为整个系统提供负载平衡服务。这一对负载平衡服务器可以按照Active-Passive模式使用,也可以按照Active-Active模式使用。
在Active-Passive模式中,一个负载平衡服务器处于半休眠状态。其将会通过向另外一个负载平衡服务器发送心跳消息来探测对方的可用性。当正在工作的负载平衡服务器不再响应心跳的时候,那么心跳应用将会把负载平衡服务器从半休眠状态唤醒,接管负载平衡服务器的IP并开始执行负载平衡功能。
422101-20160913101239258-1367633763.png
而在Active-Active模式中,两台负载平衡服务器会同时工作。如果其中一台服务器发生了故障,那么另一台服务器将会承担所有的工作:
422101-20160913101251570-2045041438.png

可以说,两者各有千秋。相较而言,Active-Active模式具有较好的抵抗访问量大幅波动的情况。例如在通常情况下,两个服务器的负载都在30%左右,但是在服务使用的高峰时间,访问量可能是平时的两倍,因此两个服务器的负载就将达到60%左右,仍处于系统可以处理的范围内。如果我们使用的是Active-Passive模式,那么平时的负载就将达到60%,而在高峰时间的负载将达到负载平衡服务器容量的120%,进而使得服务无法处理所有的用户请求。
反过来,Active-Active模式也有不好的地方,那就是容易导致管理上的疏忽。例如在一个使用了Active-Active模式的系统中,两个负载平衡服务器的负载常年都在60%左右。那么一旦其中的一个负载平衡服务器失效了,那么剩下的唯一一个服务器同样将无法处理所有的用户请求。
或许您会问:L3/4负载平衡服务器一定要有两个么?其实主要由各负载平衡服务器产品自身来决定的。在前面我们已经讲过,实际上探测负载平衡服务器的可用性实际上需要很复杂的测试逻辑。因此如果一旦我们在一个负载平衡系统中使用了过多的L3/4负载平衡服务器,那么这些负载平衡服务器之间所发送的各种心跳测试将消耗非常多的资源。同时由于很多L3/4负载平衡服务器本身是基于硬件的,因此它能够非常快速地工作,甚至可以达到与其所支持的网络带宽所匹配的处理能力。因此在一般情况下,L3/4负载平衡服务器是成对使用的。
如果L3/4负载平衡服务器真的接近其负载极限,那么我们还可以通过DNS负载平衡来分散请求:
422101-20160913101550898-135979478.png

这种方法不仅仅可以解决扩展性的问题,更可以利用DNS的一个特性来提高用户体验:DNS可以根据用户所在的区域选择距离用户最近的服务器。这在一个全球性的服务中尤为有效。毕竟一个中国用户访问在中国架设的服务实例要比访问在美国架设的服务实例快得多。
反过来由于L7负载平衡服务器主要是基于软件的,因此很多L7负载平衡服务器允许用户创建较为复杂的负载平衡服务器系统。例如定义一个具有两个启用而有一个备用的一组L7负载平衡服务器。
讲解完了高可用性,我们就来介绍一下负载平衡服务器的扩展性。其实在上面我们刚刚介绍过,L3/4负载平衡服务器拥有很高的性能,因此一般的服务所使用的负载平衡系统不会遇到需要扩展性的问题。但是一旦出现了需要扩展的情况,那么使用DNS负载平衡就可以达到较好的扩展性。而L7负载平衡则更为灵活,因此扩展性更不是问题。
但是一个负载平衡系统不可能都是由L3/4负载平衡服务器组成的,也不可能只由L7负载平衡服务器组成的。这是因为两者在性能和价格上都具有非常大的差异。一个L3/4负载平衡服务器实际上价格非常昂贵,常常达到上万美元。而L7负载平衡服务器则可以使用廉价服务器搭建。L3/4负载平衡服务器常常具有非常高的性能,而L7负载平衡服务器则常常通过组成一个集群来达到较高的整体性能。
在设计负载平衡系统时,还有一点需要考虑的那就是服务的动静分离。我们知道,一个服务常常由动态请求和静态请求共同组成。这两种请求具有非常不同的特点:一个动态请求常常需要大量的计算而传输的数据常常不是很多,而一个静态的请求常常需要传输大量的数据而不需要太多的计算。不同的服务容器对这些请求的表现差异很大。因此很多服务常常将其所包含的服务实例分为两部分,分别用来处理静态请求和动态请求,并使用适合的服务容器提供服务。在这种情况下,静态请求常常被置于特定的路径下,如“/static”。这样负载平衡服务器就可以根据请求所发送到的路径而将动态请求和静态请求进行适当地转发。
最后要提到的就是L3/4负载平衡服务器的一个软件实现LVS(Linux Virtual Server)。相较于硬件实现,软件实现需要做很多额外的工作,例如对数据包的解码,为处理数据包分配内存等等呢个。因此其性能常常只是具有相同硬件能力的L3/4负载平衡服务器的1/5到1/10。鉴于其只具有有限的性能但是搭建起来成本很低,如利用已有的在Lab里闲置的机器等,因此其常常在服务规模不是很大的时候作为临时替代方案使用。

负载平衡解决方案

在文章的最后,我们将给出一系列常见的负载平衡解决方案,以供大家参考。

在一般情况下,一个服务的负载常常是通过某些方式逐渐增长的。相应地,这些服务所拥有的负载平衡系统常常是从小到大逐渐演化的。因此我们也将会按照从小到大的方式逐次介绍这些负载平衡系统。

首先是最简单的包含一对L7负载平衡服务器的系统:

422101-20160913101655914-1737379644.png

如果服务的负载逐渐增大,那么该系统中唯一的L7负载平衡服务器很容易变成瓶颈。此时我们可以通过添加一个SSL Farm以及运行LVS的服务器来解决问题:
422101-20160913101705539-1769144529.png

如果我们还要应对增长的负载,那么就需要使用真正的基于硬件的L3/4负载平衡服务器来替代LVS,并增加各层的容量:

422101-20160913101711430-1804279403.png

由于该解决方案的下面三层基本都有理论上无限的扩展性,因此最容易出现过载的就是最上面的L3/4负载平衡服务器。在这种情况下,我们就需要使用DNS来分配负载了:

422101-20160913101717664-1238321251.png


本文转自TBHacker博客园博客,原文链接:http://www.cnblogs.com/jiqing9006/p/5867476.html,如需转载请自行联系原作者

相关实践学习
基于函数计算快速搭建Hexo博客系统
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
21天前
|
设计模式 负载均衡 网络协议
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
【分布式技术专题】「分布式技术架构」实践见真知,手把手教你如何实现一个属于自己的RPC框架(架构技术引导篇)
42 0
|
7月前
网络基础之三
网络基础之三
57 0
|
存储 数据采集 Prometheus
【云原生监控系列第一篇】一文详解Prometheus普罗米修斯监控系统(山前前后各有风景,有风无风都很自由)(一)
【云原生监控系列第一篇】一文详解Prometheus普罗米修斯监控系统(山前前后各有风景,有风无风都很自由)(一)
1223 0
【云原生监控系列第一篇】一文详解Prometheus普罗米修斯监控系统(山前前后各有风景,有风无风都很自由)(一)
|
算法 网络协议 数据挖掘
【网络篇】第一篇——网络入门基础(三)
【网络篇】第一篇——网络入门基础
【网络篇】第一篇——网络入门基础(三)
|
存储 网络协议 Linux
【网络篇】第一篇——网络入门基础(一)
【网络篇】第一篇——网络入门基础
【网络篇】第一篇——网络入门基础(一)
|
编解码 网络协议 安全
【网络篇】第一篇——网络入门基础(二)
【网络篇】第一篇——网络入门基础
【网络篇】第一篇——网络入门基础(二)
|
运维 监控 Kubernetes
深入浅出边缘云 | 1. 概述(上)
深入浅出边缘云 | 1. 概述(上)
143 0
|
存储 运维 Kubernetes
深入浅出边缘云 | 1. 概述(下)
深入浅出边缘云 | 1. 概述(下)
184 0
深入浅出边缘云 | 1. 概述(下)
|
Kubernetes 负载均衡 Cloud Native
【云原生Kubernetes系列项目实战第一篇】k8s集群+高可用负载均衡层+防火墙( 提及年少一词,应与平庸相斥)(一)
【云原生Kubernetes系列项目实战第一篇】k8s集群+高可用负载均衡层+防火墙( 提及年少一词,应与平庸相斥)(一)
277 0
【云原生Kubernetes系列项目实战第一篇】k8s集群+高可用负载均衡层+防火墙( 提及年少一词,应与平庸相斥)(一)
|
负载均衡 Kubernetes Cloud Native
【云原生Kubernetes系列项目实战第一篇】k8s集群+高可用负载均衡层+防火墙( 提及年少一词,应与平庸相斥)(三)
【云原生Kubernetes系列项目实战第一篇】k8s集群+高可用负载均衡层+防火墙( 提及年少一词,应与平庸相斥)(三)
183 0
【云原生Kubernetes系列项目实战第一篇】k8s集群+高可用负载均衡层+防火墙( 提及年少一词,应与平庸相斥)(三)