【持续更新】分布式领域基本概念

最近尝试接触一些分布式领域相关的知识,以前在学校的时候想当然地认为把程序写好了能跑其它就都不用管了,进了企业才发现根本就不是那回事儿。比如在没有接触服务器之前都是在自己的笔记本上面搭建各种环境,开发、测试、部署都是在笔记本上,运行下觉得逻辑没问题就觉得完事了,但进了企业后经常会碰到些莫名其妙的问题,比如服务器一下子可能就支撑不住了,渐渐地意识到单机的不可靠。现在有同事专门在负责集群这块,自己觉得有必要了解下这方面的知识。刚入门也不知道学什么好,看到些什么就学什么,觉得有必要总结些东西下来,要不这种学法效果会相当糟糕。也欢迎大家推荐些不错的资源或书籍~



高可用性

英文叫high availability,简称HA。wiki

高可用的定义是Ao = up time / total time,up time = (total_time - down time),

即Ao = (total_time  - down_time) / total_time,简单理解就是服务可用的时间占比。

但可用性和up time的含义还不太一样,系统在正常运行时也可能造成不可用的情况,如出现网络故障。

高可用的三个基本原则

1)减少单点故障,当一个系统或组建出现问题时不会影响到整个系统的正常服务,大体可以理解为系统容错性;

2)可靠的迁移?(reliable crossover)不知道crossover是什么含义,我的理解是业务迁移,即当某个节点不可用时,业务可以平滑地迁移到其他节点;

3)错误检测,当系统出错时能及时发现错误,这其实是运维工作的一部分。

可用性一般用百分比来描述,比如3个9(表示可用性为99.9%)、5个9(99.999%),依次类推~

高可用系统设计推崇简单设计原则,当一个系统设计地较为复杂时,出错的可能性相对更高。

业界对于高可用问题一般采用冗余的方式来解决,如为提高数据服务高可用,通常会多复制几份数据做备份,当数据服务不可用时能及时切换到备用数据,即节点的failover。



异常问题

分布式系统核心问题之一就是处理各种异常情况,常见的异常情况有以下三种:

1)机器宕机,在大型集群中每日宕机发生的概率为千分之一左右;

2)网络异常(消息丢失、消息乱序、数据错误、不可靠的TCP);

3)存储数据丢失,节点存储的数据不可被读取或读取出的数据错误。



副本

副本(replica/copy)指在分布式系统中为数据或服务提供的冗余,分为数据副本和服务副本。

对于数据副本指在不同的节点上持久化同一份数据,但出现某一个节点存储的数据丢失时,可以从副本上读到数据。数据副本是分布式系统解决数据丢失异常的唯一手段

服务副本指数个节点提供某种相同的服务。

副本一致性:分布式系统通过副本控制协议,使得从系统外部读取系统内部各个副本的数据在一定的约束条件下相同。

常见的副本一致性级别

1)强一致性(strong consistency):任何时刻任何用户或节点都可以读到最近一次成功更新的副本数据;

2)单调一致性(monotonic consistency):任何时刻任何用户一旦读到某个数据在某次更新后的值,这个用户不会再读到比这个值更旧的值;

3)会话一致性(session consistency):任何用户在某一次会话内一旦读到某个数据在某次更新后的值,这个用户在这次会话过程中不会再读到比这个值更旧的值;

4)最终一致性(eventual consistency):最终一致性要求一旦更新成功,各个副本上的数据最终将达到完全一致的状态,但达到完全一致的状态所需要的时间不能保证;

5)弱一致性(weak consistency):一旦某个更新成功,用户无法在一个确定时间内读到这次更新的值,且即使在某个副本上读到了新的值,也不能保证在其他副本上可以读到新的值。



衡量分布式系统的指标

1)性能:系统的吞吐能力(在某一时间可以处理的数据总量)、响应延迟(完成某一功能所需要时间)、并发能力(可以同时完成某一功能的能力);

2)可用性,衡量了系统的容错性,是系统容错能力的体现;

3)可扩展性:通过扩展集群机器规模提高系统性能(吞吐、延迟、并发)、存储容量、计算能力的特征;

4)一致性:使用副本会引发副本一致性的问题



数据分布方式-如何将问题拆解成可以使用多台机器分布式解决

