首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

负载平衡

Load Balancing

采用纵向扩展时,无需担心如何处理用户请求——这些请求都将汇集到单个服务器的网络接口卡上,并且得到处理,对它们的回复也是通过网络接口卡发送的。而在不同处理器之间分配负载的具体任务,就交给操作系统调度程序来完成了。由于Apache是多进程的,可以给每个处理器分配一个子进程,然后同时处理很多请求。

开始横向扩展时,新问题就出现了。我们有多个处理器,却没有操作系统分配请求。我们将在同一IP地址收到多个请求,而且希望在多台机器上服务这些请求。这些问题有多种解决方法,但它们全都从属于术语“负载平衡”。

最简单的,在多台Web服务器之间负载平衡的办法,是在DNS区域(zone)中为应用程序的域创建不止“一条”记录。DNS服务器打乱这些记录,并且随机发送给每个发出请求的主机。当用户在浏览器中输入地址时,浏览器询问DNS服务器(使用迭代查询),以返回对该域的一个记录列表。DNS会返回一列地址,客户端从这个列表中的第一个开始尝试。在不考虑冗余和故障转移的情况下,基于DNS的负载平衡是目前最简单的在多个地理区域均衡用户请求的方法。

这种负载平衡由于一些原因被称为“穷人的负载平衡”。向池中添加和移除机器是个缓慢的过程。根据所在区域(zone)的TTL和DNS服务器的缓存时间,要使对区域的更改对所有用户可见可能需要数天。在这段时间内,有些用户会看到老的区域,而有些会看到新的。很难做到快速移除一台机器。如果一台机器死机了,得先找到它,更改区域,并且等待更改散播到各地的缓存中。在这台机器死机期间,对它的任何访问都可能被抑或不被重定向到第二个IP地址,要看用户浏览器的具体行为。整个移除过程很难自动化。因为我们不希望它自动从DNS区域中删除自己,任何检查过程中的错误都会导致所有服务器从IP列表中被删除,需要等待一段时间,让区域重建缓存和修复错误,在这段时间内,站点会完全处于下线状态。

DNS负载平衡的另一个问题是,它无法实现精确的控制,也不能定制均衡方式(至少不容易)。由于DNS缓存,一个用户可能在一台机器上阻塞达一个小时,甚至更长时间。如果很多用户共享一个DNS缓存,比如大型ISP的情况,大量的用户会阻塞在一台服务器

上。总的来说,DNS负载平衡并不是一个很实用的解决方案。

在寻求更好的负载平衡解决方案前,应该谈谈两种基本的负载平衡模式和它们的区别。对于PHP的“毫不共享”的应用程序模型,来自客户端的每个请求都可以发送给池中的任何机器。这种无状态模式对负载平衡器的要求不多,因为它分别处理每个请求,并且不需要存放状态。对于带有共享的本地数据和状态的应用程序,就需要确保在一次会话(不管“会话”具体如何定义)中,同一用户的每个请求都访问同一台服务器。这种粘滞会话(sticky session)需要负载平衡器提供更多的逻辑和资源,而有些负载平衡的解决方案不提供这些。

Sticky Sessions粘滞会话

大多数4层的负载平衡器支持某种粘滞会话,在其中,客户端的每个请求都被路由到同一台后端服务器。用来实现这种负载平衡的方案各有不同,但往往用到比较高的层次,要在客户端设置cookie或者对它的HTTP请求细节应用Hash算法来创建客户端签名。在粘滞会话这个话题上,我们不会展开讨论,因为在REST模型下,它们不是必须的,这是相当幸运的,因为可以绕开粘滞会话环境中的故障转移方面的问题。

和现代计算机的其他领域一样,你至少得熟悉一个三字母缩写——VIP。也就是虚拟IP,一个VIP是由负载平衡器提供的一个IP地址,背后其实有多个“实际的”IP地址在处理请求。负载平衡器也被称作“虚拟服务器”,而池中的节点就是“实际服务器”。

硬件方式的负载平衡

Load Balancing with Hardware

