本文翻译自http://1wt.eu/articles/2006_lb/
鉴于本人英语水平翻译的或许令人费解,请有兴趣的读者参照原文阅读.
原文似乎写于2006年,最后一部分提到的有关keep-alive的内容或许稍显过时.现今大量越来越多的应用开始使用长连接服务,非阻塞IO和异步处理使维护连接的消耗变的很小了已经.所以还是要根据具体应用,来使用文章最后提供的调试技巧.
起初,WEB应用的内容大部分是静态的,可以快速地传递给用户。用户主要是在阅读,点击超链接地频率低。现在,用户会停留在WEB应用几十分钟或数小时,频繁地点击超链接,服务器要作非常多的处理工作。用户或频繁地访问那些他们清楚的知道不需要花费太多时间阅读的站点。他们每次点击超链接都会给服务器造成巨大的负载,但却希望立即得到响应。对高性能和持久可用的需求,已经有了解决方案。
什么是负载均衡?
因为任何服务器的能力是有限的,WEB应用必需可以运行在多台服务器以服务不断增加的用户。这被称为伸缩。对内部网络应用来说,伸缩性几乎不是问题,因为用户数增长的机会很少。然而,对于互联网,用户数随着可用的带宽持续地增长。服务器管理员不得不寻找肩负载散布在多台服务器上的方式,通过应用服务器地内部机制,外部组件,或者架构重新设计。负载均衡是使多台服务器承担同一个服务做相同工作的功能。随着服务器增多,任何位置失效的风险也增加,这种风险必需被定位。维护服务持续可用的功能称为高可用性。通常依赖负载均衡,这使人们对两种概念混淆不清。其实一些负载均衡技术并不提供高可用性,并且非常危险。
负载均衡技术
1.DNS
最简单的实现负载均衡的方式使分配服务器给预定义的用户组。这对内部网络服务器很容易,但对互联网服务器却不是。通常的方式是依靠DNS轮询。如果DNS服务器对一个主机明有多个对应的条目,它们将全部按循环顺序被返回。这种方式,不同的用户对相同的域名键看到不同的地址,然后访问到不同的服务器。通常用语多址的负载均衡,但是要求应用本身不受服务器缺陷影响。因此,一般用于搜索引擎,POP服务,或者传递静态内容。DNS轮询需要附加的措施检查服务器状态,并将检查失败的地址切换至其他服务器。所以通常作为补充的解决方案,而非主要措施。
host -t a google.com
2.降低每台服务器的用户数
更常用的方法是将用户群分配到多台服务器上。这就在用户跟服务器间引入了负载均衡器。可以是一台专用的硬件设备,也可以是安装在一台专用的前端服务器或者应用服务器本身的软件。主意随着新组件的开发,鼓掌风险也增大。所以最好设置另外一个负载均衡器,作为备份。
一般,硬件负载均衡器在网络数据层像路由器一样工作。使用如下的手段:
直接路由:负载均衡器将同一个服务地址路由到不同的本地物理服务器,这些服务器必须在相同的网段并共享相同的服务地址。不改变IP层的任何东西有个巨大的优势,服务器可以不再通过负载均衡器,直接响应用户。这被称为"直接服务器返回"。由于这种手段对处理能力的需求最低,所以经常被用于流量非常高的网站的前端服务器。但是需要扎实的TCP/IP网络模型知识才能正确地设置包括服务器在内地所有设备。
隧道:工作方式很像直接路由,但需要在负载均衡器和服务器间建立隧道,并且这些服务器可以在远程网络被定位。服务器直接返回依然是可行的。
IP地址转换(NAT):负载均衡器将一个虚拟的地址转换为真实的服务器地址,而用户只需要连接到这个虚拟地址即可。乍看起来这种方式比较容易部署,因为几乎不需要设置服务器。但是对编程的要求更严格,一格常见错误是服务器在一些响应中使用了内部地址。负载均衡器需要做更多的工作,例如反复的转换地址,维护会话表,所有的返回数据流也要通过负载均衡器。有时太短的会话超时设置会引起称作ACK风暴的副作用。此时,增加超时设置是唯一的解决方案,但优惠带来会话表饱和的风险。
与硬件负载相对的,我们还可以选择软件负载均衡器。软件负载均衡器通常像反向代理一样工作,伪装成服务器,并转发数据流。这暗示着服务器不能被用户直接访问,并且某些协议也不能被负载均衡。软件负载均衡器比工作在网络层的硬件设备需要更多的处理能力,但是由于它们负责用户和服务器的通信,并且只转发自己了解的数据流,这为服务器提供了第一层安全保障。因此软件负载均衡器产品,通常都具有URL过滤功能。
2.1.检测服务器
负载均衡器必需知道那些服务器是可用的,才能正确挑选服务器。因此必需周期性的对服务器进行PING测试,连接尝试,发送请求,或任何管理员认为可以衡量服务器有效性的检测措施。这些测试被称为"健康检查"。一台崩溃的服务器可能会对响应PING但无法建立TCP连接,挂起的服务器获许可以建立TCP连接但无法响应HTTP请求。在多层结构的web应用服务中,HTTP请求可以被及时响应,但其它的检测可能失败。选择被应用和负载均衡器允许的最具代表性的检测方式是需要斟酌的。
一些测试可能从数据库获取数据以确定整个处理链有效。缺点是这需要消耗相当多的服务器资源(CPU,线程,等等)。检测周期必需足够长以避免对服务器造成过多的负载,又必需足够短以尽快发现失效的服务器。对于负载均衡健康检查是相当复杂的。应用开发人员最终会实现一个特殊的请求,完成众多内部检查,专用于负载均衡器。软件负载均衡器通常会提供脚本功能,代码修改可以在短时间内完成,锁哟是目前为止最灵活的方案。
2.2.挑选最好的服务器
负载均衡器可以通过许多方式分发负载,一个误解是发送请求给"第一个响应的服务器"。这种实践是错误的,因为任何原因一台服务器只要响应稍微快一点儿,它将获得最多的请求,导致不均衡。另一种想法是发送请求给"最低负载的服务器",这在包含很多非常长的会话的环境中很有用,但是不适合负载频繁发生变化的web服务器。
对于均匀的服务器农场(所有服务器性能相同),"轮询"通常是最好的选择,轮流使用每台服务器。如果服务器的性能不同,那么"加权轮询"算法更合适,按照每台服务器设置的容量分配负载。
这些算法后一格缺点,分配目标是不确定的。这意味着两个来自同一用户的连贯请求可能被分发到不同的服务器。应用服务器无法保存这两个连贯请求的上下文环境。并且复杂的会话(例如SSL密钥协商)会一遍又一遍的重复在每个连接建立。为了解决这个问题,通常会以如一格非常简单的算法:地址哈希。概括的说,就是将用户的IP地址用服务器数量除,其结果为用户决定了要选择的服务器。只要服务器数量不变且用户的IP地址稳定,这种算法就可以很好的工作,但情况不总如此。党人和服务器失效,所有的用户将被重新分配,他们的会话也跟着丢失。并且通过使用共享代理访问网络的用户(出口IP地址相同)也不能使用这种应用。这种方法不总可用,因为需要一组分布好,且数量庞大的源IP地址。这在Internet上是满足的,但对于小公司甚至是ISP这样的基础设施都并非如此。然而,这对于避免过于频繁的重算SSL会话密钥非常有效。
解决上述问题的方案是持久化。持久化会保持用户请求事中转发到持有其会话上下文的同一台服务器。一般廉价的解决方案是应用使用HTTP302响应将用户重定向到本地服务器地址。主要的缺陷是当服务器实效,用户无法脱身,依然会尝试向其发送请求。就像DNS轮询一样,这种手段只适用于大网站,可以担保服务器对用户总是可及的。
另一个解决方案是使负载均衡器保存用户服务器关系,最廉价的方法是保存用户IP和它上次访问的服务器映射。这一般只能解决服务器集群由于实效引起可用数变化的问题,但对变化IP的用户无能为力。
持久化
还有什么别的方法吗?起初,我们一致试图确保用户可以将接下来的请求发送到相同的服务器维持会话上下文。那么,服务器又是如何识别上下文的?依靠Cookie。Cookies正是为了这个目的而发明:向用户发送一个信息,当用户回访的时候将此信息传回以便于我们知道如何处理用户请求。当然这需要用户支持Cookie,需要持久化的应用亦是如此。
如果负载均衡器可以基于用户提供的Cookie识别服务器,那将解决大部分问题。主要有两种途径获取Cookie,负载均衡器可以读取服务器为用户设置的Cookie,或者自己为用户插入用于识别服务器的Cookie。
1.Cookie解析
Cookie解析是入侵最低的解决方案。设置负载均衡器以及恶习应用设置的Cookie(例如JSESSIONID)。当负载均衡器受到用户请求,就检查是否含有这个Cookie和已知的值。如果不是这样就按照设置的负载均衡算法将请求定向到任何一台服务器。接下来将会从服务器的响应中提取出Cookie随同服务器标识符一起保存到本地的表中。当用户再回来的时候,负载均衡器看到那个Cookie,用他的值在这个表里找到对应的服务器,将请求转发给它。这个方法部署简单,但是在解析方面有两个小缺陷。
1)负载均衡器的内存是有限的,为了避免饱和,必需限制Cookie在表中的存活时间。这意味着如果用户在Cookie过期后回访,将被定向到错误的服务器。
2)如果负载均衡器崩溃,由备份机取代,但是备份机不知道任何的关系,将会再次把用户定向到错误的服务器。当然也不可能使用两个活动的负载均衡器组合工作,可以通过实时SESSION同步解决这个问题,但这很难担保。
3)应对这些缺陷的一格变通方案是在可以的情况下选择确定的负载均衡算法,例如用户地址哈希。此时,就算负载均衡器的Cookie丢失了,至少有固定IP地址的用户会保持定向到相同的服务器。
2.Cookie插入
如果用户支持Cookie,那么为什么不使用固定的文本值添加另一个呢?这被称为"Cookie插入",将服务器标识符插入到服务器给用户的响应中。这种方式负载均衡器不需要保存任何东西,只须重用用户提供的值选择正确的服务器即可。这解决了Cookie解析的两个问题,内存限制和负载均衡器接管。然而这需要负载均衡器做更多努力,打开数据流并插入数据。这并不容易,特别是基于ASIC的负载均衡器,对于TCP和HTTP协议只知甚少。还需要用户代理支持多重Cookie,但并不总是如此,例如小型移动终端有时会限制只允许单一的Cookie。Cookie插入的变种应运而生,例如Cookie修改,将服务器标识符当作前缀与已有Cookie组合起来。需要特别主意负载均衡器对响应缓存的识别:例如,前端缓存会保存负载均衡器的Cookie并将其传递给访问主页的用户,这会导致所有的用户被定向到相同的服务器。
3.持久化的限制-SSL
我们重新审视这些方法,发现都需要窥探拥护和服务器的交互,有时深知需要修改。但是越来越多的应用开始使用SSL来保障安全,负载均衡器将会无法访问HTTP内容。因此我们看到越来越多的负载均衡器提供SSL支持。其本质非常简单,负载均衡器作为反向代理,为创成终端服务器提供SSL支持。它持有服务器凭证,解密收到的请求,访问其内容,然后将请求定向到服务器(以纯文本形式或稍微重新加密)。服务器不再需要处理SSL,从而获得性能提升。然而负载均衡器称为了瓶颈:单台基于软件的负载均衡器,不可能比8台服务器组成的集群更快的处理SSL。由于这种架构错误,负载均衡器将比应用服务器更早饱和。唯一的补救措施是,在前端再增加一层负载均衡器,然后添加更多的负载均衡器来处理SSL负载。显然这是错误的。维护负载均衡器集群是相当困难的,而且健康检查会对服务器造成相当可观的负载。正确的做法是使用专用的SSL处理集群。
4.专用的SSL缓存集群
当应用需要使用SSL时,最具伸缩性的方案时使用反向代理集群专门处理SSL。这时个只有好处没有坏处的方案。
1)SSL反向代理廉价却很强劲。任何人都可以花费不到1000美元构建一个基于Apache的强大的反向代理集群。 具有同样性能的开启SSL的负载均衡器成本超过15000美元。
2)SSL反向代理通常提供缓存甚至数据压缩功能。大部分的电子商务应用的可缓存性为80%-90%,这意味着vexie反向代理缓存可以降低服务器至少80%的请求负载。
3)SSL代理可以被多个应用共享,所有的应用都会降低对大型服务器及其数量的需求,这意味着维护和许可证的成本降低。
4)SSL集群可以随着数据流量的增长扩大规模,这无序负载均衡器的升级或更换。负载均衡器的性能通常超过大部分架构合理的网站的需求。
5)第一级代理提供了非常好的安全保障,通过过滤无效请求和对未知攻击的URL过滤。
6)当有特别需要(例如强壮的鉴权功能)的时候,替换SSL集群中的产品非常简单。相反,由于健康检查方法,负载均衡算法和持久化方法的变化,应用程序的行为也会收到影响,替换开启SSL的负载均衡器是令人生畏的。
7)负载均衡器的选择将仅仅基于其平衡负载的能力,而非其处理SSL的性能。这比多合一的方案更易适配也更加廉价。
一个SSL代理集群通常由一组相同的服务器组成,这些服务器通常运行着类似Apache+ModSSL的软件(甚至是一些为商业解决方案)。这个集群被外部网络的负载均衡器或内部的软件负载均衡器(例如基于Linux的LVS)平衡负载。这些服务器几乎不需要维护,因为它们的任务仅仅是转换HTTPS数据流为HTTP,检查缓存,转发未缓存的请求给应用服务器集群。主意同一个负载均衡器可以被反向代理和应用服务器共享。
选择硬件还是软件负载均衡器?
虽然所有的方案看起来都趋向于可以做所有的事,其实并不如此。选择负载均衡器基本要考虑三个方面:性能,可靠性,功能。
性能是至关重要的,如果负载均衡器称为瓶颈,那么其它都是扯淡。可靠性和非常重要,因为所有的数据流都经过负载均衡器,所以其可靠性比服务器更优先。功能是选择方案的决定因素,但不是至关重要的。取决于权衡什么样的服务器变更是可接受的。
1.会话数量
比较基于硬件和基于代理的负载均衡器时,最常见的问题之一是为什么会有如此多的会话重叠?代理一般只能承受数千的同步会话,而硬件可以接受数百万。显然没有人会使用20000台Apache服务器处理4百万的同步会话!事实上这取决于负载均衡器是否需要管理TCP连接。TCP协议要求再一个会话结束后,连接要保持TIME_WAIT状态足够长的时间以处理接下来的重连接,有可能是会话关闭后的几分钟。再这个延迟后会话被自动移除。实际上依赖与具体的实现,延迟通常在15-60秒之间。在支持高并发率的系统中这导致一个实际问题,所有中止的会话都必需保持在一张表中。对于60秒的延迟,25000个会话每秒要消耗15000000个条目。幸运的是这张表里的会话不需要携带任何数据并且非常廉价。因为这些都被操作系统透明的处理,代理只需要宣称支持多少活动会话。但是当负载均衡器需要管理TCP连接,它必需支持一个非常大的会话表来存储会话,并具有全局限制。
2.3/4层负载均衡
很明显,在网络层处理数据包附加的开销是最低的,可以提供最好的性能。因此基于网络层的负载均衡器工作在3层时可以达到非常大的带宽,例如使用哈希算法直接路由。3层通常是不够的,网络负载均衡器需要考虑4层。最大的开销在于会话的建立和销毁,因为这种操作的消耗非常低廉,可以非常高的频率进行。更复杂的操作是地址转换,需要消耗更多的出力资源重新计算校验和还有查表。硬件加速带来的性能飞跃随处可见,负载均衡也不例外。专用的ASIC(特定用途集成电路)可以有线的速度路由数据包,高频率的建立和销毁会话,而且不会给通用操作系统强加负载。
相反的,4曾处理的如软件代理性能收到和大限制,因为数据传输层的简单任务,操作系统需要解码数据包,拆包以获取数据,分配缓存来排队数据,与远程服务器建立连接,管理系统资源,等等。因此基于网络的4层负载均衡器,比同样功能的硬件设备慢5到10倍。
内存也是受限资源:基于网络的负载均衡器需要内存爱存贮游离的数据包和会话(基本上就是地址,协议,端口和一些参数,每个会话需要数百字节内存以存储这些内容)。基于代理的负载均衡器需要缓存跟系统通信,系统需要数千字节未每个会话建立缓存,这意味着系统只能维持几万个活动会话。
基于网络的负载均衡器也会提供一些有用的功能,例如虚拟MAC地址,映射所有的主机或网络。这是基于代理的依赖通用操作系统的负载均衡器所不具备的。
基于网络的负载均衡器也会做一些肮脏的事,例如转发收到的无效数据包,或者混淆会话(特别是工作在NAT模式时)。之前说过在做NAT转换时太短的会话超时设置有时会引发ACK风暴,由于过早重用刚刚终止的会话。这会在用户和服务器之间造成网络阻塞。这些对于基于代理的负载均衡器时不会发生的,因为它们依赖操作系统提供的标准兼容的TCP协议栈,并且会完全关闭用户和服务器之间的会话。
总的来说,一个调试好的基于网络的负载均衡器可以在性能和功能上提供最好的3/4层解决方案。
3.7层负载均衡
7层负载均衡包含基于Cookie的持久化,URL切换等等有用的特性。依赖通用TCP/IP协议栈的代理显然可以无阻碍的完成这些工作,但对于急于网络的负载均衡器不得不诉诸于一些技巧,可是这些技巧杜宇大量数据处理也不总是有效,经常引发各种麻烦。其中最难解决的是:
1)多数据包头:负载均衡器要在数据包中查找字符串。当期待的数据不在首先到来的数据包内时,必须记住先到的数据包内的数据,这需要消耗内存。如果字符串分布在两个连续的数据包内时,这很难被发现。然而这时TCP数据流的标准操作。被Apache支持的一个8K的请求通常要跨越6个数据包。
2)逆序的数据包:当大小不同的数据包一同在网络上传输的时候,通常较小的数据包会在比大数据包早到达目标。负载均衡器必需有压的处理这种情况。
3)数据片段:如果一个数据包太大无法被中转,中间路由器允许将其切割成一组小数据包。这在当今的互联网上很少见了,但在内部网络上越来越常见,因我VPN限制有效负载的尺寸。数据片段非常难处理,因为它们会以可辨的顺序到达,需要被缓存以重构数据包,并且只有第一个数据包才含有会话信息。基于代理的负载均衡器易爆物法应付分片的数据,并将其视为攻击拒绝处理。
4)数据包丢失和重发:在互联网上有大量的数据包在传输中丢失。它们会在很短的延迟后重新被重新发送,但是也需要重新处理。所以负载均衡器绝不能认为已经做过的处理不需要再做一次。
5)区分头和数据:在HTTP中,协议信息被称为头,位于交换信息的开头,而数据紧跟着第一个空行之后。数据包丢失会导致基于网络的负载均衡器将Cookie匹配或修改到数据段,这会破坏信息。有一个相关问题会经常发生在绘画结束时:如果用户过快重用源端口,负载均衡器会混淆头和数据,无法进行7层处理。
6)数据插入:当数据被负载均衡器插入(例如Cookie),负载均衡器需要重新未所有传递过来的数据包计算校验和,这回引发可观的性能下降,有时还会产生错误的行为。
也就是说在数据包层次很难做7层处理。有时,内容无法被识别,请求会被发送到错误的服务器,并且又是响应在返回给用户前得不到处理。实际上,全功能的负载均衡器常引入本地代理来执行最复杂的操作。一些基于切换的负载均衡器将部分7层处理交给托管的小型处理器,其它的负载均衡器具有专用的处理器来做7层处理。但是,一般这种方式都达不到结构最接近的服务器硬件的性能。另外,由于需要缓存,所以会受到内存消耗的限制。
通常,这些负载均衡器不会宣称自己可以处理多少7层同步会话,总是说这不是问题,这明显是错误的:对于一个与ApacheWeb服务器兼容的负载均衡器来说,它必须支持消耗8KB内容的单个请求,这意味着对同时对每个以建立的会话保持8KB的缓存数据。支持4百万个这样的同步会话需要32G内存,这通常是负载均衡器不具备的。极端情况下的测试,很容易发现拒绝服务现象。
相反的,基于代理的负载均衡器对于7层处理几乎没有消耗。因为上述所有的问题都自然的被底层的操作系统解决了。负载均衡器只需关注内容并执行正确的动作即可。通常来说,它们还可以不产生额外的消耗记录非常详细的日志,这不仅降低了服务器的浮在,也为容量计划提供了至关重要的数据。
最后,由于不断进化的应用软件需要新的持久化和内容分析方法,7层也在迅速的进化。然而软件作者可以非常容易的为客户提供软件更新和安装,而硬件负载均衡器的更新需要更高级的验证,并且其生产商很难提供如软件升级同样等级的反应速度。
4.对于那些可以负担得起的人来说,最佳的组合
最佳的组合是在第一级使用基于网络的负载均衡器(通常是硬件加速的)在SSL反向代理和7层的基于代理的负载均衡器间做4层负载均衡。首先4层负载均衡器对7层负载均衡器做的检查通常会比代理的自我分析更加可靠。其次,这提供了最佳的负载伸缩能力,因为当7层的代理饱和时可以很容易的增加更多,4层负载均衡器饱和。到这个阶段已经可以承受数G的并发流量了。是时候使用前文提到的DNS轮询技术来使用多个4层负载均衡器,这样可以更好的为多个站点加速。
忽略负载均衡器的应用程序调试。
安装一个负载均衡器可以提供可伸缩性,但是这不能避免调试应用和服务器。
1.分离静态和动态内容。
听起来非常普通,但是极少被做好。在许多应用程序中,大约有25%的的请求是为动态内容,剩下的75%都是对静态内容的请求。每个Apache+PHP进程,依据应用程序的不同需要消耗应用服务器15-50M内存。让这样一个怪物进行一些列复杂的处理并保持长时间活跃只为在网络上向用户传输一个小图标绝对是反常的。更糟糕的是这个进程由于用户下载一个像PDF这样的大文件而被独占几分钟。
最简单的方案是在服务器集群前端使用反向代理缓存,它可以直接将缓存的内容返回给用户无须再查询应用服务器。最干净的方案是使用一个专用的轻量HTTP服务器来提供静态内容服务。它可以与应用安装在同一台服务器,在另一个端口运行服务实例。最好使用像lighttpd或者thttpd这样的单线程服务器,因为它们对于每个会话的消耗相当低。应用可以将静态内容存放在一个容易解析的文件夹中例如“/static”以便前端的负载均衡器可以将流量导向专用的服务器。也可以为静态内容服务器使用完全不同的域名,这样可以将这些服务器安装在不同的位置,通常更靠近用户。
2.在服务器端可以做些什么调试?以APACHE服务器为例。
在服务器端有一些小技巧可以显著的提升用户容量。下面提到的技巧通常可以在APACHE+PHP服务器上提高两到三倍并发用户数,无需任何的硬件升级.
首先,禁用keep-alive。这是最影响性能的事。在服务器运行NCSA-httpd的时代,服务器要为每个请求建立进程。所有这些进程将会杀死服务器,keep-alive是解决这个问题的一个灵巧的方案。现在事情变了。服务器不在为每个连接建立进程并且每个连接的消耗也非常小。应用服务器运行有限的线程或者进程通常是由于内存限制,文件描述符限制或者锁消耗。使用户独占一个线程几秒甚至几分钟却什么都不做纯属浪费。CPU不能得到充分使用,内存也会被大量消耗,还需要用户等待一个链接释放。如果keep-alive时间太短无法在两次用户点击之间维护会话,这就没用了。如果时间足够长,在不考虑大多数浏览器通常会建立4个同步会话的情况下,大概意味着服务器需要为每一位同步用户分配一个进程。简单的说一个使所有类似Apache服务器开启keep-alive的网站只能为数百的用户服务。
另外,观察每个进程平均使用的内存。调整MaxClient参数来控制最大的并发进程数量可以避免服务器做内存交换。如果进程对内存的使用存在较大的差异,说明某些请求产生了大量的数据,保存在内存中着实是一种浪费。为解决这个问题,需要通过MaxRequestsPerChild参数告知Apache使进程尽快死亡。这个参数的值越大,内存的使用量也越大。值越低,CPU的消耗就越高。通常设置为30-300之间的值结果是最好的。然后设置MinSpareServers和MaxSpareServers为接近MaxClinet的值可以使服务器在负载到来时无须花费大量的时间建立新进程。
使用这些技巧,一台近期的具有2G内存的服务器可以毫无问题的服务几百个客户端。剩下的就是负载均衡器的工作了。