哈希方式:按照数据某一特征计算哈希值,并将哈希值与机器进行映射,从而达到将不同哈希值的数据分布到不同机器上。优点:要记录的原信息比较简单,根据哈希计算方式可以知道数据被分布在了哪台机器;缺点:可扩展性不高,一旦集群需要扩展,原有的数据需要重新分布,分布的好坏与哈希函数有关。
针对哈希方式扩展性差,一种思路是不再简单的将哈希值与机器做除法取模映射,而是将对应关系作为元数据由专门的元数据服务器管理。
按数据范围分布:将数据按特征值的值域范围划分为不同区间,使得集群中每台(组)服务器处理不同区间的数据。需要记录所有的数据分布情况。优点:可以灵活地根据数据量的具体情况拆分原有数据区间,拆分后的数据区间可以迁移到其他机器,需要扩容时可以随时添加机器;缺点:需要维护较为复杂的元信息。
按数据量分布:与具体的数据特征无关,而是将数据视为一个顺序增长的文件,并将这个文件按照某一较为固定的大小划分为若干数据块(chunk),不同的数据块分布到不同的服务器上,需要记录数据块的具体分布情况。优点:一般没有数据倾斜的问题,通过迁移数据块可以完成负载均衡;缺点:需要维护较复杂的元信息。
一致性哈希:最初在P2P网络中作为分布式哈希表(DHT)的常用数据分布算法。基本方式是使用一个哈希函数计算数据或数据特征的哈希值,令该哈希函数的输出值域为一个封闭的环。将节点随机分布到这个环上,每个节点负责处理从自己开始顺时针至下一个节点的全部哈希值域上的数据。优点:可以任意动态添加、删除节点,只影响一致性哈希环上相邻的节点;缺点:很难保持均匀分配,当一个节点异常时,该节点全部压力转移到相邻一个节点上,当加入一个新节点时只能为一个相邻节点分摊压力。改进算法:引入虚节点。创建多个虚节点(远大于集群中机器个数),将虚节点分布在一致性哈希环上,为每个节点分配若干虚拟节点。(先通过数据的哈希值找到对应的虚节点,再查找元数据找到对应的真实节点)

eg:
GFS&HDFS 按数据量分布
Map reduce 按GFS的数据分布做本地化
Big Table&HBase 按数据范围分布
PNUTS 哈希方式/按数据范围分布
Dynamo&Cassandra 一致性哈希
Mola&Armor 哈希方式
Big Pipe 哈希方式
Doris 哈希方式与按数据量分布组合


副本与数据分布

一种基本的数据副本策略是以机器为单位,若干机器互为副本,副本机器之间的数据完全相同。优点是非常简单,缺点是恢复数据的效率不高、可扩展性不高。另一种做法是将数据拆为较合理的数据段,以数据段为单位作为副本。
本地化计算:将计算尽量调度到与存储节点在同一台物理机器上的计算节点上进行。


副本控制协议

按特定的协议流程控制副本数据的读写行为,使得副本满足一定的可用性和一致性要求的分布式协议。分为两大类:中心化副本控制协议和去中心化副本控制协议。
中心化副本控制协议:由一个中心化节点协调副本数据的更新、维护副本之间的一致性。优点:协议相对较为简单,所有副本相关控制由中心节点完成。缺点:可用性依赖于中心节点。

primary-secondary(primary-backup)协议:副本被分为两大类,其中有且仅有一个副本作为primary副本,除primary以外的副本都作为secondary副本。一般要解决四大类问题:数据更新流程、数据读取方式、Primary副本的确定和切换、数据同步。

最终一致性:读取任何副本可以满足
会话一致性:为副本设置版本号,每次更新后递增版本号,用户读取副本时验证版本号
强一致性:读取primary副本的数据、由primary控制节点secondary节点的可用性、基于Quorum机制

数据读取方式:如果只需要最终一致性,则读取任何副本都可以满足要求。如果需要会话一致性,则可以为副本设置版本号,每次更新后递增版本号,用户读取副本时验证版本号,从而保证用户读到的数据在会话范围内单调递增。使用Primary-secondary比较困难的是实现强一致性,有以下几种思路:
(1)由于数据的更新流程都是由primary控制的,primary副本上的数据一定是最新的,所以如果始终只读primary副本的数据,可以实现强一致性。
(2)由primary控制节点secondary节点的可用性。当primary更新某个secondary副本不成功时,primary将该secondary副本标记为不可用,从而用户不再读取该不可用副本。不可用的secondary副本可以继续尝试与primary同步数据,当与primary完成数据同步后,primary可以副本标记为可用。
(3)基于Quorum机制。

primary副本出现宕机问题时切换到secondary副本难点:1)发现primary副本宕机是个较为复杂的问题;2)切换副本后不能影响副本的一致性。这个问题暴露出该副本协议最大缺点是primary切换会带来一定停服时间。

