异常分类
分布式存储系统所关注的异常类型和单体系统不一样,有如下几种:
- 服务器宕机:设计存储系统时需要考虑如何通过读取持久化介质(如机械硬盘,固态硬盘)中的数据来恢复内存信息。
- 网络异常:设计容错系统的一个基本原则是:网络永远是不可靠的,任何一个消息只有收到对方的回复后才可以认为发送成功,系统设计时总是假设网络将会出现异常并采取相应的处理措施。
- 磁盘故障:对于磁盘数据错误,往往可以采用校验和( checksum)机制来解决。
除了异常之外,还存在“超时”状态,RPC执行的结果有三种状态:“成功”、“失败”、“超时”(未知状态),也称为分布式存储系统的三态。
一致性保证
副本是分布式存储系统容错技术的唯一手段。由于多个副本的存在,如何保证副本之间的一致性是整个分布式系统的理论核心。从客户端的角度来看,一致性包含如下三种情况:
1、强一致性:
2、弱一致性:
3、最终一致性:最终一致性是弱一致性的一种特例。“最终”一致性有一个“不一致窗口”(时间延迟),最终一致性描述比较粗略,其常见的变体如下:
- 读写( Read-your-writes)-致性
- 会话( Session)-致性
- 单调读( Monotonic read)-致性
- 单调写( Monotonic write)-致性
从存储系统的角度看,一致性主要包含如下几个方面:
1) 副本一致性:存储系统的多个副本之间的数据是否一致,不一致的时间窗口等;
2) 更新顺序一致性:存储系统的多个副本之间是否按照相同的顺序执行更新操作。
衡量指标
评价分布式存储系统有一些常用的指标:
性能:吞吐能力(QPS、TPS)、响应延迟。
可用性:系统的可用性可以用系统停服务的时间与正常服务的时间的比例来衡量。
一致性:越是强的一致性模型,用户使用起来越简单。如果系统部署在同一个数据中心,只要系统设计合理,在保证强一致性的前提下,不会对性能和可用性造成太大的影响。
可扩展性:系统的可扩展性( scalability)指分布式存储系统通过扩展集群服务器规模来提高系统存储容量、计算量和性能的能力。
性能分析
一般来说,对分布式系统的性能分析的结果是不精确的,然而,至少可以保证,估算的结果与实际值不会相差一个数量级。举个例子,Google的BigTable中随机写和顺序写的性能是差不多的,写入操作需要首先将操作日志写入到GFS,接着修改本地内存。为了提高性能,BigTable实现了成组提示技术。
只有理解存储系统的底层设计和实现,并在实践中不断地练习,性能估算才会越来越准。
数据分布
1、哈希分布(代表:Dynomo):如果哈希函数的散列特性很好,哈希方式可以将数据比较均匀地分布到集群中去。然而,找出一个散列特性很好的哈希函数是很难的。这是因为,如果按照主键散列,那么同一个用户id下的数据可能被分散到多台服务器,这会使得一次操作同一个用户id下的多条记录变得困难;如果按照用户id散列,容易出现“数据倾斜”(data skew)问题,即某些大用户的数据量很大,无论集群的规模有多大,这些用户始终由一台服务器处理。另一种思路就是采用一致性哈希( Distributed Hash Table,DHT)算法(顺时针查找)。一致性哈希的优点在于节点加入/删除时只会影响到在哈希环中相邻的节点,而对其他节点没影响。一致性哈希算法在很大程度上避免了数据迁移。Dynamo系统通过牺牲空间换时间,在每台服务器维护整个集群中所有服务器的位置信息,将查找服务器的时间复杂度降为O(l)。一致性哈希还需要考虑负载均衡,比较好的做法是引入“虚拟节点”的概念。
2、顺序分布(代表:BigTable):哈希散列破坏了数据的有序性,只支持随机读取操作,不能够支持顺序扫描。顺序分布在分布式表格系统中比较常见,一般的做法是将大表顺序划分为连续的范围,每个范围称为一个子表。Bigtable将一张大表根据主键切分为有序的范围,每个有序范围是一个子表。为了支持更大的集群规模,Bigtable这样的系统将索引分为两级:根表以及元数据表(Meta表),由Meta表维护User表的位置信息。顺序分布与B+树数据结构比较类似,每个子表相当于叶子节点,随着数据的插入和删除,某些子表可能变得很大,某些变得很小,数据分布不均匀。如果采用顺序分布,系统设计时需要考虑子表的分裂与合并。子表合并的目的是为了防止系统中出现过多太小的子表,减少系统中的元数据。
3、负载均衡:工作节点通过心跳包(Heartbeat,定时发送)将节点负载相关的信息,如CPU,内存,磁盘,网络等资源使用率,读写次数及读写数据量等发送给主控节点。负载均衡操作需要控制节奏,负载均衡操作需要做到比较平滑,一般来说,从新机器加入,到集群负载达到比较均衡的状态需要较长一段时间,比如30分钟到一个小时。
复制
复制协议分为两种:强同步复制和异步复制,如图1。
图1 主备复制协议示范
强同步复制和异步复制都是将主副本的数据以某种形式发送到其他副本,这种复制协议称为基于主副本的复制协议( Primary-based protocol)。这种方法要求在任何时刻只能有一个副本为主副本,由它来确定写操作之间的顺序。如果主副本出现故障,需要选举一个备副本成为新的主副本,这步操作称为选举,经典的选举协议为Paxos协议。
主备副本之间的复制一般通过操作日志来实现。操作日志的原理很简单:为了利用好磁盘的顺序读写特性,将客户端的写操作先顺序写入到磁盘中,然后应用到内存中,由于内存是随机读写设备,可以很容易通过各种数据结构,比如B+树将数据有效地组织起来。当服务器宕机重启时,只需要回放操作日志就可以恢复内存状态。为了提高系统的并发能力,系统会积攒一定的操作日志再批量写入到磁盘中,这种技术一般称为成组提交。
如果每次服务器出现故障都需要回放所有的操作日志,效率是无法忍受的,检查点( CheckPoint)正是为了解决这个问题。系统定期将内存状态以检查点文件的形式dump到磁盘中,并记录检查点时刻对应的操作日志回放点。检查点文件成功创建后,回放点之前的日志可以被垃圾回收,以后如果服务器出现故障,只需要回放检查点之后的操作日志。(内存中只有“检查点”之后的数据)
分布式存储系统要求能够自动容错,也就是说,CAP理论中的“分区可容忍性”总是需要满足的,因此,一致性和写操作的可用性不能同时满足。例如,Oracle教据库的DataGuard复制组件包含三种模式:
最大保护模式( Maximum Protection):即强同步复制模式,写操作要求主库先将操作日志(数据库的redo/undo日志)同步到至少一个备库才可以返回客户端成功。
最大性能模式( Maximum Performance):即异步复制模式,写操作只需要在主库上执行成功就可以返回客户端成功。
最大可用性模式( Maximum Availability):上述两种模式的折衷。
容错
常见故障中,单机故障和磁盘故障发生概率最高。在分布式系统中,可以通过租约(Lease)机制进行故障检测。
租约机制就是带有超时时间的一种授权。假设机器A需要检测机器B是否发生故障,机器A可以给机器B发放租约,机器B持有的租约在有效期内才允许提供服务,否则主动停止服务。需要注意的是,实现租约机制时需要考虑一个提前量。
故障恢复中,总控节点一般需要等待一段时间,比如1个小时,如果之前下线的节点重新上线,可以认为是临时性故障,否则,认为是永久性故障。停服务时间包含两个部分,故障检测时间以及故障恢复时间。故障检测时间一般在几秒到十几秒,和集群规模密切相关,集群规模越大,故障检测对总控节点造成的压力就越大,故障检测时间就越长。
图2 故障恢复
可扩展性
分布式存储系统大多都带有总控节点,基于这点,很多人会自然地认为总控节点存在瓶颈问题,认为去中心化的P2P架构更有优势。然而,事实却并非如此,主流的分布式存储系统大多带有总控节点,且能够支持成千上万台的集群规模。可扩展性应该综合考虑节点故障后的恢复时间,扩容的自动化程度,扩容的灵活性等。
分布式存储系统中往往有一个总控节点用于维护数据分布信息,执行工作机管理,数据定位,故障检测和恢复,负载均衡等全局调度工作。通过引入总控节点,可以使得系统的设计更加简单,并且更加容易做到强一致性,对用户友好。那么,总控节点是否会成为性能瓶颈呢?如果总控节点成为瓶颈,例如需要支持超过一万台的集群规模,或者需要支持海量的小文件,那么,可以采用两级结构,如图3所示。
图3 两级元数据结构
在数据库的扩容中,如果系统的读取能力不足,可以通过增加副本的方式解决,如果系统的写入能力不足,可以根据业务的特点重新拆分数据,常见的做法为双倍扩容,即将每个分片的数据拆分为两个分片,扩容的过程中需要迁移一半的数据到新加入的存储节点。传统的数据库架构在可扩展性上面临如下问题:
1. 扩容不够灵活。
2. 扩容不够自动化。
3. 增加副本时间长。
同一个组内的节点服务相同的数据,这样的系统称为同构系统。同构系统的问题在于增加副本需要迁移的数据量太大,由于拷贝数据的过程中存储节点再次发生故障的概率很高,所以这样的架构很难做到自动化,不适用大规模分布式存储系统。而异构系统将数据划分为很多大小接近的分片,每个分片的多个副本可以分布到集群中的任何一个存储节点。由于整个集群都参与到节点的故障恢复过程,故障恢复时间很短,而且集群规模越大,优势就会越明显。
图4 同构系统和异构系统的区别
分布式协议
在计算机世界里,为了解决一件事情,另外的问题就会接踵而至,从另一个层面印证了IT架构永远是一种平衡的艺术。
“BASE”其核心思想是根据业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency);在互联网领域,通常需要牺牲强一致性来换取系统的高可用性,只需要保证数据的“最终一致”,只是这个最终时间需要在用户可以接受的范围内;但在金融相关的交易领域,仍然需要采用强一致性的方式来保障交易的准确性与可靠性。
业界常见的事务处理模式很多,包括两阶段提交、三阶段提交、Sagas长事务、补偿模式、可靠事件模式(本地事件表、外部事件表)、可靠事件模式(非事务消息、事务消息)、TCC、Paxos及其相关变种等等。不同的事务模型支持不同的数据一致性,这里我们不对每种协议都展开详细讨论,在分布式存储方面,用的最多的是两阶段提交协议及Paxos协议,以下重点介绍这两种协议。
1、两阶段提交协议( Two-phase Commit,2PC):经常用来实现分布式事务,在两阶段协议中,系统一般包含两类节点:一类为协调者( coordinator),通常一个系统中只有一个;另一类为事务参与者(participants,cohorts或workers),一般包含多个。
协议中假设每个节点都会记录操作日志并持久化到非易失性存储介质,即使节点发生故障日志也不会丢失。在提交阶段,协调者将基于第一个阶段的投票结果进行决策:提交或者取消。并通过引入事务的超时机制防止资源一直不能释放的情况。
两阶段提交协议可能面临两种故障:
事务参与者发生故障。给每个事务设置一个超时时间,如果某个事务参与者一直不响应,到达超时时间后整个事务失败。
协调者发生故障。协调者需要将事务相关信息记录到操作日志并同步到备用协调者,假如协调者发生故障,备用协调者可以接替它完成后续的工作。如果没有备用协调者,协调者又发生了永久性故障,事务参与者将无法完成事务而一直等待下去。
总而言之,两阶段提交协议是阻塞协议。
2、Paxos协议:用于解决多个节点之间的一致性问题。只要保证了多个节点之间操作日志的一致性,就能够在这些节点上构建高可用的全局服务,例如分布式锁服务,全局命名和配置服务等。为了实现高可用性,主节点往往将数据以操作日志的形式同步到备节点。如果主节点发生故障,备节点会提议自己成为主节点。网络分区的时候,可能会存在多个备节点提议(Proposer,提议者)自己成为主节点。Paxos协议保证,即使同时存在多个proposer,也能够保证所有节点最终达成一致,即选举出唯一的主节点。
(3)Paxos与2PC的区别:Paxos协议和2PC协议在分布式系统中所起的作用并不相同。Paxos协议用于保证同一个数据分片的多个副本之间的数据一致性。当这些副本分布到不同的数据中心时,这个需求尤其强烈。2PC协议用于保证属于多个数据分片上的操作的原子性。这些数据分片可能分布在不同的服务器上,2PC协议保证多台眼务器上的操作要么全部成功,要么全部失败。
Paxos协议有两种用法:一种用法是用它来实现全局的锁服务或者命名和配置服务,例如Google Chubby以及Apache Zookeeper。另外一种用法是用它来将用户数据复制到多个数据中心,例如Google Megastore以及Google Spanner。
2PC协议最大的缺陷在于无法处理协调者宕机问题。如果协调者宕机,那么,2PC协议中的每个参与者可能都不知道事务应该提交还是回滚,整个协议被阻塞,执行过程中申请的资源都无法释放。因此,常见的做法是将2PC和Paxos协议结合起来,通过2PC保证多个数据分片上的操作的原子性,通过Paxos协议实现同一个数据分片的多个副本之间的一致性。另外,通过Paxos协议解决2PC协议中协调者宕机问题。当2PC协议中的协调者出现故障时,通过Paxos协议选举出新的协调者继续提供服务。
跨机房部署
机房之间的数据同步方式可能为强同步或者异步。如果采用异步模式,那么,备机房的数据总是落后于主机房;如果采用强同步模式,那么,备机房的数据和主机房保持一致。当主机房出现故障时,除了手工切换,还可以采用自动切换的方式,即通过分布式锁服务检测主机房的服务,当主机房出现故障时,自动将备机房切换为主机房。