Behind The Cloud--浅析分布式系统背后的基础设施

分布式hadoop消息队列分布式缓存远程调用

前言:WEB2.0的时代格局下,信息越来越发散,搜索越来越智能,内容越来越丰富,互动越来越频繁。随着团购、社区、微博不断崛起,互联网已经深入人心。用户规模的不断扩大,新业务的不断开拓,直接导致系统规模不断膨胀。在这种情况下,如何来保障分布式系统构建的稳定性与高性能呢?

 

 

冰冻三尺,非一日之寒,构建一个健壮的分布式网络环境,并非一蹴而就的事情,而是经过漫长的演变,一次次的血泪宕机之后,才逐渐形成的一个相对稳定的架构。一个稳健成熟的分布式系统的背后,往往会涉及众多的支撑系统,如:

分布式文件系统,用来提供给诸如图片,文档,搜索dump数据等内容的弹性存储空间,理论上外部可以认为其空间是无限的。

分布式协作系统,分布式系统中完成一个任务往往需要诸多机器之间的相互协作,分布式协作系统用来协调各机器的交互,实现诸如服务器状态感知,分布式锁,分布式应用配置项管理等功能。

分布式服务治理,为了提升系统重复利用率,避免重复造轮子,在分布式系统构建中,往往会采用SOA(service-oriented architecture)的体系结构,随着服务不断增多,依赖关系开始变得错综复杂,需要有一套健全的机制来对服务进行治理。

海量数据处理,搜索引擎的索引做fullbuild时,涉及到的数据可能包含整个互联网,如此巨大的数据量,往往需要一个庞大的集群来进行处理, job如何调度,节点间如何通信,failover等等,这些都是不得不考虑的问题。

集群负载均衡,依靠单一的服务器设备很难承受高并发访问的压力,而在网络的水平扩展过程中,负载均衡是一种常用的手段。

分布式数据库系统,用来提供海量数据的存储,能够自动切分数据,且能够提供高并发条件下的读写访问。

分布式缓存,高并发环境下,为了降低DB压力,缓存往往充当挡箭牌的角色,而分布式缓存则在更高的层面上完善了集群的failover和数据冗余机制。

分布式消息队列,消息发送后,可以立即返回,由消息系统来确保消息的可靠传递,消息作为应用间的一种通信方式,能够有效的降低各模块间的耦合性,提升分布式协作的效率。

分布式系统无疑规模庞大而复杂,笔者仅希望通过些许文字的分析和介绍,来窥探构建分布式系统所依赖的一些基础设施的概貌,给读者以启发。

 

CAP理论

在分布式系统的设计的过程中,有一点不得不提,这便是CAP理论。CAP理论最早是在2000年由Berkeley的Eric Brewer教授在ACM PODC会议上提出,此后,MIT的Seth GilbertNancy Lynch理论上证明了Brewer猜想的正确性。C表示一致性(Consistency),A表示可用性 (Availability),P则表示分区可容忍性 (Tolerance of networkPartition),一致性和可用性比较好理解,分区可容忍性指的是数据的分布对系统的正确性和性能的影响,往往也可理解为可扩展性,该理论认为,一致性、可用性和分区可容忍性这三点是不能够同时满足的。我们在以此作为设计的指导思想,设计实现一个分布式系统的时候,往往得做一些取舍,鱼和熊掌不可兼得。

 

1.分布式文件系统

传统的基于NFS或者Samba构建的共享文件系统,受限于共享服务器本身的磁盘吞吐率及并发处理能力,当并发的IO达到一定规模时,共享服务器将不堪重负,很容易形成瓶颈,从而导致读写请求响应缓慢甚至是无法响应。即便是采用一定级别的RAID来提高并行读写能力,也无法满足蜂拥而至的大量读写访问请求,并且容易形成单点故障。

 

Behind The Cloud--浅析分布式系统背后的基础设施

 

图1传统的共享文件系统

 

而在大型分布式系统的构建过程中,往往会涉及到相关海量文件的存储问题。举例来说,搜索引擎要实现快速的检索,索引文件显然是必不可少,而一般社交网站,均会提供给用户上传文件和图片的功能,在线的视频及音乐网站,也需要提供数目庞大视频及音乐供用户下载。而正是这些海量的数据存储以及高并发访问需求,催生了分布式文件系统。

事实上,分布式文件系统并非传统意义上的文件系统,它工作在操作系统的用户空间,由应用程序来实现,因此并不依赖于底层文件系统的具体实现。相较于传统的文件系统,分布式文件系统往往更像是一个抽象的实现,拥有自己独特的内容组织结构,从而保障了高容错、高可靠,高可扩展、低成本、高吞吐的特性。广为人知的分布式文件系统包括google的GFS,hadoop下的HDFS,淘宝的TFS[1]等等。