数据同步:为了解决secondary副本与primary不一致的问题,需要做数据同步。
1)网络分化等异常,secondary上的数据落后于primary数据,解决方式是回放primary上的操作日志;
2)在某些协议下,secondary数据存在脏数据,比如进行了多余的修改操作,较好的做法是设计的分布式协议不产生脏数据,也可以设计一些基于undo日志的方式删除脏数据;
3)secondary是一个新增副本,完全没有数据,解决方法是直接拷贝primary副本的数据(最好支持快照功能)

去中心话副本控制协议:所有节点关系完全对等,通过平等协商达到一致。最大缺点:协议过程通常比较复杂。Paxos是唯一工程中得到应用的强一致性中心化副本控制协议。



Lease机制

Lease机制是最重要的分布式协议,广泛应用于各种实际的分布式系统中。即使在某些系统中相似的设计不被称为lease,但我们可以分析发现其本质就是一种lease的实现。

case study: 基于Lease的分布式cache
基本原理:中心服务器在向各节点发送数据时同时向节点颁发一个lease。每个lease具有一个有效期,和信用卡上的有效期类似,lease上的有效期通常是一个明确的时间点。这样的lease有效期与节点收到lease的时间无关,节点可能收到lease时该lease就已经过期失效。中心服务器发出的lease的含义为:在lease的有效期内,中心服务器保证不会修改对应数据的值。因此,节点收到数据和lease后,将数据加入本地Cache,一旦对应的lease超时,节点将对应的本地cache数据删除。中心服务器在修改数据时,首先阻塞所有新的读请求,并等待之前为该数据发出的所有lease超时过期,然后修改数据的值。
具体流程:
(1)客户端节点读取元数据:判断元数据是否已经处于本地cache且lease处于有效期内,如果不存在则向中心服务器发送请求,返回元数据及lease
(2)客户端修改元数据:中心服务器阻塞所有读数据请求,等待所有与该元数据相关的lease超时,修改元数据并向客户端节点返回修改成功。
优化点:
1)服务器修改元数据时阻塞所有读数据请求,造成没有读服务(返回数据但不颁发lease,或lease为已颁发lease的最大有效期限);
2)服务器在修改元数据时需要等待所有的lease过期超时,造成读延迟增大(主动通知持有lease节点放弃lease消息)。

lease机制的容错性:
(1)通过引入有效期,Lease机制能否非常好的容错网络异常。Lease颁发过程只依赖于网络可以单向通信,即使接收方无法向颁发者发送消息,也不影响lease的颁发。同一个lease可以被颁发者不断重复向接收方发送。即使颁发者偶尔发送lease失败,颁发者也可以简单的通过重发的办法颁发。一旦lease被接收方成功接收,后续lease机制不再依赖于网络通信,即使网络完全中断lease机制也不受影响。
(2)Lease机制能较好的容错节点宕机。如果颁发者宕机,则宕机的颁发者通常无法改变之前的承诺,不会影响lease的正确性。在颁发者恢复后,如果颁发者恢复了之前的lease信息,颁发者可以继续遵守lease的承诺。如果颁发者无法恢复lease信息,则只需等待一个最大的lease超时时间就可以使得所有的lease都失效,从而不破坏lease机制。
(3)lease机制不依赖于存储。颁发者可以持久化颁发过的lease信息,从而在宕机恢复后可以使得在有效期的lease继续有效。

lease机制始终同步问题:
(1)如果颁发者的时钟比接收者的时钟慢,则当接收者认为lease已经过期的时候,颁发者依旧认为lease有效。接收者可以用在lease到期前申请新的lease的方式解决这个问题。
(2)如果颁发者的时钟比接收者的时钟快,则当颁发者认为lease已经过期的时候,接收者依旧认为lease有效,颁发者可能将lease颁发给其他节点,造成承诺失效,影响系统的正确性。对于这种问题,通常做法是将颁发者的有效期设置得比接收者的略大,只需大过时钟误差就可以避免对lease的有效性的影响。

基于lease机制确定节点状态
primary-secondary架构中,如何确定primary节点出故障?增加心跳检测机制,每个节点定期给检测节点发送心跳包,诊断出故障可能有多重情况:节点出故障、网络故障、检测节点故障(处理心跳延迟)。导致出现双主问题。
解决双主问题的两种思路:
1)设计的分布式协议可以容忍“双主”错误,采用去中心化设计;
2)利用lease机制。由中心节点向其他节点发送lease,若某个节点持有有效的lease,则认为该节点正常可以提供服务。

lease的有效时间选择:工程中,常选择的lease时长是10秒级别。



Quorum机制(一种简单有效的副本管理机制)

