1.2 分布式设计目标
为了解决单机系统无法解决的问题,我们需要设计分布式系统。对于什么才是分布式系统设计的目标,不同人有着自己对分布式系统理解下的目标定义。Tanenbaum和Van Steen等人将连接用户和资源、透明度、开放性、可扩展性列为分布式系统的目标;Coulouris、Dollimore和Kindberg等人将异质性、开放性、安全、可扩展性、故障处理、并发、透明度列为分布式系统的设计目标和挑战。在本书中,我们则从分布式数据服务的角度来看待如何进行分布式系统的设计,我们认为分布式系统的目标包括以下四点。
●一致性
●可用性
●分区容错性
●可扩展性
在本节中,我们将对这四个目标进行概括性介绍。
1.2.1 一致性
为了更直观地理解什么是一致性,我们先想象一个简单的场景:假设有一份数据需要长期存储而且要确保不丢失,需要怎么处理?一个很自然的想法是准备多个硬盘,在不同硬盘上多备份几个副本。在现实中,实现多个副本的操作显然是容易的——虽然硬盘之间是孤立的,不能互相通信,但是备份数据是静态的,初始化后状态就不会发生改变,由人工进行的文件复制操作,很容易就可以保障数据在各个备份盘中的一致性。然而在分布式系统中面对数据服务的多样性,我们必须考虑动态的数据如何在不可靠的网络通信条件下,依然能够在各个节点之间正确复制的问题。
我们延伸一下场景:假设我们拥有一份随时会变动的数据,需要确保它正确地存储于分布式网络中的几台不同计算机上,要怎么处理?这样的场景在大型单体机上是不曾遇到的。在分布式系统中,机器的读写操作由于物理限制(地理位置、网络传输)无法同时到达不同机器的数据源中。此外,分布式系统中机器的故障与机器的轮换是常见的情况。我们需要在分布式系统中专门由一致性算法去保证复制的顺利进行。
对于分布式系统而言,一致性是在探讨当系统内的一份逻辑数据存在多个物理的数据副本时,对其执行读写操作会产生什么样的结果。此处我们提到的“读写”操作与上述例子中“数据会变动”是相近的。事实上,讨论数据一致性的前提,就是同时存在读操作和写操作,否则是没有意义的。把两个因素加在一起,就是多副本数据上的一组读写策略,被称为一致性模型(consistency model)。本节将简略地介绍数据同步的概念、数据一致性的级别、列举常见的数据一致性算法。在后续章节中,我们会对这些概念进行详细讲解。
我们在谈论数据同步时,通常将同步方式分为两类:异步与同步。基于这两类数据同步方式,下述5点是常用的数据同步机制。
●Backup,即定期备份,对现有系统的性能基本没有影响,但节点宕机时只能勉强恢复。
●Master-Slave,即主从复制,异步复制每个指令,可以看作是更细粒度的定期备份。
●Multi-Master,即多主复制,也称主主复制,Master-Slave的加强版,可以在多个节点上写,事后再想办法同步。
●2 Phase Commit,即两阶段提交,同步先确保通知到所有节点再写入,性能瓶颈很容易出现在“主”节点上。
●Paxos,类似2PC,同一时刻有多个节点可以写入,也只需要通知到大多数节点,有更高的吞吐量。
以同步为代表的数据复制方法,被称为状态转移(state transfer),是较符合人类行为的可靠性保障手段,但通常要以牺牲可用性为代价,实现难度较高、性能较差。我们在构建分布式系统的时候,往往不能承受这些代价,一些关键的系统在必须保障数据正确可靠的前提下,也对可用性有着非常高的要求,譬如系统要保证数据达到99.999999%的可靠性,同时系统自身也要达到99.999%可用的程度。为缓解当分布式系统内机器增加带来的可靠与可用的矛盾,目前分布式系统中主流的复制方法是以操作转移(operation transfer)为基础的。操作转移的核心思想是:让多台机器的最终状态一致。只要确保它们的初始状态是一致的,并且接收到的操作指令序列也是一致的即可,无论这个操作指令是新增、修改、删除或其他任何可能的程序行为,都可以理解为将一连串的操作日志正确地广播给各个分布式节点。图1-1是对数据同步机制的总结。
有了上述的铺垫,我们再来思考什么是一致性。实际上,保证一致性就是指在分布式系统中多个节点之间不能产生矛盾。一致性模型的数量很多,让人难以分辨。为了便于读者理解,我们暂且将一致性分为以下两类,在后续章节中,我们还会对这些分类进行细化。
图1-1 数据同步机制的总结
●强一致性:在任何时刻所有的用户或者进程查询到的都是最近一次成功更新的数据,要求更新过的数据都能被后续的访问看到。
●弱一致性:在某一时刻用户或者进程查询到的数据可能都不同,但是最终成功更新的数据都会被所有用户或者进程查询到。
读者应该注意,我们目前提及的一致性与读者常见的分布式共识是有区别的,一致性是指数据不同副本之间的差异,而共识是指达成一致性的方法和过程。我们在后续的章节中会深入讲解二者之间的区别。
在被设计为强一致性级别的分布式系统中,我们不得不提到Paxos算法,它是如此重要,以至于谷歌 Chubby 服务的发明者 Mike Burrows曾说:“There is only one consensus protocol, and that is Paxos.All other approaches are just broken versions of Paxos.”这句话的大意为其他一致性共识算法只是Paxos的变体。这句话或许有些夸大的成分,然而列举强一致性算法,我们确实看到常见的Raft算法、ZAB算法皆可被认为是Paxos的变体,在后续章节中会依次详细介绍这些算法。
在分布式系统中,除强一致性外,其余的一致性都可以被称为弱一致性。考虑到在分布式环境下网络分区现象是不可能消除的,数据同步必须进行权衡。人们在设计分布式系统时,不再追求系统内所有节点在任何情况下的数据状态都一致,而是采用“轻过程,重结果”的原则,使数据达到最终一致性即可。一个经典的弱一致性算法是Quorum算法,这是一种用来保证数据冗余和最终一致性的投票算法,我们在后续章节中会展开讲解。
1.2.2 可用性
在详细介绍可用性之前,我们在此先正式介绍著名的CAP理论以贯穿一致性、可用性、分区容错性三个章节。CAP理论由加州大学伯克利分校的计算机教授Eric Brewer在2000年提出,其核心思想是任何基于网络的数据共享系统最多只能满足数据一致性(consistency)、可用性(availability)和分区容错性(partition tolerance)三个特性中的两个,三个特性的定义如下。
●数据一致性:等同于所有节点拥有数据的最新版本。注意:CAP理论中的一致性强调的是在任何时刻、任何分布式节点中所看到的数据都是符合预期的,是一种强一致性。
●可用性:数据具备高可用性。分布式系统不会因为系统中的某台机器故障而直接停止服务,某台机器故障后,系统可以快速切换流量到正常的机器上,继续提供服务。
●分区容错性:容忍网络出现分区,分区之间网络不可达时,系统仍能正确地提供服务的能力。
CAP对三者的关系可以概述为图1-2,CAP理论告诉我们:我们可以得到CA、CP、AP的系统,却无法得到一个三者同时兼顾的系统。
图1-2 CAP理论图示
CAP理论似乎给分布式系统定义了一个悲观的结局,我们想在此说明一个大家视CAP理论为金科玉律带来的错误认识:长期以来大家按照CAP理论对热门的分布式系统进行分类判断是不够准确的。人们承认在大规模的分布式环境下,网络分区是必须容忍的现实,于是只能在可用性和一致性两者之间做出选择,譬如人们认为HBase是CP系统,Cassandra是AP系统。然而,CAP理论是对分布式系统中一个数据无法同时达到可用性和一致性的断言。目前的分布式系统中,往往存在多种类型的数据,C与A之间的取舍可以在同一系统内以非常细小的粒度反复发生,而每一次的决策可能因为具体的操作,甚至因为涉及特定的数据或用户而有所不同。通过数据类型的多样化以及在优化数据一致性和可用性之间的平衡措施,我们可以看到Cassandra在牺牲一定性能的情况下是可以保证弱一致性的,这使得单从CAP的角度来划定分布式系统是不严谨的。
可用性代表系统不间断地提供服务的能力。理解可用性要先理解与其密切相关的两个指标:可靠性(reliability)和可维护性(maintainability)。可靠性使用平均无故障时间(Mean Time Between Failure, MTBF)来度量;可维护性使用平均可修复时间(Mean Time To Repair, MTTR)来度量。衡量系统可以正常使用的时间与总时间之比,其度量公式为:
Availability=MTBF/(MTBF+MTTR)
即可用性是由可靠性和可维护性之比得到的。可用性通常有“6个9”的说法(99.9999%),即代表年平均故障修复时间为32秒。
可用性的实现不同于一致性有着许多经过数学证明后的理论,在分布式系统中大多遵循一些实用的设计理念来实现可用性。首先是进行冗余设计,在分布式系统中单点故障不可避免,而减少单点故障的不二法门就是冗余设计。通过将节点部署在不同的物理位置,避免单机房中多点同时失败。目前常见的冗余设计有主从设计和对等治理设计,主从设计又可以细分为一主多从设计和多主多从设计。在冗余设计之后,还可以结合分布式系统自身实例的负载能力,对服务端进行限流设计。一般来说,限流设计可以从拒绝服务、服务降级、优先级请求、弹性伸缩等方面进行设计。具体的办法是:在流量暴增时,系统会暂时拒绝周期时间内请求数量最大的客户端;也可通过牺牲数据强一致性的方式来获得更大的性能吞吐;将目前系统的资源分配给优先级更高的用户;建立性能监控系统,感知目前最繁忙的服务,并自动伸缩。
1.2.3 分区容错性
分布式系统一般以网络为桥梁将多个机器进行互联,网络不是永远可靠的,网络出现故障会导致一个或多个机器内部出现网络分区,我们设计的分布式系统要能够正确应对网络分区,在分区的情况下还能保证数据的一致性。
如图1-3所示,在分区的情况下,一个数据只在一个分区中的节点保存,与这个分区不连通的部分访问不到这个数据,这时分区就是无法容忍的。提高分区容错性的办法就是将一个数据项复制到多个节点上,那么出现分区之后,这一数据项就可能分布到各个分区里。然而,要把数据复制到多个节点,就会带来一致性的问题,即多个节点上的数据可能是不一致的。要保证数据一致,每次写操作就都要等待全部节点写成功,而等待又会带来可用性的问题。总的来说,数据存在的节点越多,分区容错性越高,但要复制更新的数据就越多,一致性就越难保证。为了保证一致性,更新所有节点数据所需要的时间就会变长,可用性就会降低。
图1-3 服务器之间的分区
随着技术的发展,人们发现即使网络出现故障,可能出现分区,实际上也可以正确地实现能够自动完成故障切换的系统。在构建能自动恢复同时又避免脑裂的多副本系统时,人们发现,关键点在于过半票决(majority vote),这是Raft论文中出现的用来构建Raft的一个基本概念。
如图1-4所示,过半票决系统的第一步在于,服务器的数量应是奇数,而不是偶数。如果服务器的数量是奇数,那么当出现一个网络分割时,两个网络分区将不再对称。假设出现了一个网络分割,那么一个分区会有两个服务器,另一个分区只会有一个服务器,这样就不再对称了。这是过半票决吸引人的地方。所以,首先要有奇数个服务器。然后为了完成任何操作,例如Raft的Leader选举、提交一个Log条目,你必须凑够过半的服务器来批准相应的操作。这里的过半是指超过服务器总数的一半。直观来看,如果有3个服务器,那么需要2个服务器批准才能完成任何的操作。换个角度来看,这个系统可以接受1个服务器的故障,任意2个服务器都足以完成操作。如果你需要构建一个更加可靠的系统,那么你可以为系统加入更多的服务器。
图1-4 分区容错
1.2.4 可扩展性
我们设计的分布式系统要具有可扩展性,这里的可扩展其实就是指我们可以通过使用更多的机器来获取更高的系统总吞吐量以及更好的性能,当然也不是机器越多性能越好,针对一些复杂的计算场景,节点越多性能并不一定会更好。
如图1-5(左图)所示,当一台服务器被多人同时访问时,其性能可能会降低,我们可以通过增加服务器的配置来提升性能,这称为垂直可扩展性。但是,一台高性能的服务器造价昂贵,而且当这台服务器发生故障时,会导致整个系统都无法访问。可扩展性是一个很强大的特性。如图1-5(右图)所示,如果你构建了一个系统,并且只要增加计算机的数量,系统就能相应提高性能或者吞吐量,这将会是一个巨大的成果,因为只需要花钱就可以买到计算机,这称为水平可扩展性。所以,当人们使用一整个机房的计算机来构建大型网站的时候,为了获取对应的性能,必须要时刻考虑可扩展性。
图1-5 可扩展性
总的来说,可扩展性具有处理日益变化的需求的能力,可以添加资源并向上扩展,以无缝处理增加的客户需求和更大的工作负载;当需求和工作量减少时,可以无缝地移除资源以节约服务器资源。与此同时,可扩展性可在一定程度上提升系统的可用性,保证在计划外中断期间提供对数据库资源的持续访问。计划外中断是指意外的系统故障,例如电源中断、网络中断、硬件故障、操作系统或其他软件错误。此外,可扩展性会带来一致性和容错性的问题,因此,也不是机器越多性能越好。