Behind The Cloud--浅析分布式系统背后的基础设施

 

图2HDFS体系结构

 

以HDFS为例,它包括一个主从(Master/Slave)的体系结构。NameNode负责管理文件系统的命名空间,维护着文每个filename到对应的blocksequence的映射,以及每个block对应的machinelist 。而DataNode则负责他们所在的物理节点上的存储管理。分布式文件系统提供一组类似本地文件系统的访问接口,客户端从NameNode中获得组成该文件的数据块的位置列表,当查询到数据块存储在DataNode的具体位置上后,客户端便直接从DataNode上读取文件数据,而不需要NameNode干预。

大部分分布式系统均是浇筑在廉价的PC服务器上,即便单个设备标称的(MTBF平均故障间隔时间)很高,但由于集群内机器数目庞大,因此硬件故障的概率也是非常高的。HDFS拥有比较完善的冗余备份和故障恢复机制,每个文件都被分成一系列的数据块存储,每个数据块均会有副本,且副本的数量(也称为复制因子)可配置。为保证数据的可靠性、可用性以及网络带宽的利用率,HDFS采用机架感知策略来进行数据的冗余与备份,一般情况下,当复制因子为3的情况下,数据的一个副本放在同一个机架的另一个节点,一个副本存放在本地节点,最后一个副本存放在不同机架的节点,这种策略既可以防止整个机架失效时数据丢失,在保证可靠性和可用性的前提下,又能保证一定的性能,减少了机架间的数据的交换。

对于HDFS来说,也并非所有领域都能够适用。由于HDFS是按文件来建立索引的,一般来说,每个文件的索引目录及块将占用大约150个字节的空间,而这些索引最终都是需要被加载进内存的,单台机器的硬件能力有限,因此文件的数量将NameNode节点内存的制约。并且NameNode本身无法扩展,存在单点的风险。相对于HDFS,淘宝的TFS采用二级索引的方式,将大量小文件合并成一个大文件,有点类似GFS中chunk的概念,对于chunk的定位称为一级索引,而chunk内部具体的文件定位信息称为二级索引,这样便避免了大量小文件带来的索引信息的膨胀。并且TFS的NameServer采用了HA结构,双机互为热备,能在Master宕机后,迅速切换到Slave,对外提供服务[2]


Behind The Cloud--浅析分布式系统背后的基础设施

 

图3 TFS体系结构

 

 

2.分布式协作

大型分布式系统当中,完成一个任务往往需要诸多机器之间的相互协作,而由于局部故障的存在,我们很难判断相互之间的协作是否成功。为了安全的处理局部故障,某些时候需要依靠第三方提供的一致性服务来实现诸如服务器状态感知,分布式锁,分布式应用配置项管理等任务。因此构建一个能有效防止单点故障,并且能够自动负载均衡的自治系统,来对其他分布式集群提供一致性服务,在大集群协作的分布式环境中相当重要。

Leslie Lamport在1990年提出的基于消息传递的一致性算法Paxos,解决了分布式系统如何就某个值(决议)达成一致的问题,也就是之前所说的分布式一致性问题。基于Paxos算法,google开发了分布式锁系统chubby,为松散耦合的分布式系统提供粗粒度的锁定以及可靠的存储,与此类似的开源系统有Apache下的ZooKeeper 。

以ZooKeeper为例,它实现了Zab协议,该协议看起来像是Paxos协议的某种变形,该协议包括两个阶段,Leader election阶段和Atomic broadcas阶段,集群中将选举出一个leader,其他的机器则称为follower,所有的写操作都被传送给leader,并通过broadcas将所有的更新告诉follower。


Behind The Cloud--浅析分布式系统背后的基础设施

图4Zookeeper集群

Zookeeper的核心其实是一个精简的文件系统,提供一些简单的操作以及一些附加的抽象(例如znode的排序以及Watch),并且集群的部署使其具有较高的可靠性。Zookeeper的协作过程简化了松散耦合系统之间的交互,即使参与者彼此不知道对方的存在,也能够相互发现并且完成交互。

在大型分布式系统中的master选举中,常常需要用到分布式锁,作为一种机制,来提供一组服务器进程间的互斥,而当前的master便是此刻持有锁的进程。在zookeeper源码包的recipe目录下有一个互斥锁lock的实现范例,笔者对其简要包装,以便看起来更为明了,详情请参见笔者博客,当然,如果读者有兴趣,也可以对Barrier,Latch等予以实现。

 