Write-all-read-one(简称WARO) 是一种最简单的副本控制规则,顾名思义即在更新时写所有的副本,只有在所有的副本上更新成功,才认为更新成功,从而保证所有的副本一致,这样在读取数据时可以读任一副本上的数据。由于更新操作需要在所有的N个副本上都成功,更新操作才能成功,所以一旦有一个副本异常,更新操作失败,更新服务不可用。对于更新服务,虽然有N个副本,但系统无法容忍任何一个副本异常。另一方面,N个副本中只要有一个副本正常,系统就可以提供读服务。对于读服务而言,当有N个副本时,系统可以容忍N-1个副本异常。
WARO牺牲了更新服务的可用性,最大程度的增强读服务的可用性。将WARO的条件进行松弛,从而使得可以在读写服务可用性之间做折中,得出Quorum机制。

Quorum机制下,当某次更新操作wi一旦在所有N个副本中的W个副本上都成功,则就称该更新操作为“成功提交的更新服务”,称对应的数据未“成功提交的数据”。令R>N-W,由于更新操作wi仅在W个副本上成功,所以在读数据时,最多需要读取R个副本则一定能够读到wi更新后的数据vi。
分析Quorum机制的可用性:限制Quorum参数为W+R=N+1。由于更新操作在W个副本上都成功,更新操作才能成功,所以一旦N-W+1个副本异常,更新操作始终无法在W个副本上成功,更新服务不可用。另一方面,一旦N-R+1个副本异常,则无法保证一定可以读到与W个副本有交集的副本集合,则读服务的一致性下降。
仅仅依赖Quorum机制是无法保证强一致性的。因为仅有Quorum机制时无法确定最新已成功提交的版本号,除非将最新已提交的版本号作为元数据由特定的元数据服务器或元数据集群管理,否则很难确定最新成功提交的版本号。
对于一个 强一致性系统,应该始终读取返回最新的成功提交的数据,在Quorum机制下,要达到这一目的需要对读取条件做进一步加强。
1. 限制提交的更新操作必须严格递增,即只有在前一个更新操作成功提交后才可以提交后一个更新操作,从而成功提交的数据版本号必须是连续增加的。
2. 读取R个副本,对于R个副本中版本号最高的数据,
  2.1 若已存在W个,则该数据为最新的成功提交的数据
  2.2 若存在个数据少于W个,假设为X个,则继续读取其他副本,直若成功读取到W个该版本的副本,则该数据为最新的成功提交的数据;如果在所有副本中该数据的个数肯定不满足W个,则R中版本号第二大的为最新的成功提交的副本。

基于Quorum机制选择Primary:基本primary-secondary协议中,primary负责进行更新操作的同步工作。现在基本primary-secondary协议中引入Quorum机制,即primary成功更新W个副本(含primary本身)后向用户返回成功。读取数据时依照一致性要求的不同可以有不同的做法:如果需要强一致性的立刻读取到最新的成功提交的数据,则可以简单的只读取primary副本上的数据即可。也可以通过quorum的方式读取;如果需要会话一致性,则可以根据之前已经读到的数据版本号在各个副本上进行选择性读取;如果只需要弱一致性,则可以选择任意副本读取。
在primary-secondary协议中,当primary异常时,需要选择出一个新的primary,之后secondary副本与primary同步数据。通常情况下,选择新的primary的工作是由某一中心节点完成的,在引入Quorum机制后,常用的primary选择方式与读取数据的方式类似,即中心节点读取R个副本,选择R个副本中版本号最高的副本作为新的primary。新primary与至少W个副本完成数据同步后作为新的primary提供读写服务。首先,R个副本中版本号最高的副本一定蕴含了最新的成功提交的数据。再者,虽然不能确定最高版本号的数是一个成功提交的数据,但新的primary在随后与secondary同步数据,使得该版本的副本个数达到W,从而使得该版本的数据成为成功提交的数据。



日志技术

日志技术是宕机恢复的主要技术之一,最初使用在数据库系统中。在数据库系统中实现宕机恢复,其难点在于数据库操作需要满足ACID,尤其在支持事务的数据库中宕机往往发生在某些事务只执行了部分操作的时候。此时宕机恢复的主要目标就是数据库系统恢复到一个稳定可靠的状态,消除未完成的事务对数据库状态的影响。数据库的日志主要分为Undo Log、Redo Log、Redo/Undo Log与No Redo/No undo log。这四类日志的区别在更新日志文件和数据文件的时间点要求不同,从而造成性能和效率也不相同。

