野男人

不想做CEO的程序员不是一个好的产品经理

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
服务器农场的可伸缩性和高可用性
Server load balancing architectures, Part 1: Transport-level load balancing
Scalability and high availability for server farms
By Gregor Roth, JavaWorld.com, 10/21/08

原文:http://www.javaworld.com/javaworld/jw-10-2008/jw-10-load-balancing-1.html

进入Internet的门槛是很低的,任何有点想法的人都可以开发一个小应用,然后购买一个域名,架设一些基于PC的服务器就可以处理即将到来的流量了。启动时的投资很小,风险也小。不过随着你的应用的流行,这个廉价的架构很快就会成为问题。一个能处理所有请求的单服务器是不够处理高流量的。这种情况下会进行scale up:通过购买超强的处理器或更多内存的方式升级现有的架构(infracstructure)。

scaling up只是一个短期的解决方案。作用也有限,因为升级服务器的成本与其获得的相应能力不成比例。因此,许多成功的Internet公司使用scale out的方式。应用系统的组件由多个运行在服务器农场(server farms)上的实例来处理,这些服务器农场是基于廉价硬件和操作系统构建的。当访问量(traffic)增加,服务器也相应增加。

服务器农场(server-farm)的方式有它自己的一些要求。对于软件来说,必须设计成能在不同的服务器上以多个实例运行。可以通过将应用划分成更小的能独立部署的组件做到这点。如果这些应用组件是无状态的,这很容易办到。因为组件不必保留任何事务状态,任一组件都能平等的处理同样的请求。如果需要更多的处理能力,只需要加入更多的服务器并安装这些应用组件就行了。

当应用组件是有状态时我们面临更多的挑战。例如,如果应用组件保持购物车数据,一个到来的请求必须被路由到持有此请求者购物车数据的应用组件才行。本文稍后将讨论在一个分布式环境中如何处理此类应用级会话(application session)数据。然而,为了减少复杂性,许多成功的Internet应用系统都试图尽可能的避免有状态的应用组件。

就系统架构来说,处理载荷必须分布到服务器中。这被成为服务器负载均衡(server load balancing)。负载均衡技术也与其它领域相关,例如,网络链接、CPU或硬盘间如何分散工作(spreading work among components such as network links, CPUs, or hard drives. )本文着重讨论服务器负载均衡(Server load balancing)。

可用性和可伸缩性(Availability and scalability)

服务器负载均衡(Server load balancing)将服务请求分布到一组真实的服务器中,对客户端来说这些服务器看上去就像是一个单独的大服务器。经常一个URL后有数十个真实服务器实现一个单独的虚拟服务(Often dozens of real servers are behind a URL that implements a single virtual service.)。

这是如何工作的?在一种广泛使用的服务器负载均衡架构中,到来的请求直接发给一个专用的负载均衡器,此服务器对客户端来说是透明的。基于某些参数,如可用性或当前服务器载荷等,负载均衡器决定哪个服务器应该处理请求并将请求转发给选中的服务器。为给负载均衡算法提供必要的输入数据,负载均衡器还需要获取服务器的健康和负载情况,以确保它们能处理并及时响应。图1表示传统的负载均衡器架构。

Figure 1. Classic load balancer architecture (load dispatcher)
图1所示的负载调度(load-dispatcher)体系结构(architecture)只是负载均衡方法中的一种。要判断那种负载均衡方法最适合你的架构(infrastructure),必须考虑可用性和可伸缩性。

可用性(availability)由运行时间(uptime)定义,运行时间(uptime)是 the time between failures。(停机时间(downtime)是指检测到错误、修复错误、执行必要的恢复以及重启任务的时间。)运行时间(uptime)内系统必须在预定期限响应每一个请求。如果时间超出,客户端将视为出现服务器故障。基本上,系统中的高可用性就是冗余:如果一个服务器出错,其他服务器将透明的接管出错服务器的载荷。一个单独服务器的错误对客户端来说是不可见的。

可伸缩性(scalability)意味着系统可以为一个客户端提供服务,也能在满足服务质量(例如响应时间)的前提下同时为成千上万的客户端提供服务。随着载荷的不断增长,一个高可伸缩的系统能通过添加硬件资源成线性比例的增加系统的吞吐量(throughput)。

如图1所示,高可伸缩性可以通过将进来的请求分发到服务器上实现。如果载荷增加了,只要负载均衡器不成为瓶颈,可以通过添加更多的服务器解决。要达到高可用性,负载均衡器必须监视服务器以避免将请求转发给过载的或已经死掉的服务器。此外,负载均衡器自己也必须是冗余的,稍后将讨论这点。

服务器负载均衡技术

一般服务器负载均衡解决方案可分为两种主要类型:
* 传输层(transport-level)负载均衡:例如基于DNS方式的、或TCP/IP级的负责均衡,这些都与应用的有效载荷无关;
* 应用层(application-level)负载均衡:使用应用的有效载荷做为负载均衡的依据。