3.分布式服务治理

随着访问量的急剧攀升,导致服务端承受的压力越来越大,由于单台服务器节点硬件的垂直可扩展的空间有限,且随着规模的增大成本急剧攀升,单一应用依靠增加机器带来的速度提升成本越来越高,已经无法满足需求,因此不得不对其进行业务的拆分和细化,将相关业务抽取出来,形成独立的服务,这样既能够方便扩展和管理,又能够适应不断变化的需求,降低开发成本,提高系统的复用性,这便是所谓的SOA(Service-oriented Architecture)。


Behind The Cloud--浅析分布式系统背后的基础设施

 

图5 基于rpc的SOA模型

服务的提供者provider动态的将服务注册到注册服务器registry,服务的消费者consumer通过查询注册服务器registry,了解需要调用的服务的具体的位置,然后发起远程调用,监视器monitor则动态的监控服务调用的响应时间以及调用频率,以及consumer和provider的负载情况,保证服务质量。

服务消费者通过远程调用来消费服务提供者所提供的服务,远程调用的方式可以通过多种方式来实现,如http,socket(包括NIO,mina,netty),rmi等等,参数通过序列化后传递到远端执行,并将执行后的结果返回。一个简单的服务暴露与引用机制的实现通常需包含exprot和refer两个方法,export方法让provider能够暴露服务的引用,而refer方法使consumer能够引用到服务,笔者的博客有一个rpc的简单实现可供参考,此处便不再赘述。

当服务开始不断增多,依赖关系开始变得错综复杂时,需要有一套健全的机制来对服务进行治理。当服务暴露的地址越来越多的时候,单靠人工来管理和维护越来越困难,必须得有一个统一的集中的地方,来进行统一的维护,实现服务的动态注册以及发现。规模扩大以后,单台服务器根本无法承载大量访问请求和调用,因此集群负载均衡以及failover也是不得不考虑的问题。作为服务的提供方,需要对服务的调用进行预先的容量的规划,拥有适当的监控机制,能够监控到服务每天的调用量以及响应时间,在达到压测值之前进行容量的扩充,并且对服务采取分级制度,保证核心服务的稳定,这样才能够有效避免局部故障导致整个系统的不可用。而某些敏感的服务,并不是所有应用都能够调用的,如用户的个人信息,身份证号码等等,因此服务的授权、令牌的管理也需要考虑。

 

4.海量数据处理

海量数据(Big Data)是指那些足够大的数据,以至于无法使用传统的方法来进行处理。这在过去一直是搜索引擎构建者所面临的首要问题,而如今,各种社交网站,智能移动终端,电子商务平台,每天产生的PB级的数据。为了应对大规模数据处理的挑战,google创造了mapreduce计算模型。

而Hadoop下的mapreduce项目作为google mapreduce的开源实现,已在国内外各大公司中得到广泛使用。简单的说,mapreduce就是一个任务的分解和结果的汇总的过程,它被抽象成map和reduce两个函数,map负责任务的分解,reduce负责把分解后多个任务处理的结果进行汇总。至于在并行编程中其他的各种复杂的问题,如job调度,通信,容错等等,均由mapreduce框架来负责处理。


Behind The Cloud--浅析分布式系统背后的基础设施

 

图6mapreduce计算模型

Mapreduce一个任务的运行需要由JobTracker和TaskTracker两类控制节点的配合来完成,JobTracker将Mappers和Reducers分配给空闲的TaskTracker后,由TaskTracker来执行这些任务。Mapreduce框架尽量的在那些存储数据的节点(如DataNode)上来执行计算任务,采用移动计算而非移动数据的思想,减少数据在网络中的传输,以此提高计算的效率。同时JobTracker也负责任务的容错管理,如果某个TaskTracker发生故障,JobTracker会重新进行任务调度。当然,JobTracker在集群中的单点问题,也不得不考虑。


Behind The Cloud--浅析分布式系统背后的基础设施

 

图7 mapreduce任务调度

相对于构建在GPU上的并行计算架构,如nVIDIA推出CUDA和ATI的Stream技术,Mapreduce在更高的层面定义了分布式环境下的并行计算的概念。传统的计算密集型任务,数据传输的时间几乎可以忽略不计,而Mapreduce应对的场景是分布式环境下的海量数据,它的长处在于充分利用了本地计算的优势,采用移动计算而非数据的思想,可以说mapreduce是为数据而生。

 

 

5.集群负载均衡