Redo Log与Check Point
问题模型:假设需要设计一个高速的单机查询系统,将数据全部存放在内存中以实现高速的数据查询,每次更新操作更新一小部分数据。现在问题为利用日志技术实现该内存查询系统的宕机恢复。与数据库的事务不同的是,这个问题模型中的每个成功的更新操作都会生效。这也等效为数据库的每个事务只有一个更新操作,且每次更新操作都可以也必须立即提交。
Redo Log是一种非常简单实用的日志技术。以下是Redo Log的更新流程:
(1)将更新操作的结果(如Set K1=1,则记录K1=1)以追加写的方式写入磁盘的日志文件;
(2)按更新操作修改内存中的数据;
(3)返回更新成功。
用Redo Log进行宕机恢复非常简单,只需要“回放”日志即可:从头读取日志文件中的每次更新操作的结果,用这些结果修改内存中的数据。
宕机恢复流量的缺点是需要回放所有redo日志,效率较低,假如需要恢复的操作非常多,那么这个宕机恢复过程将非常漫长。解决这一问题的方法即引入 check point技术。在简化的模型下,check point技术的过程即将内存中的数据以某种易于重新加载的数据组织方式完整的dump到磁盘,从而减少宕机恢复时需要回放的日志数据:
(1)向日志文件中记录“Begin Check Point”;
(2)将内存中的数据以某种易于重新加载的数据组织方式dump到磁盘上;
(3)向日志文件中记录“End Check Point”。

基于check point的宕机恢复流程
(1)将dump到磁盘的数据加载到内存;
(2)从后向前扫描日志文件,寻找最后一个“End Check Point”日志;
(3)从最后一个“End Check Point”日志向前找到最近的一个“Begin Check Point”日志,并回放该日志后的所有更新操作日志。
同一条日志可以重复回放的操作即所谓具有“ 幂等性”的操作。工程中,有些时候Redo日志无法具有幂等性,例如加法操作、append操作等。此时,dump的内存数据一定不能包括“begin check point”日志之后的操作。为此,有两种办法,其一是check point的过程中停更新操作,不再进行新的操作,另一种方法是,设计一种支持快照的内存数据结构,可以快速的将内存生成快照,然后写入check point日志再dump快照数据。例如使用哈希表的数据结构,当做快照时,新建一个哈希表接收新的更新,原哈希表用于dump数据,此时内存中存在两个哈希表,查询数据时查询两个哈希表并合并结果。

No Undo/No Redo Log(0/1目录)
问题场景:若数据维护在磁盘中,某批更新由若干个更新操作组成,这些更新操作需要原子生效,即要么同时生效,要么都不生效。
0/1目录技术中有两个目录结构,称为目录0和目录1.另有一个结构称为主目录记录当前正在使用的目录称为活动目录。主记录中要么记录使用目录0,要么记录使用目录1。目录0或目录1中记录了各个数据的在日志文件中的位置。0/1目录的数据更新过程始终在非活动目录上进行,只是在数据生效前,将主目录的0、1值反转,从而切换主记录:
(1)将活动目录完整拷贝到非活动目录;
(2)对于每个更新操作,新建一个日志项纪录操作后的值,并在非活动目录中将相应数据的位置修改为新建的日志项的位置;
(3)原子性修改主记录:反转主记录中的值,使得非活动目录生效。
0/1目录将批量事务操作的原子性通过目录手段归结到主记录的原子切换。由于多条记录的原子修改一般较难实现而单条记录的原子修改往往可以实现,从而降低了问题实现的难度。在工程中0/1目录的思想运用非常广泛,其形式也不局限于上述流程中,可以是内存中的两个数据结构来回切换,也可以是磁盘上的两个文件目录来回生效切换。



两阶段提交协议

两阶段提交协议是一种经典的强一致性中心化副本控制协议。虽然在工程中该协议有较多的问题,但研究该协议能很好的理解分布式系统的几个典型问题。
问题背景:在经典的分布式数据库系统中,同一个数据库的各个副本运行在不同的节点上,每个副本的数据要求完全一致。数据库中的操作都是事务,一个事务是一系列读、写操作,事务满足ACID。每个事务的最终状态要么是提交,要么是失败。一旦一个事务成功提交,那么这个事务中所有的写操作中成功,否则所有的写操作都失败。在单机上,事务靠日志技术或MVCC等技术实现。在分布式数据库中,需要有一种控制协议,使得事务要么在所有的副本上都提交,要么在所有的副本上都失败。对同一个事务而言,虽然在所有副本上执行的事务操作都完全一样,但可能在某些副本上可以提交,在某些副本上不能提交。这是因为,在某些副本上,其他的事务可能与本事务有冲突(如死锁),从而造成在有些副本上可以提交,而有些副本上事务无法提交。
两阶段提交协议是一种典型的“中心化副本控制”协议。在该协议中,参与的节点分为两类:一个中心化协调者节点和N个参与者节点。
思路:在第一阶段,协调者询问所有的参与者是否可以提交事务(请参与者投票),所有参与者向协调者投票。在第二阶段,协调者根据所有参与者的投票结果做出是否事务可以全局提交的决定,并通知所有的参与者执行该决定。在一个两阶段提交流程中,参与者不能改变自己的投票结果。两节点提交协议的可以全局提交的前提是所有的参与者都统一提交事务,只要有一个参与者投票选择放弃事务,则事务必须被放弃。