要在池中多台机器间实现请求负载平衡,最直接的方式是使用硬件设备。只要接上硬件,打开它,完成一些基本的(或更常见的,非常复杂的)配置,就可以开始分担流量。但使用硬件设备有一些缺点。它在可配置性方面很差,尤其是管理大型的VIP(带有很多实际服务器)时,因为配置界面往往是复杂的基于telnet的命令行,或者20世纪90年代初期的Web界面。任何对配置的改动或检查都是手工的过程,或者是编写一套脚本,通过调用底层自身接口,进行远程操的过程。具体要看程序的规模,这一点有可能不是什么大问题,尤其是在你打算配置好设备之后就很长时间不去管它,让它自己运作的情况下。

但是,这种方案在价格方面不如人意。硬件负载平衡设备总是非常昂贵,从上万美元到数十万美元。还要记得我们至少需要两台用于灾难时实现故障转移,成本就更加昂贵了。有有很多公司制造负载平衡产品,通常还捆绑有其他特性,比如Web缓存、GSLB、HTTPS加速和DOS保护等。这类设备中比较上规模的有Alteon’s AS 系列(应用交换机)、Citrix Netscalers、Cisco的CSS系列(内容交换服务器),以及Foundry Network的ServerIron家族。初期要找到这些产品可不容易,因为公司之间命名习惯各不相同,容易互相混淆。除了打上负载平衡器的标签之外,这些设备也叫Web交换机、内容交换机和内容路由器等。

和早先提到的DNS方式的负载平衡方法相比,使用硬件设备有一些主要的优点。向VIP添加真实的服务器和从VIP中移除真实的服务器都是即刻发生的。只要添加了一台新的服务器,流量就会流向它,移除它时,流量也立即停止流向它(尽管对于粘滞会话而言,情况可能不是那么简单)。这里没有DNS方式中需要等待传播的问题,因为需要知道配置的唯一一台机器就是负载平衡器自己(和它的备用机器,但配置变化往往通过专有连接在这两者之间共享)。当三台真实的服务器被放置到VIP中,就可以确认它们会享有相同流量。和DNS负载平衡不同,这种方式并不依赖于客户端从服务器处得到不同的答案,所有客户端都访问相同的地址。对于每一个客户端,都只有一台服务器——虚拟服务器。因为这种方法是在单一位置基于每个请求(对于粘滞会话则是基于会话)来处理负载平衡的,它可以公平地均衡负载。而实际上这种方式是可以根据任意需求来均衡负载的。如果手中有一台Web服务器,由于某些原因(有更多的RAM或者更强的处理器),它有额外的容量或性能,那么可以对它进行不平均分配,给予它更多的流量,这样可以充分地使用手中的资源,而不是白白地浪费额外的性能。

硬件设备这种方式和基于DNS的方式相比,最大的优点在于能很好地处理故障问题。当在VIP池中,有一台真实的服务器当机了,负载平衡器可以自动检测到这一点,并且不再向它发送流量。这样就可以完全自动化Web服务的故障转移。向VIP中添加比所需更多的服务器之后,如果有一台或者两台当机了,处于其他节点之上的空闲的性能会继而为我们服务,就好像没发生什么事一样——至少从用户角度考虑没什么事发生。接下来的问题就是,设备怎么知道哪一台真实服务器出故障了呢?各种设备支持不同的自定义的方式,但基本上都基于同一个理念,设备周期性地检测每台真实服务器,看它是否仍然给出回复。具体操作可能包含简单的网络可用性检测、使用ping或者协议级别的检测,比如对于给定的HTTP请求,会返回一个特定的字符串或者复杂的自定义的通知机制,检查内部应用程序的健康状态。

软件方式的负载平衡

Load Balancing with Software

在跑出去花个$100 000来购买这些光彩夺目的机器的其中一台之前,最好还是了解一下其他的选择。在软件层次,也可以完成相同种类的负载平衡,不过是通过运行在常规机器之上的服务软件,而不是运行在ASIC之上的负载平衡操作系统。软件负载平衡解决方案从非常简单的操作系统到极为复杂的操作系统都有。这里会看一些常用的选择,以及它们为我们提供了些什么。