当现有网络的各个核心部分承载的业务量不断提高,访问量及数据流量快速增长,其处理能力和计算强度也会随之相应地增大,依靠单一的服务器设备很难承受如此压力。在网络的水平扩展过程中,负载均衡是一种常用的手段。


Behind The Cloud--浅析分布式系统背后的基础设施

 

图8某CDN节点负载均衡结构

专用的负载均衡设备,如F5 BIG-IP、Citrix NetScaler等等,这些产品往往价格不菲,在预算有限的情况下,软件负载均衡的解决方案虽然跟前者比起来性能稍逊且受到某些限制,但也能够满足正常需求。

软件实现负载均衡方式有多种,可以从OSI模型的第二层,第三层,第四层以及第七层来实现,涵盖了DNAT,直接路由,IP隧道,HTTP反向代理等方式。

以LVS为例,它是由中国开源社区的先驱人物章文嵩博士所开发,目前它的模块已经内置到linux系统内核。LVS是通过linux系统内核改变数据包内容的惊人能力,来构建的一个强大的负载均衡调度器。并且它还实现了很多常用的负载均衡调度策略,如轮询调度、加权轮询调度、最小连接调度、源地址Hash等等。

而以nginx、apache等为代表的则是应用层的基于反向代理的负载均衡调度器,他们既可以作为web服务器来提供服务,也可以作为负载均衡调度器来实现后端应用服务器的负载均衡。HAProxy稍显特殊,它既可以工作在OSI模型的传输层,也可以工作在应用层,既支持TCP协议,又支持HTTP协议。

相比较而言,LVS的抗负载能力更强,性能更稳定,支持的协议更丰富,而nginx、apache、HAProxy等负载均衡器的可配置性更强,对于HTTP协议的支持更为友好。

 

6.分布式cache

高并发环境下,大量的读写请求涌向DB,从减轻DB压力和提高响应速度两个角度来考虑,一般均会在DB前面加一层缓存,而单台缓存性能有限,且如果大量使用本地缓存,可能会导致相同的数据被不同的节点存储多份,对内存资源造成较大的浪费,因此,分布式cache 必不可少。

 

Behind The Cloud--浅析分布式系统背后的基础设施

 

图9memcache构建的cache集群

当有N台cache服务器,如何将请求映射到cache服务器呢?对于分布式系统来说,集群的扩展和容错几乎是常态。常用的hash算法如hash(key)%N,可以将请求均匀的映射到每台cache服务器,看起来一切正常,一旦某台cache服务器down掉,或者集群压力过大,需要新增新的cache服务器,所有的key将会重新分布,而这将会演变成一场灾难,所有的请求将如洪水般疯狂的涌向后端的数据库服务器,而后端数据库服务器的不可用,将会导致整个应用的不可用,形成所谓的雪崩效应。

consistent hash解决了这个问题,consistent hash算法早在1997年就在论文Consistent hashing and random trees中被提出,它能够在移除/添加一台cache时,尽可能小的改变已存在的key映射关系,避免大量key的重新映射。

基于memcache构建的cache集群,结构简单,可扩展性高,大部分场景均能够适用,在业界也得到了较为广泛的认可。一般认为,存入cache的数据均是可以丢失的,并且可以从DB中进行还原,因此,跟其他分布式系统不同,memcache本身并没有一个有效的冗余和容错机制。

在大多数情况下,一两台cache宕机所导致的内存中数据丢失,并不会产生很大的影响。但是,由于cache的内存操作和DB的磁盘物理操作所花费的代价不在一个数量级, cache集群的抗压能力远非DB能比,很多系统设计时往往出于成本等因素的考虑,需要保持较高级别的缓存命中率,来保证对DB的访问请求在可控范围,否则,没有cache的掩护,DB无法独立承担压力。在这种情况下,cache的冗余和容错机制,便成为一个不得不考虑的问题。

针对于以上情况,淘宝开源的分布式缓存系统tair[3]提供了相应的解决方案。它包含主控节点config server和数据节点data node,config server负责管理所有的data server,并维护data server的状态信息,而data server对外提供各种数据服务,并向config server发送心跳。Configserver会对数据的冗余备份,并且可根据配置进行自动均衡,当某台data server不可用,config server会重新分配该data server上的请求,当新的data server增加进来的时候,config server会协调其他data server将一部分数据进行迁移。tair完善了cache的冗余复制以及集群变更后数据迁移的机制。

 

7.分布式数据库