两阶段提交协调者流程:
1. 写本地日志“begin_commit”,并进入WAIT状态;
2. 向所有参与者发送“prepare消息”;
3. 等待并接收参与者发出的对“prepare消息”的响应:
  3.1 若收到任何一个参与者发出的“vote-abort消息”;
    3.1.1 写本地“global-abort”日志,进入ABORT;
    3.1.2 向所有的参与者发送“global-abort消息”;
    3.1.3 进入ABORT状态;
3.2 若收到所有参与者发送的“vote-commit”消息;
  3.2.1 写本地“global-commit”日志,进入COMMIT状态;
  3.2.2 向所有的参与者发送“global-commit消息”;
4. 等待并接收参与者发送的对“global-abort消息”或“global-commit消息”的确认响应消息,一旦收到所有参与者的确认消息,写本地“end_transaction”日志流程结束。

两阶段提交协调者流程
1. 写本地日志“init”记录,进入INIT状态;
2. 等待并接收协调者发送的“prepare消息”,收到后
  2.1 若参与者可以提交本次事务
    2.1.1 写本地日志“ready”,进入READY状态
    2.1.2 向协调者发送“vote-commit”消息
    2.1.4 等待协调者的消息
      2.1.4.1 若收到协调者的“global-abort”消息
        2.1.4.1.1 写本地日志“abort”,进入ABORT状态
        2.1.4.1.2 向协调者发送对“global-abort”的确认消息
      2.1.4.2 若收到协调者的“global-commit”消息
        2.1.4.1.1 写本地日志“commit”,进入COMMIT状态
        2.1.4.1.2 向协调者发送对“global-commit”的确认消息
  2.2 若参与者无法提交本次事务
    2.2.1 写本地日志“abort”,进入ABORT状态
    2.2.2 向协调者发送“vote-abort”消息
    2.2.3 流程对该参与者结束
    2.2.4 若后续收到协调者的“global-abort”消息可以响应
3. 即使流程结束,但任何时候收到协调者发送的“global-abort”消息或“global-commit”消息都要发送一个对应的确认消息。

异常处理
a、宕机恢复
协调者宕机恢复后,首先通过日志查找到宕机前的状态。如果日志中最后是“begin_commit”记录,说明宕机前协调者处于WAIT状态,协调者可能已经发送过“prepare消息”也可能没发送过,但协调者一定还没发送过“global-commit消息”或“global-abort消息”,即事务的全局状态还没有确定。此时,协调者可以重新发送“prepare消息”继续两阶段提交流程,即使参与者已经发送过“prepare消息”的响应,也不过是再次重传之前的响应而不会影响协议的一致性。如果日志最后是“global-commit”或“global-abort”记录,说明宕机前协调者处于COMMIT或ABORT状态。此时协调者只需重新向所有的参与者发送“global-commit消息”或“global-abort消息”就可以继续两阶段提交流程。

参与者宕机恢复后,首先通过日志查找宕机前的状态。如果日志中最后是“init”记录,说明参与者处于INIT状态, 还没有对本次事务做出投票选择,参与者可以继续流程等待协调者发送的“prepare消息”。如果日志中最后是“ready”记录,说明参与者处于REDAY状态,此时说明参与者已经就本次事务做出了投票选择,但宕机前参与者是否已经向协调者发送“vote-commit”消息并不可知。所以此时参与者可以向协调者重发“vote-commit”,并继续协议流程。如果日志中最后是“commit”或“abort”记录,说明参与者已经收到过协调者的“global-commit消息”(处于COMMIT状态)或者“global-abort消息”(处于ABORT状态)。至于是否向协调者发送过对“global-commit”或“global-abort”的确认消息则未知。但即使没有发送过确认消息,由于协调者会不断重发“global-commit”或“global-abort”,只需在收到这些消息时发送确认消息既可,不影响协议的全局一致性。

b、响应超时
协调者在WAIT状态状态超时,即协调者等待参与者对“prepare消息”的响应超时, 在超时时间内始终不能收到所有的参与者的投票结果而收到的响应都是“vote-commit”消息,从而协调者无法确定该事务是否可以提交。 这种超时可能的原因有:
1. 协调者与某个参与者网络中断,协调者的“prepare”消息无法发送到参与者,或者参与者的响应消息无法发送到协调者。
2. 参与者宕机,如果某个参与者宕机,则无法响应协调者的“prepare消息”,只有等该参与者恢复后才能响应消息。
对于这种超时,协调者可以选择直接放弃整个事务,向所有参与者发送“global-abort”消息,进入ABORT状态。由于协调者在超时前并没有发送任何“global-abort”或者“global-commit”消息,所以协调者此时放弃事务不影响协议的一致性。