对于简单的情况,可以用Perlbal(http://www.danga.com/perlbal/),一个自由的基于Perl的负载平衡应用程序。Perlbal支持简单的HTTP平衡,将流量分配到多台没有区分权重的后台服务器上。连接只是平衡到一台服务器上,该服务器上有处于工作状态的HTTP服务,所以不需要任何配置来检测和禁用死机的服务器。运行时统计信息可以通过管理控制台获取,以进行监测和警告。后台服务器可以动态地加入和删除信息,而不用重新启动系统。Perlbal不支持粘滞会话。

Pound(http://www.apsis.ch/pound/)提供了大多数你希望从负载平衡器获得的服务。它在多台服务器之间均衡请求,检测出故障的服务器,执行第七层的负载平衡(本章稍后讨论),支持粘滞会话,并且封装了SSL。Pound基于GPL发布,因此属于自由软件,并且它非常易于使用。对服务器是否当机的检测仅限于检测能否连接到Web服务端。外界没有办法从Pound服务器中抽取当前运行时的信息,因此无法知道哪些真实服务器在哪些时候是活跃的,和当前正在服务哪些请求,也无法获取任何其他活动数据,这增加了调试难度。配置的变动需要重新启动后台程序,在高流量环境中这会导致连接丢失。

对极为复杂的情况,可以考虑Linux虚拟服务器或者LVS (http://www.linuxvirtualserver. org/), 它给Linux打上内核补丁,把Linux变成负载平衡操作系统。LVS可以使用NAT(和其他软件一样,回复要流经负载平衡器)重定向流量,也可以使用IP通道,或者直接服务路由(direct service routing),这样响应就从真实服务器直接传到客户端。LVS的连接调度组件负责选择一个真实服务器来服务某个请求,它有不下10种的调度算法,能应付任何模式下对真实服务器的选择。对于粘滞会话,LVS有多种支持方法(源IP、认证、URL参数、cookie等)。通过简单地查看/proc文件,LVS能在运行时抽取大量统计信息。而且LVS所有的配置选项都可以动态改变,因此它能够在不影响用户的前提下,添加和删除真实的服务器。有几本书讨论LVS和它大量的选项,其中包括Karl Kopper(No Starch Press)的《The Linux Enterprise Cluster》,它专门有一章是讲这个的。LVS服务器可以使用heartbeat协议交流,这样在一台负载平衡器当机时,就能进行在线故障转移,这让LVS成为我们所举的软件负载平衡例子中,唯一提供在线故障转移的。

如果你打算使用软件负载平衡,那么需要提供运行软件的硬件。但这种硬件的要求往往较低,所以成本和硬件设备方式平衡中的设备是不能比的。常规的机器比起它们ASIC驱动的兄弟要更容易出故障,所以应该配备两个甚至更多的冗余。如果把相同种类的机器用于负载平衡和Web服务,那么就可以避免配备额外的空闲机器。在需要另一个负载平衡器时,从Web服务器池中拿一台过来就行,反过来也是一样。

第四层

Layer 4

传统上,负载平衡是第四层的事。一个第四层的连接被建立起来,并且被均衡到某台可用的真实服务器。负载平衡器在这一层捕获请求即可,因为TCP流包含路由这个请求所需要的信息——来源和目标的IP地址和端口。有了这些信息,就可以把这个连接引导到后台的正确端口。

最简单的第四层负载平衡的形式是使用循环(round robin)算法。在这个算法中,我们接收每个新来的连接,并将它导向列表中处于首位的后台服务器。当下一个请求到来时,就把它导向下一个后台服务器,依此类推,直到向每个后台服务器都发送过一个请求。这时,下一个新的请求就发送到第一个后台服务器,并且重复整个过程。负载平衡器层需要的是真实服务器的列表,还有一个变量,它负责标记最后被使用的服务器,如图9-2所示。

还有其他第四层的调度算法,但它们都遵循类似的原则进行工作,利用可用的真实服务器和前一个连接或现有连接的信息。在最少连接算法中,负载平衡器检查活跃连接(对经NAT处理过的连接,这些活跃连接会流经平衡器),并且将新的请求赋予当前服务请求数目最少的真正的服务器。这样比循环调度好的地方在于,对于那些正在处理较慢查询的真实服务器给予了一定补偿,避免同时压给它们太多连接。

第四层调度算法也包含定制的度量值,比如检查每台真实服务器的负载特定或程序特定的度量值,并且基于这些信息分配新的连接。只要所需查看的内容没有超出第四层的请求分组的内容,就认为它是属于第四层的平衡器。

第七层

Layer 7

第七层负载平衡是这个领域中的新来者。第七层负载平衡器检测包括第七层的消息,检查HTTP请求自身。这能够查看请求和请求的标头,并将这些信息纳入平衡策略的考虑中。因此在平衡请求时,可以基于查询字符串中含有的信息、cookie中的信息或任何标头中

图9-2:第四层的负载平衡

的信息,当然也可以用第四层的信息,包括源地址和目标地址。

最常用于第七层平衡的是HTTP请求自己。基于URL平衡,可以确保对某个特定资源的所有请求都被调度到同一台服务器上,如图 9-3所示。

要实现这种方式,可以一直保留一个hash表,或者使用简单的索引。采用hash表方式,在表中为每个被请求的URL创建一个条目。当出现hash表中没有的URL请求时,就为它选择一台服务器,并且把这个选择作为hash的值存放在hash表中。有后续的相同URL的请求时,会查看hash表,并且找到相应的服务器。

简单的索引方式,是对URL执行某种hash算法或者CRC算法,从而从URL中导出一个数字。如果为每台真实的服务器分配一个数字,比如说1到5,那么接着要做的就是把得到的数字去模拟服务器的数目,再加一,就得到了这个URL映射到的真实的服务器。如果所使用的hash函数能够以相当均匀的分布来分发URL,那么就能在每台服务器上分布相同数目的URL。如果某台服务器不可用,则需要有某种公式去重新计算一个hash值,它会映射到余下的真实服务器中的某一台。比如说URL/foo.txt一般映射到4号服务器,但4号服务器不可用,那么就执行更进一步的hash计算,并且最终决定将它映射到2号。

图9-3:第七层的负载平衡

所有后续的相同URL的请求都会采用相同的算法,并且URL会被调度到同一台服务器,当4号服务器重新可用之后,对/foo.txt这个URL的请求又被导向这台服务器。这种方式可以避免在负载平衡器上保留一个hash表,这意味着我们不需要很多内存或磁盘空间。一个轻微的缺陷在于,如果4号服务器发生颠簸,请求会在4号和2号服务器之间切换,而不是持续发送给某一台服务器。

这时,你可能会奇怪,到底为什么需要映射一个特定的URL到一台特定的真实服务器。这是个合理的问题,而且答案并非显而易见,考虑如图 9-4所示的设置。

假定某个大型的后台存储器上有需要提供给客户的文件。我们当然可以选择直接从磁盘上提供,但磁盘速度缓慢,而且我们打算每秒服务大量请求,因此需要Squid这样的缓存代理(本章末会讨论这个)来缓存需要服务的文件。一旦文件工作集合增大了,就需要增加另一台缓存服务器或很多缓存服务器来应付负载。缓存服务器只需要保留数据的一小部分在内存中和小而快速的磁盘上,就可以让整体速度较快。

如果使用的是第四层的负载平衡器,那么请求会散布在全部的缓存服务器上,看起来一切都很好。真的么?如果现在工作集很大,那么我们会逐渐把一些内容移出缓存,或者至少

图9-4:负载平衡器和缓存阵列

从内存缓存中移除它们。增加更多服务器的目的也是增加在线缓存的大小。实践结果表明,在每个缓存中的内容都大同小异——最经常被请求的文件被请求很多很多次,其中部分请求发向第一台缓存服务器,部分请求发向第二台服务器,依此类推。这意味着即使有五台缓存服务器,实际上也可能没有存放任何额外的对象——所有的缓存服务器都包含相同的内容,这是对空间的浪费。

使用第七层负载平衡时,就可以使单个对象只存在于一台缓存服务器上,确保所有的缓存都是完全唯一的(不考虑一台机器出故障时的故障转移语义)。这样能充分利用所有可用的缓存空间,能同时把更多的数据缓存起来。这提高了缓存命中率,并且能够服务的请求数目远胜从前,因为不在缓存中,因而需要到磁盘上去获取的对象更少了。

使用Apache的mod_rewrite,再加上一些脚本,就能实现第七层的负载平衡。把一个Apache实例设置成负载平衡器,就可以通过一个脚本来路由所有的请求。

RewriteEngine  on

RewriteMap  balance    prg:/var/balance.pl

RewriteLock  /var/balance.lock

RewriteRule  ^/(.*)$    ${balance:$1}    [P,L]

每个抵达负责负载平衡的Apache实例的请求,都被传递给了/var/balance.pl脚本,由这个脚本决定把请求均衡到哪儿。mod_rewrite接着在proxy模式(P选项)下重写URL。如果不考虑池中的服务器是正常运行还是当机,那么可以用非常简单的一个Perl脚本实现基于URL的均衡:

#!/usr/bin/perl -w

use strict;

use String::CRC;

$|++;

my @servers = qw(cache1 cache2 cache3);

while (<STDIN>) {

  my $crc = String::CRC::crc($_, 16);

  my $server = $servers[$crc % scalar @servers];

  print "http://$server/$_";

}

Apache启动后,该脚本被执行,然后就一直接收需要重写的URL。String::CRC模块为每个URL计算循环冗余校验码(CRC),这个过程非常快(这个模块是用C语言写的),并且对于相同的字符串总是给出相同的结果。接着使用计算出来的CRC的值从后台机器列表中选择一台服务器。

经过稍许努力,就可以加入一个数据库查找或者缓存查找,每几秒检查一次后台服务器的状态,或者向每台服务器依次执行HTTP请求,以检查其是否可用,当第一个选择不可用的时候,返回列表中的下一台服务器(或者采用其他某种算法)。

把代理作为后台服务器的一部分运行并不是一个明智的选择,因为Apache往往有很“厚重”的线程,而且对每台后台服务器的每个当前请求,负载平衡器都要保持一个线程处于打开状态。作为替代方案,可以运行一个最小化的Apache实例,只加载必需的模块(这里是mod_rewrite和mod_proxy),减小每个线程的体积。还有一种方案,是在某台后端服务器上运行两个Apache实例来运行这个负载平衡代理,一个在端口80上,用于负载平衡,另一个在不同的端口上(比如81),用于内容服务。

使用Apache作负载平衡器还有一个缺点——运行它的机器会成为独立的故障点。要解决这个问题,可以在多个Apache负载平衡器前端使用一个专用的平衡硬件设备,在多台Apache负载平衡器之间使用DNS负载平衡,或者在操作系统层级,使用某种灵活的IP故障转移软件。

大规模平衡

Huge-Scale Balancing

对于更大规模的设置,需要的就不仅仅是简单的独立加载的(或活跃/非活跃对)负载平衡器了。对于特定应用领域的大型程序,可能需要把应用的服务划分成一个或者多个透明的集群。使用第七层负载平衡器,可以把真实的服务器划分成多个池,每个池处理特定集合的URL。这种情况下,可以按树结构安排负载平衡器,前端平衡器将流量划分到集群,每个集群都有自己的VIP。每个机器都有另一个负载平衡器,在集群的相同节点之间平衡请求。

对这样的负载平衡器配置的需求很少,因为可以使用单个智能化的负载平衡器运行整个配置。有些情况下,对于集群级别的第七层负载平衡,可能需要把应用程序划分成多个区域,每个都有它自己的平衡器,以避免hash表的空间被耗尽。

需要大规模平衡负载时,更经常需要的是全局服务器负载平衡(GSLB),从而在两个或者三个DC之间平衡负载。GSLB执行一些比简单的循环负载平衡更为复杂的函数。使用不同的度量,我们能从最近的DC(这里的最近是指跳跃计数或者跳跃延时)为用户提供内容,尽可能地减少用户延时。在靠近用户的多个DC处安置应用程序并不少见。如果在美国东海岸和西海岸各有一个DC,在欧洲有一个,东亚有一个,那么总会有一个DC离用户比较近。

延时的长短很重要,但多数据中心负载平衡还带来了一些其他特性,这些特性一般是在数据中心内部负载平衡时所期望的。对我们而言,其中主要的特性是能检测故障和路由流量以绕过故障点。在机器层,常规的负载平衡器能保护我们。如果一台机器出现故障,负载平衡器会停止向它发送流量。使用GSLB,可以在DC层级做同样的事情——如果一个数据中心下线了,还是能通过其余的数据中心提供所有的流量服务,保持应用程序在线,并且用户根本意识不到有数据中心下线了。

当然,事情并不总是一帆风顺的,大多GSLB策略中都存在一些问题。GSLB的两种主要方法各有其自身的问题。我们会讨论两种方法中的问题,以及如何处理这些问题。数据中心之间的平衡存在的最基本的问题就是没法使用一个单独的设备来进行负载平衡,因为到这个设备的外部连接可能会中断。

我们已经知道,DNS系统可以为一个域添加多个“A”记录,并且让它们指向不同的IP地址。在当前例子中,假定有两个数据中心,每个都有一个单独的负载平衡器。负载平衡器的VIP分别是1.0.0.1和2.0.0.1。在myapp.com的DNS zone文件中,为A记录设置两个IP——1.0.0.1和2.0.0.1。当用户请求域的IP地址时,返回这两个地址的列表,并且它

们的顺序是随机的。客户端尝试第一个地址,并且被导向某个DC的负载平衡器,继而到达某个真正的服务器。如果数据中心不在线,并且IP地址变成unreachable,客户端会尝试列表中的下一个地址,连接到另一个DC的负载平衡器。想要一切都运行良好,就要让负载平衡器理解,当它后端的所有服务器都不可用了,它需要显示自身处于死机状态。如果介于负载平衡器和后台真实服务器之间的连接被破坏(有可能是某个网络电缆被拔出或者某个交换机失灵),那么我们希望将流量故障转移到另一个数据中心。或者,后台服务器出现故障的那台负载平衡器可以把所有连接都引导到另一个数据中心的VIP上去。

通过灵活处理DNS请求,可以添加粗略的基于近似关系的DC平衡。当一个DNS请求进入时,可以判断它来自哪个用户,找出和这个用户最近的数据中心。接着把这个数据中心的VIP的IP地址作为DNS响应返回。世界各地的用户请求DNS解析我们的域名时,他们将得到不同的答案。但这种方法存在一定的问题。这里提到了最近的DC,所谓的远近,是到用户本地的DNS服务器的实际距离。这和用户实际接入网络时所在地点往往是不同的,有可能超出10跳之远。使用在另一个国家的DNS服务器的用户,会由他本地的DNS服务器所在国家的DC提供服务,而不是根据当前所用的DNS服务器来平衡。这不算什么大问题,另一个问题是,我们不能仅仅返回最近的DC,这会丢失在DC失效时故障转移的能力。要获得任何冗余能力,都需要返回指向不同DC的多个IP地址。当然我们可以返回排好序的IP地址,但中途的DNS服务器会打乱这个序列。还可以返回添加了权重的列表,通过多次包含某个DC的IP地址来提高它的优先级,但这只是增加了客户连接上离他们最近的DC的可能性,而不是确定性。我们还可以将DNS响应的生存时间(TTL)设置为0,并且只返回客户端应该连接的DC。这样在DC故障时,新的DNS请求就会返回正在工作的服务器。这种方式的问题在于,有些DNS缓存和几乎所有浏览器都会缓存对任何对象的DNS,时间跨度从一个小时(Internet Explorer)到一个星期(某些DNS缓存服务器)不等。

但是这种方式的根本问题在于不能有效地均衡负载。运气不好时,DNS缓存可能导致百分之八十的流量流向一个DC,而其他的DC无所事事,我们也基本上做不了什么事情来处理这种情况。用DC的负载平衡器提供DNS服务,可以稍微改善这种情况,稍微增加由空闲DC服务的用户数目,但这还只是在较高层次进行流量的配置,而不是按连接进行调整的。

在GSLB情况下,另一种相当可靠的故障转移方案是利用互联网路由协议。这种方法需要一个自治系统(AS)号码,以及来自各个DC的ISP的合作,这些要求使得它对大多数人都不太实际。这个系统的原理相当简单,源自互联网常用的故障处理方式。我们以单一IP

地址1.0.0.1发布DNS,这个地址属于第一个AS(它覆盖1.0.0.0/24地址空间)。然后使用BGP协议,将这个路由从第一个DC的负载平衡器中发布出去。与此同时,也从其他的数据中心发布相同的路由,但使用更高的度量(BGP使用度量来判断最佳路径)。流量会流向第一个DC的负载平衡器,在这里,它们按照连接的方式被平衡到这个DC的真正的服务器中和其他DC中。当第一个DC不可用时,本地的路由器会发现这一点,并使用BGP通知其他路由器,将这个路由从路由表中删除,流向1.0.0.1的流量会被路由到第二个DC。当然这种方式也有缺点:在进行路由收敛时,用户不得不耐心等待。

采用这个模型,也无法得到使用本地DC带来的延迟时间的减少,因为所有的连接都流经第一个DC的负载平衡器。使用特定的站点名称,比如说site2.myapp.com,将它的DNS记录直接指向第二个DC的VIP,就可以将用户绑定到不同的地址,从而解决上述的延迟时间的问题。当某个用户开始向第一个DC发起请求时,基于接近程度,它了解到这个用户应该使用第二个DC,就使用一个HTTP重定向到特定地址。这种方式存在的问题,是无法处理第二个站点出故障的情况,因为已经使用静态的域名“硬性选定”了这个站点。一种可能的解决方案,和第一个系统类似,是为第一个DC使用更高的度量,建议路由选择第二个DC,而不是第一个DC。这样在被硬性均衡到第二个DC,而且在它不可用的情况下会自动转向第一个DC,并且一直使用这个DC(site1.myapp.com)。

没有哪个方案是完美的,有时两者的结合可能会适合你。高端硬件负载平衡设备往往支持基于DNS的站点故障转移,并且转移到还有幸存的负载平衡器处。有时把这总体负载平衡和故障转移的重负交给第三方更为简单。Akamai的EdgePlatform 服务(原来叫Freeflow)就是用于这个目的,提供基于接近程度/延迟的DNS平衡,并带有智能的站点故障转移功能,你可以在http://www.akamai.com 找到更多信息。

平衡非HTTP流量

Balancing Non-HTTP Traffic

需要平衡的不仅仅是HTTP流量,其他向外部提供的服务或者自己内部使用的服务也需要流量平衡。向外部用户提供的其他主营服务可能是电子邮件,将一个公开的DNS MX记录指向一个IP地址。就和Web服务一样,可以将这条记录指向一个负载均衡过的VIP,在后端其实有多台机器。和HTTP不一样,在DC服务中断时,我们还可以处理邮件路由的少量中断。当一台邮件服务器尝试发送邮件到另一台服务器(比如我们的应用程序),并且发现对方无法抵达时,它会把邮件放入队列,并且以后再次尝试。对于基于DNS的负载平衡,可以只提供最近处的DC的IP地址,在当前选择的DC不可用时,依赖DNS

缓存超时将流量导向新的DC。实际上,电子邮件为我们提供了另一种DNS负载平衡的故障安全的处理方法。在zone文件中的MX记录看起来如下所示:

MX  5  mx1.myapp.com.

MX  10  mx2.myapp.com.

MX  15  mx3.myapp.com.

我们为列出的每个IP地址都附上了一个优先级,首先尝试数值最低的那条记录。如果连接失败,继续尝试列表中的下一个,直到找到可达的服务器。我们完全可以只用DNS接近度平衡来将邮件均衡到地理位置上的各个数据中心,并且无需任何成本即可拥有DC故障转移的语义。所需要做的,只是给予最近的DC最高优先级(数值最低),给第二接近的DC第二高的优先级(数值第二低),依此类推。

因为SMTP和HTTP如此类似(基于文本的协议,有MIME标头和主体),基本上可以使用HTTP负载平衡来均衡邮件流量。全部要做的就是创建一个新的服务(从负载平衡器的角度讲)监听端口25,并且将它关联到后台真正的服务器。不同之处在于如何检测真实服务器是否活跃。只要执行HTTP检查的负载平衡器(比如说Perlbal)就不能均衡SMTP流量——在这样的平衡器看来,端口25上的HTTP后台进程好像已经僵死了。

要平衡其他种类的流量,可以使用一种欺骗性的手段,将它们封装成HTTP消息,并且使用常规的HTTP负载平衡器。假定应用程序有一个服务,返回了一个当前用户的对象的列表。一开始,我们可以使用PHP函数调用本地“连接”到这个服务。随着应用程序的增长,我们发现这个服务组件对CPU要求很高,因此将它分离出来,独占一台机器,并且使用某种定制的基于套接字的协议来和它连接。事情进一步演化,当需要把这个服务划分到多台机器上去时,就需要某种方法来决定对于每个请求使用哪台机器进行连接。如果能更改这个服务,让请求和响应遵循HTTP协议,那么只需要在负责服务的那些机器前端添加一个负载平衡器,然后通过VIP连接到这个服务。这样就把均衡逻辑从应用程序层级移出,而且看起来就像一直都是连接同一台单独的服务器。

对于有些服务,比如向一个服务器池所做的数据库查询,如果没有使用基于HTTP的协议,而且要切换成基于HTTP的协议又非常麻烦的话,负载平衡的设置非常烦琐。作为替代的解决方案,可以使用随机的方式来进行廉价的负载平衡。假定有五台数据库从机,每个都能同样好地为请求提供服务。当前的目标是为每台机器分配五分之一的查询,并且处理好机器故障问题。如果将服务器名字放置在一个数组中,我们可以简单地打乱这个列表,并且挑选一台服务器来使用。如果连接这台服务器失败,那么就尝试列表中的下一台服务器,直到找到可以连上的服务器,或者发现没有服务器可用为止。在PHP中,用数行代码就可以实现这个功能:

function db_connect($hosts, $user, $pass){

  shuffle($hosts);

  foreach($hosts as $host){

    $dbh = @mysql_connect($host, $user, $pass);

    if ($dbh){

      return $dbh;

    }

  }

  return 0;

}

我们会得到一个有效的数据库句柄或者0(如果所有可选数据库都出了问题)。因为每次都随机打乱主机列表,所以查询是平均分配在所有的服务器上的。如果希望给某台特定的服务器更多份量的查询,只要简单地在主机列表中多次包含它即可。接着添加特定的短路逻辑,在某台服务器连接失败的情况下,避免再次尝试连接这台服务器。还可以添加主要服务器集合和次要服务器集合的逻辑,我们只有在尝试过所有的主要的服务器之后,才会尝试次要服务器池中的服务器。这很容易实现,只要乱序产生两个数组,然后把它们拼接成一个单独的主机列表即可。

和使用专用的硬件或软件来进行均衡相比,这种负载平衡模式有不少优点。首先,它要比其他方式经济得多,因为不需要任何附加硬件来控制平衡。第二,它可以均衡任何应用程序能够连接上的流量,这意味着无需尝试着将复杂的协议改为基于HTTP运行。当然,它也有不少缺点。这种方式给业务逻辑层添加了复杂性,而冗余相关的操作不应该驻留在这一层。糟糕的层次划分会产生让人困惑和难以维护的应用程序,所以总要注意将负载平衡操作和主业务逻辑代码分开。不添加额外的逻辑的话,它也不能支持粘滞会话。如果需要粘滞会话,那么可以生成一个随机数字,把它存放在用户cookie中,然后使用它作为随机算法的种子。这样,对于这个用户,每次打乱主机列表的最后结果的序列总是相同的。这种方式也依赖于使用代码快速检测某个服务了。如果一台服务器能够接收连接,但却从不给出回复,那么我们就得等待这台实际上死机的服务器,直到I/O超时。当一台服务器处于这种状态时,一部分用户一开始还是会被平衡到这台服务器上,不得不等待I/O超时,然后才能被引向列表中的下一台机器。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论