Google所提出的bigtable,为分布式存储大规模的结构化数据提供了一种可靠的解决方案。Bigtable的开源实现包括HBase,Hypertable,Cassandra等等。以HBase为例,它以表的形式存储数据,每个表由行列组成,每个列属于一个特定的列族,行和列确定一个存储单元,而每个存储单元又可以有多个版本,通过时间戳来标识。HBase集群中通常包含两种角色,HMaster和HRegionServer,当表随着记录条数的增加而不断变大后,将会分裂成一个个region,每个region可以由[startkey,endkey)来表示,它包含一个startkey到endkey的半闭区间。一个HRegionServer可以管理多个region,并由HMaster来负责HRegionServer的调度以及集群状态的监管。由于region可分散由不同的HRegionServer来管理,因此理论上再大的表都可以通过集群来处理,当然,仅仅是理论上,集群的规模不可能无限大,它也会受到-ROOT-表的制约。


Behind The Cloud--浅析分布式系统背后的基础设施

 

图11 HBase集群部署图

类似Bigtable的这类分布式数据库虽然优势明显,但也并非所有场景都能够适用。以HBase为例,它的列可以动态增加,对于需求经常变动的应用来说,无疑是个极大的福音。能够自动的切分数据而不需要人工干预,降低运维成本。天然的能够提供高并发条件下的读写支持。但是它缺乏对传统的RDBMS的大多数特性的支持,如外键,事务,模糊查询,order by语句等等,因此只能适用于结构相对简单的数据模型。这类分布式数据库与传统的RDBMS更多的是一种相互补充的关系,而非相互替代, RDBMS适合于精细型数据,而分布式数据库则更加粗犷。

 

9.分布式消息队列

消息可以当做应用之间的一种通信方式,也可以看成是应用集成的一种方式,分布式系统架构的过程中,通过消息队列的引入,能够有效的降低各模块间的耦合性,提升分布式协作的效率。消息的发送者将消息发送后可以立即返回,不用等待接收者响应,消息被保存在队列中,直到被接收者取出。并且,当系统处于峰值压力下时,分布式消息队列可以作为前置缓冲,来缓解集群的压力,避免整个系统被压垮。

分布式消息的传输模式一般包括点对点消息传送(P2P)和订阅发布(Pub/Sub)两种模式,如图所示:


Behind The Cloud--浅析分布式系统背后的基础设施

 

图12 点对点消息模型


Behind The Cloud--浅析分布式系统背后的基础设施

 

 

图13订阅发布模型

在分布式系统环境下,消息从发送到接收,经历过的中间环节非常多,任何一个环节出错,均有可能会导致消息的丢失,而对所有的消息均采用持久化处理,又会导致的性能的成本升高,因此,对消息进行分级是有必要的。既允许一部分消息丢失,来换取高吞吐率,对于不可丢失的消息,先做持久化后再进行返回。某些情况下,多条消息可能需要同时发送成功,而这些消息的接收者可能是不同的对象,这种情况下,则需要使用XA事务来进行保障。多线程高并发条件下,消息发送的顺序性很难保障,并且,由于网络的缘故,可能消息接收方明明收到消息,而在回复确认的时候网络闪断,则接收方有可能收到重复的消息,因此,既要保证消息不重发又要保证消息的安全是非常困难的,所以我们在可能接收到重复消息的情况下,必须保证所设计系统的幂等性。

常见开源的消息队列产品包括apache的ActiveMQ,spring的RabbitMQ,memcacheQ以及淘宝的metamorphosis等等,以淘宝的metamorphosis[4]为例,它支持broker以Master/Slaver的方式实现HA(High Availability)和Failover,Master与Slaver通过Msg log来实现同步,避免了broker的单点问题。

 

 

小结

本文对分布式系统架构所涉及的一些基础设施做了简要介绍,讨论了构建一个健壮的分布式系统所涉及到的众多领域,并分析了他们的设计思想及原理。一个成熟的分布式系统通常内置冗余设计,通过使用大量普通的机器集群来替代高性能服务器,以此节约成本,并保障系统的高可用性,而CAP理论则告诉大家,一致性、可用性、分区容错性这三者无法同时兼备,因此,我们在设计时必须得有所取舍。

由于分布式系统本身的复杂性,牵涉范围广泛,因此难免挂一漏万,一个可靠的完整的分布式系统架构可能还涉及到硬件虚拟化,数据仓库,搜索,集群监控,智能运维等等领域,限于篇幅,笔者此处便不再赘述。

 

 

 

参考文献

[1] TFShttp://code.taobao.org/p/tfs/wiki/index/

[2] http://www.infoq.com/cn/articles/tao-tfs

[3] tair http://code.taobao.org/p/tair/wiki/index/

[4] metamorphosis http://code.taobao.org/p/metamorphosis/wiki/index/

相关推荐