负载均衡解决方案还可以划分为基于软件的负载均衡器和基于硬件的负载均衡器。基于硬件的负载均衡器由专用硬件组成,包括定制的ASIC集成芯片。不需要有通用操作系统,ASIC直接完成网络流量的高速转发。基于硬件的负载均衡器常用于传输层负载均衡。一般而言,基于硬件的负载均衡器要比基于软件的速度更快,缺点是价格更高。

与硬件式负载均衡器相比,基于软件的负载均衡器运行在标准操作系统和标准硬件(例如PC机)上。基于软件的解决方案 要么在一个专用于负载均衡器的硬件节点上运行(如图1所示),要么直接在应用中运行。

基于DNS的负载均衡

基于DNS的负载均衡代表了一种早期的服务器负载均衡方法。Internet的域名系统(DNS)将IP地址和一个主机名关联。如果你在浏览器上输入主机名(URL的一部分),浏览器会向DNS服务器发送请求以将主机名解析成IP地址。

基于DNS的方式,是基于DNS能够让多个IP地址(多个真实服务器)被赋给同一个主机名。DNS查找例子如清单1所示:
>nslookup amazon.com
Server:   ns.box
Address:  192.168.1.1

Name:       amazon.com
Addresses:  72.21.203.1, 72.21.210.11, 72.21.206.5
清单1. DNS查找例子

如果DNS服务器采用了轮询(round-robin)方式,那么DNS每次在对一个主机请求返回响应后,返回IP地址顺序都会发生变化。通常像浏览器这样的客户端会试着连接DNS请求返回的第一个地址。其结构就是对多个客户的响应被分布到服务器中。与图1中的负载均衡体系结构相比,不需要有中间的负载均衡器硬件节点。

对全球范围的服务器负载均衡(这里负载被分布到不同地方的数据中心),DNS是一个有效的解决方案。基于DNS的全球服务器负载均衡经常与数据中心的其它服务器负载均衡解决方案联合使用。

DNS方式虽然容易实现,但是有很严重的缺陷。为了减少DNS请求,客户端倾向于缓存DNS请求的结果。如果一个服务器变得不可用,客户端缓存以及DNS服务器仍然保有不可用的服务器地址。因此,DNS方式对实现高可用性没有太大帮助。

TCP/IP服务器负载均衡

TCP/IP服务器负载均衡器在底层实现交换(switching)。一个流行的基于软件的底层(low-level)服务器负载均衡器是Linux Virtual Server(LVS)。真实的服务器对外部世界表现的就像是一个单独的“虚拟”服务器。一个TCP连接进来的请求被负载均衡器转发给真实的服务器,该负载均衡器运行在一个打了包括IP虚拟服务器(IPVS)Linux内核补丁的系统上。

为确保高可用性,许多情况下需要建立一对负载均衡器节点,其中一个负载均衡器节点是被动模式。如果一个负载均衡器坏了,运行在两个负载均衡器上的心跳程序会激活被动负载均衡器节点,并初始化虚拟IP地址(VIP)的takeover(initiates the takeover of the Virtual IP address(VIP))。心跳程序负责管理两个负载均衡器的容错,简单的send/expect脚本用来监视所有的真实服务器。

通过使用一个赋给负载均衡器的VIP,客户端实现了透明访问。如果客户端发送一个请求,首先请求主机名被传输给VIP。当收到请求的数据包,负载均衡器将决定应该由哪个真实的服务器处理请求数据包(request packet)。请求数据包(request packet)目标的IP地址被重写成真实服务器的真实IP(Real IP, RIP)。LVS支持好几种分布请求给真实服务器的调度算法。常用的是轮询(round-robin)调度,类似基于DNS的负载均衡。LVS的负载均衡判断是在TCP级(OSI参考模型的第4层)进行。

在收到请求数据包(request packet)后,真实服务器处理请求并返回响应数据包(response packet)。为了强制响应数据包(response packet)通过负载均衡器返回,真实服务器使用VIP作为其缺省响应路由(response route)。如果负载均衡器收到响应数据包,响应数据包的源IP地址被重写成VIP。这种LVS路由模式被成为Network Address Transiation(NAT)路由。图2展示了使用NAT的LVS实现。

Figure 2. LVS implemented with NAT routing