协调者在COMMIT或ABORT状态超时,即协调者等待参与者对“global-commit”或“global-abort”消息的响应时超时,从而协调者无法确认两阶段提交是否完成。这种超时可能的原因有:
1. 协调者与某个参与者网络中断,协调者的“global-commit”或“global-abort”消息无法发送到参与者,或者参与者的响应消息无法发送到协调者。
2. 参与者宕机,如果某个参与者宕机,则无法响应协调者的“global-commit”或“global-abort”,只有等该参与者恢复后才能响应消息。
对于这种超时,协调者只能不断重发“global-commit” 或“global-abort”消息给尚未响应的参与者,直到所有的参与者都发送响应。可以这么认为,两阶段提交协议对于这种超时的相关异常没有很好的容错机制,整个流程只能阻塞在这里,且流程状态处于未知。也许所有的参与者都完成了各自的流程,只是由于协调者无法收到响应,整个两阶段提交协议就无法完成。

参与者等待协调者的“prepare”消息时超时,此种异常的原因可能是协调者宕机或者协调者与参与者网络中断。对于这种超时,参与者可以进入ABORT状态,这样即使后续收到了“prepare”消息,也不影响协议的一致性也不会阻塞其他流程,唯一的缺点是,该事务可能原本可以提交,现在却被放弃。

参与者在READY状态等待协调者发送的“global-commit”或“global-abort”消息超时。出现这种超时的原因可能是协调者宕机也可能是网络中断。因为参与者处于 READY 状态,说明参与者之前一定已经发送了“vote-commit”消息,从而参与者已经不能改变自己的投票选择。此时,参与者只能不断重发“vote-commit”消息,直到收到协调者的“global-commit”或“global-abort”消息后流程才可继续。可以这么认为,两阶段提交协议对于这种超时的相关异常也没有很好的容错机制,整个流程只能阻塞在这里,且对于参与者而言流程状态处于未知,参与者即不能提交本地节点上的事务,也不能放弃本地节点事务。

两阶段提交协议在工程实践中真正使用的较少,主要原因有以下几点:
第一、两阶段提交协议的 容错能力较差。从上文的分析可以看出,两阶段提交协议在某些情况下存在流程无法执行下去的情况,且也无法判断流程状态。在工程中好的分布式协议往往总是可以在即使发生异常的情况下也能执行下去。 
第二、两阶段提交协议的 性能较差。一次成功的两阶段提交协议流程中,协调者与每个参与者之间至少需要两轮交互 4 个消息“prepare”、“ vote-commit”、“ global-commit”、“确认 global-commit”。过多的交互次数会降低性能。另一方面,协调者需要等待所有的参与者的投票结果,一旦存在较慢的参与者,会影响全局流程执行速度。



基于MVCC的分布式事务

MVCC即多个不同版本的数据实现并发控制的技术,其基本思想是为每次事务生成一个新版本的数据,在读数据时选择不同版本的数据即可以实现对事务结果的完整性读取。在使用MVCC时,每个事务都是基于一个已生效的基础版本进行更新,事务可以并行进行,从而可以产生一种图状结构。

事务在基于基础数据版本做本地修改时,为了不影响真正的数据,通常有两种做法,一是将基础数据版本中的数据完全拷贝出来再修改,SVN即使用了这种方法,SVN check out即是拷贝的过程;二是每个事务中只记录更新操作,而不记录完整的数据,读取数据时再将更新操作应用到用基础版本的数据从而计算出结果,这个过程也类似SVN的增量提交。

分布式MVCC的重点不在于并发控制,而在于实现分布式事务。假设在一个分布式系统中,更新操作以事务进行,每个事务包括若干个对不同节点的不同更新操作。更新事务必须具有原子性,即事务中的所有更新操作要么同时在各个节点生效,要么都不生效。假设不存在并发的事务,即上一个事务成功提交后才进行下一个事务。

基于MVCC的分布式事务的方法为:为每个事务分配一个递增的事务编号,这个编号也代表了数据的版本号。当事务在各个节点上执行时,各个节点只需记录更新操作及事务编号,当事务在各个节点都完成后,在全局元信息中记录本次事务的编号。在读取数据时,先读取元信息中已成功的最大事务编号,再于各个节点上读取数据,只读取更新操作编号小于等于最后最大已成功提交事务编号的操作,并将这些操作应用到基础数据形成读取结果。