LVS还支持其它路由模式,如直接服务器返回(Direct Server Return),这种情况下响应数据包被真实服务器直接发给客户端。要做到这点,VIP必须也被赋给所有的真实服务器。使服务器的VIP对网络不可解析是非常重要的(It is important to make the server's VIP unresolvable to the network),不然负载均衡器会不可访问(unreachable,不能连线)。如果负载均衡器收到一个请求数据包,请求的MAC地址(OSI模型第2层)被重写,替换成IP地址。真实服务器受到请求数据包并处理之。根据源IP地址,响应数据包将绕过负载均衡器被直接发送给客户端。对于Web流量,这种方式可以大幅度的减少负载均衡器的工作载荷。通常要传输的响应数据包要多过请求数据包。例如,如果请求一个Web页面,通常只有一个IP数据包(IP packet)被发送。如果是一个较大的Web页面请求,请求的页面需要返回好几个响应IP数据包。

缓存机制(Caching)

如果需要支持应用级缓存或者应用会话支持,像LVS这样的底层(low-level)服务器负载均衡解决方案会带来一些限制。如果经常重复的访问相同数据,缓存机制(Caching)提供了一个重要的可伸缩性原则(Caching is an important scalability principle)以避免这种代价高昂的操作。一个缓存是一个临时的存储场所,用于保存一些冗余数据,这些数据是以前进行数据抓取操作的结果。缓存的价值取决于抓取数据的代价vs命中率和缓存大小。

基于负载均衡器调度算法,一个用户会话中的那些请求将被不同的服务器处理。如果服务器端使用了缓存,到处散落的请求会成为一个麻烦。一个解决办法是将缓存放在一个全局空间中。memcached就是一个流行的分布式缓存解决方案,它提供了一个大型的跨多个集群的缓存。它是分区的、分布式缓存,使用了一致性哈希算法(consistent hashing)为一个给定的缓存决定相关的缓存服务器。基于缓存的关键字哈希值,客户端库总能将相同的哈希值映射到同一缓存服务器(daemon)地址上,这些地址将用于存放缓存数据。如图3所示:

Figure 3. Load balancer architecture enhanced by a partitioned, distributed cache

清单2使用了spymemcached,一个Java写的memcached客户端跨多个机器缓存HttpResponse消息。spymemcache库实现了上面描述的逻辑。
以下是spymemcahced的一些实现代码和相关说明(略过)
balabala.....

一致性哈希表的方式具有高可伸缩性,memcached客户端实现了一种支持高可用性的容错策略。但是如果一个缓存服务器(daemon)崩溃了,缓存的数据也就丢失了。这只是一个小麻烦,因为缓存的数据都是冗余的。

一个简单的办法可以使memchach处理这种情况,将缓存数据存放在一个主缓存服务器和一个二级缓存服务器上。如果主缓存服务器当掉,二级缓存服务器可能保留有相关数据,如果没有,请求的(缓存的)数据必须从相关数据源恢复。

应用会话数据支持(Application session data support)

应用会话数据代表用户相关的应用会话状态,例如选中的目录ID,或者购物车中的文章。应用会话数据必须能跨多个请求。传统Web应用中此类会话数据必须在服务器端保持。通过cookie或隐藏域存放在客户端有两个主要缺陷:它暴露了内部会话数据,例如购物车中的价格,导致对客户端的攻击,导致安全问题;由于HTTP cookie消息头的数据量限制以及应用数据从客户端来回传送的开销,这种方法只能保持少量的数据。

与memcached体系结构类似,会话服务器(session server)被用来在服务器端存放应用会话数据。然而与缓存相比,应用会话数据不属于冗余数据,因此如果内存耗尽不能简单的移除应用会话数据以腾出空间。对缓存来说可以根据内存管理的需要随时移除缓存数据。当缓存最大数量到达时,缓存算法一般采用最不常用(Last Recently Used,LRU)的缓存数据先移除。

如果会话服务器崩溃,应用会话数据也就丢失了。与缓存数据相比,应用会话数据大多数情况下是不可恢复的(recoverable)。因此采用支持应用会话数据容错的解决方案非常重要。

客户端关联(client affinity)

缓存及会话服务器的缺点是每个请求导致额外的从服务器到缓存(或会话)服务器的网络调用。多少情况下,调用延时都不会造成什么问题,因为缓存(或会话)服务器和业务服务器都放置在相同的快速网络中。但是当数据量增长时延时可能会带来问题。为避免中业务服务器和缓存(或会话)服务器之间反复的移动较大量的数据,一个客户端的所有请求必须总是转发到相同服务器。这意味着一个用户会话的所有请求被同一服务器实例处理。这种情况下一个本地缓存可以用来替代分布式的memcached服务器架构(infrastructure)。这种方式被称为client affinity,不再需要缓存服务器。
客户端关联(client affinity)总是将客户直接连到”它的“专用服务器上。

以下代码略

LVS通过persistence支持affinity:在一个预定时间内记住最后的连接。它使得一个特定客户端总是连接到相同的真实服务器上。但是如果一个链接是通过代理(proxy)进行的,对同一个session它可能每次都有不同的TCP连接,这种情况下,persistence帮不了什么忙。

总结

基于纯传输层(transport-level)的服务器负载均衡器架构(infrastructures)更为常见。它们简单、灵活并且高效,而且它们对客户端没有限制。此类体系结构(architectures)与分布式缓存或会话服务器组合,可以处理应用级(application-level)缓存和会话数据。然而,如果从缓存(或会话)服务器来回移动数据的开销增长了,此类体系结构会主机变得没有效率。要基于应用级服务器负载均衡器实现用户端关联(client affinity),必须避免在服务器间拷贝大量的数据集。第2部分将讨论系统级负载均衡机制。
posted on 2010-09-20 22:01  野男人  阅读(1826)  评论(1编辑  收藏  举报