上述方法的一个重要问题是,随着执行的事务越来越多,各个站点保存的更新操作会越来越多,读取数据时需要应用的更新操作也越来越多。工程中可以对此周期性的启动合并操作,将历史上不再需要的版本合并为一个更新操作。



Paxos协议

Paxos协议中,有一组完全对等的参与节点(称为accpetor),这组节点各自就某一事件做出决议,如果某个决议获得了超过半数节点的同意则生效。Paxos协议中只要有超过一半的节点正常,就可以工作,能很好对抗宕机、网络分化等异常情况。

Paxos协议中,有三类节点:
Proposer:提案者。Proposer可以有多个,Proposer提出议案(value)。所谓value,在工程中可以是任何操作,例如“修改某个变量的值为某个值”、“设置当前primary为某个节点”等等。Paxos协议中统一将这些操作抽象为value。不同的Proposer可以提出不同的甚至矛盾的value,例如某个Proposer提议“将变量X设置为1”,另一个Proposer提议“将变量X设置为2”,但对同一轮Paxos过程,最多只有一个value被批准。

Acceptor:批准者。Acceptor有N个,Proposer提出的value必须获得超过半数(N/2+1)的Acceptor批准后才能通过。Acceptor之间完全对等独立。

Learner:学习者。Learner学习被批准的value。所谓学习就是通过读取各个Proposer对value的选择结果,如果某个value被超过半数Acceptor通过,则Learner学习到了这个value。这里类似Quorum机制,某个value需要获得W=N/2+1的Acceptor批准,从而学习者需要至少读取N/2+1个Accpetor,至多读取N个Acceptor的结果后,能学习到一个通过的value。

上述三类角色只是逻辑上的划分,实践中一个节点可以同时充当这三类角色。

Paxos协议一轮一轮的进行,每轮都有一个编号。每轮Paxos协议可能会批准一个value,也可能无法批准一个value。如果某一轮Paxos协议批准了某个value,则以后各轮Paxos只能批准这个value。上述各轮协议流程组成了一个Paxos协议实例,即一次Paxos协议实例只能批准一个value,这也是Paxos协议强一致性的重要体现。

每轮Paxos协议分为两个阶段,准备阶段和批准阶段,在这两个阶段Proposer和Acceptor有各自的处理流程。

Proposer的流程
准备阶段
1. 向所有的Acceptor消息“Prepare(b)”;这里b是Paxos的轮数,每轮递增
2. 如果收到任何一个Acceptor发送的消息“Reject(B)”,则对于这个Proposer而言本轮Paxos失败,将轮数b设置为B+1后重新步骤1;

批准阶段,根据收到的Acceptor的消息作出不同选择)
3. 如果接收到的Acceptor的“Promise(b, v_i)”消息达到N/2+1个(N为Acceptor总数,除法取整,下同);v_i表示Acceptor最近一次在i轮批准过value v。
  3.1 如果收到的“Promise(b, v)”消息中,v都为空,Proposer选择一个value v,向所有Acceptor广播Accept(b, v);
  3.2 否则,在所有收到的“Promise(b, v_i)”消息中,选择i最大的value v,向所有Acceptor广播消息Accept(b, v);
4. 如果收到Nack(B),将轮数b设置为B+1后重新步骤1;

Accpetor流程
准备阶段
1. 接受某个Propeser的消息Prepare(b)。参数B是该Acceptor收到的最大Paxos轮数编号;V是 Acceptor批准的value,可以为空
  1.1 如果b>B,回复Promise(b, V_B),设置B=b; 表示保证不再接受编号小于b的提案。
  1.2 否则,回复Reject(B)

批准阶段
2. 接收Accept(b, v),
  2.1 如果b < B, 回复Nack(B),暗示proposer有一个更大编号的提案被这个Acceptor接收了
  2.2 否则设置V=v。表示这个Acceptor批准的Value是v。广播Accepted消息。



CAP理论

CAP理论是由Eric Brewer提出的分布式系统中最为重要的理论之一。
Consistency(一致性):CAP理论中的副本一致性特指一致性;
Availability(可用性):指系统在出现异常时仍然可以提供服务;
Tolerance to the partition of network(分区容忍):指系统可以对网络分区这种异常情况进行容错处理。

CAP理论指出:无法设计一种分布式协议,使得同时完全具备CAP三个属性,即1)该种协议下的副本始终是强一致性,2)服务始终是可用的,3)协议可以容忍任何网络分区异常;分布式系统协议只能在CAP这三者间所有折中。




参考资料:
(1)《分布式系统原理介绍》 刘杰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值