CAP
分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。
一致性
一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
可用性
可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
分区容忍性
网络分区指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。
在分区容忍性条件下,分布式系统在遇到任何网络分区故障的时候,仍然需要能对外提供一致性和可用性的服务,除非是整个网络环境都发生了故障。
权衡
在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是要在可用性和一致性之间做权衡。
可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,
为了保证一致性(CP),不能访问未同步完成的节点,也就失去了部分可用性;zookeeper
为了保证可用性(AP),允许读取所有节点的数据,但是数据可能不一致。Eureka
分布式事务
TCC
TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。
TCC本质上是一个业务层面上的2PC,他要求业务在使用TCC模式时必须实现三个接口Try()、Confirm()和Cancel()。在讲述2PC的时候,我们说过2PC无法解决宕机问题,那TCC如何解决2PC无法应对宕机问题的缺陷的呢?答案是不断重试。
TCC 是服务化的二阶段编程模型,其 Try、Confirm、Cancel 3 个方法均由业务编码实现:
Try 操作作为一阶段,负责资源的检查和预留。
Confirm 操作作为二阶段提交操作,执行真正的业务。
Cancel 是预留资源的取消。
SAGA
SAGA Choreography 策略是通过【事件机制】实现的,各个服务都定义好正常、异常的处理方法,然后监听目标事件,根据不同的事件来调用不同的处理方法。
此策略好处是实现简单,坏处是整体事件逻辑会比较复杂,比如有10个服务参与其中,那么整体事件订阅学历证关系就会很凌乱。
Orchestration 策略的重点在于总指挥,需要为其定义指挥手册,以便总指挥在不同的时刻向相应的服务发送对应的指令
本地消息表
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
之后将本地消息表中的消息转发到消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
2PC
步骤
1、准备阶段-协调者开始事务
协调者开始向每一个参与者发起请求询问是否可以执行事务。
2、准备阶段-参与者投票
参与者内部开始锁定资源并且执行事务,但是本地事务不会提交,即锁不会释放,如果事务执行成功,那么返回协调者ok,否则返回失败。
3、提交阶段-协调者提交或取消事务
协调者根据参与者返回的信息,如果存在一个响应返回失败或者响应超时,那么事务将会被取消,协调者会向所有参与这发送回滚信息。反之,协调者会发送提交信息,并等待响应,理论都会响应成功,最终事务完成。
4、提交阶段-参与者提交事务
参与者在这里会等待协调者的通知,如果协调者没有通知那么参与者将会一直处于等待中。如果协调者通知提交则执行提交,并释放锁资源,返回正常响应。如果协调者通知回滚则执行回滚操作,并释放资源返回响应。
问题
1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
3PC
步骤
1、CanCommit-协调者询问事务提交
协调者向所有参与者发起请求询问是否可以执行事务。等待响应。
2、CanCommit-参与者响应事务提交
参与者根据自己的实际情况返回响应,是否可以执行事务,可以在响应OK。这个过程参与者并没有锁定资源。
3、PreCommit-协调者发起事务预提交
协调者在第一步询问之后,如果参与者都是正常,则会发起预提交事务,这里和2PC的第一步是一致的。否则,如果参与返回异常或者响应超时,则取消事务,并且向所有参与方发生事务取消回滚。
4、PreCommit-参与者执行事务预提交
参与者在接收到协调者发起的预提交请求时,则执行事务,同时锁定所有资源,但是不提交事务,同时给协调者正常的响应,这里和2PC的第2步是一致的。如果接收到回滚请求,在结束事务。
5、DoCommit-协调者发起事务提交
协调者在第3步之后接收到所有参与者的响应,如果全部返回正常,那么则提交事务,如果提交事务响应成功则事务正常结束,否则,回滚事务。这里和2PC的处理是一致的。
6、DoCommit-参与者响应事务提交
参与者在接收到协调者提交指令后,则正常提交事务释放锁信息,并返回正常响应,如果接收到协调者的回滚请求则正常回滚事务。在这里也和2PC一致,但是如果协调器一直没有发出请求,则参与者会提交事务。
3PC与2PC 流程中的区别
1、3PC引入了CanConmmit阶段,相比2PC,直接开始执行事务,如果有一个参与这没有能力处理请求,那么其他参与者的资源锁定就造成不必要的浪费,3PC加入这个阶段优化了这个问题。
2、3PC在最后提交阶段中,如果协调者没有在规定时间有一个正常的响应,那么参与者则会提交事务。这一个超时机制虽然解决了一定程度的资源浪费问题,但是缺会引入一个数据一致性的问题,所以在实际使用中还是需要有一定平衡。
问题
1、是否解决事务一致性问题,2PC和3PC在针对最后一步提交事务中参与者的单点故障问题导致的不一致都是没有解决的。但是在3PC中由于为了解决P问题下的资源浪费问题,引入的超时机制可能会导致一定的数据不一致问题,但是由于3PC的CanCommit阶段的存在,让发生一致性问题的概率减少。
2、是否解决单点故障问题,2PC和3PC在遇到单点故障问题时基本表现一直,但是3PC在最后提交阶段,由于引入超时机制对于协调者的单点故障问题做了优化。
4、是否解决资源浪费问题,3PC相比于2PC对于资源浪费的优化会更多一些,CanCommit阶段的引入和超时机制的引入都对于资源浪费问题进行了优化,这种优化会在很大程度上提高分布式事务的并发性能。
分布式协议
Paxos
用于达成共识性问题,即对多个节点产生的值,该算法能保证只选出唯一一个值。
主要有三类节点:
提议者(Proposer):提议一个值;
接受者(Acceptor):对每个提议进行投票;
告知者(Learner):被告知投票的结果,不参与投票过程。
Prepare阶段
Proposer向每个Acceptor发送一个prepar(n)请求,这个请求的主要目的是告诉Acceptor我要提出一个提议编号为n的提议,Acceptor根据自己本地记录的状态来决定是否允许该Proposer可以继续接下来的提议。
Proposer向Acceptor发送prepare(n)请求时,Acceptor可能处于三种状态,每种状态对于prepare请求的处理都是不同的。
-
Acceptor未收到过prepare请求。假设请求为prepare(n1),Acceptor会允许该请求,表示Proposer可以以编号n1进行后续的提案了,并在本地记录提议编号n1;
-
Acceptor已经接收过其他Proposer发送的prepare请求但未接受过Accecp请求。假设请求为prepare(n2),这个时候会分为两种情况,
n2<=n1,Acceptor拒绝prepare(n2)请求,并告知Proposer此时在本地记录的提议编号n1, Proposer收到响应后可以用一个大于n1的编号n3发起新的prepare(n3)请求。
n2 > n1,Acceptor更新自己的记录为n2,并允许Prepare(n2)请求。
- Acceptor已经接收过其他Proposer发送的Prepare和Accep请求。这个时候表示该Acceptor已经接受过其他Proposer的Accept请求,也就是已经同意了某个value1,当Acceptor同意某个value1之后,是不可以修改的。这个时候Acceptor会返回一个二元组(n, value1),告诉Proposer可以进行提议了,但是提议的值必须用value1。
Accept阶段
当Proposer的prepare(n)请求被超过半数的Accepor允许之后,Proposer就可以开始真正的提议了,Proposer发送一个(num,value)二元组进行提议,注意这里的value可能是用的Proposer自己的value,也可能是Acceptor返回的value。
Acceptor接收到该请求之后,会判断提议编号是否和本地保存的是否一致。如果一致,那么该提议被该Acceptor通过,如果不一致,那么会告诉提议者,提议编号和本地保存的不一致,这个时候提议者会以一个更大的提议编号重新发起一个新的prepare请求。Acceptor的值一旦确定,便不可以再修改。
1、参谋1发起提议,派通信兵带信给3个将军,携带一个小牌子,内容为(编号1);
2、3个将军的情况如下:
a)将军1和将军2收到参谋1的提议,将军1和将军2把(编号1)记录下来,如果有其他参谋提出更小的编号,将被拒绝;同时让通信兵带信回去,内容为(ok);
b)负责通知将军3的通信兵被抓,因此将军3没收到参谋1的提议;
3、参谋2在同一时间也发起了提议,派通信兵带信给3个将军,内容为(编号2);
4、3个将军的情况如下:
a)将军2和将军3收到参谋2的提议,将军2和将军3把(编号2)记录下来,如果有其他参谋提出更小的编号,将被拒绝;同时让通信兵带信回去,内容为(ok);
b)负责通知将军1的通信兵被抓,因此将军1没收到参谋2的提议;
5、参谋1收到至少2个将军的回复,再次派通信兵带信给有答复的2个将军,内容为(编号1,进攻时间1);
6、2个将军的情况如下
a)将军1收到了(编号1,进攻时间1),和自己保存的编号相同,因此把(编号1,进攻时间1)保存下来;同时让通信兵带信回去,内容为(Accepted);
b) 将军2收到了(编号1,进攻时间1),由于(编号1)小于已经保存的(编号2),因此让通信兵带信回去,内容为(Rejected,编号2);
7、参谋2收到至少2个将军的回复,再次派通信兵带信给有答复的2个将军,内容为(编号2,进攻时间2);
8、将军2和将军3收到了(编号2,进攻时间2),和自己保存的编号相同,因此把(编号2,进攻时间2)保存下来,同时让通信兵带信回去,内容为(Accepted);
9、参谋2收到至少2个将军的(Accepted)内容,确认进攻时间已经被多数派接受;
10、参谋1只收到了1个将军的(Accepted)内容,同时收到一个(Rejected,编号2);参谋1重新发起提议,派通信兵带信给3个将军,内容为(编号3);
11、3个将军的情况如下
a)将军1收到参谋1的提议,由于(编号3)大于之前保存的(编号1),因此把(编号3)保存下来;由于将军1已经接受参谋1前一次的提议,因此让通信兵带信回去,内容为(编号1,进攻时间1);
b)将军2收到参谋1的提议,由于(编号3)大于之前保存的(编号2),因此把(编号3)保存下来;由于将军2已经接受参谋2的提议,因此让通信兵带信回去,内容为(编号2,进攻时间2);
c)负责通知将军3的通信兵被抓,因此将军3没收到参谋1的提议;
12、参谋1收到了至少2个将军的回复,比较两个回复的编号大小,选择大编号对应的进攻时间作为最新的提议;参谋1再次派通信兵带信给有答复的2个将军,内容为(编号3,进攻时间2);
13、将军1和将军2收到了(编号3,进攻时间2),和自己保存的编号相同,因此保存(编号3,进攻时间2),同时让通信兵带信回去,内容为(Accepted);
参谋1收到了至少2个将军的(accepted)内容,确认进攻时间已经被多数派接受。
此时参谋1,2,将军1~3 均已达成共识。最终的结果为:进攻时间2
其实,将军1~3拿到的编号并不相同,由于编号只是用来保证数据唯一性,所以并不是最终起决定性作用的数据。
最终大家达成一致的数据是:进攻时间2
将军1:<编号3,进攻时间2>
将军2:<编号3,进攻时间2>
将军3:<编号2,进攻时间2>
面试题:如何保证Paxos算法活性
假设存在这样一种极端情况,有两个Proposer依次提出了一系列编号递增的提案,导致最终陷入死循环,没有value被选定
通过选取主Proposer,规定只有主Proposer才能提出议案。只要主Proposer和过半的Acceptor能够正常网络通信,主Proposer提出一个编号更高的提案,该提案终将会被批准。
每个Proposer发送提交提案的时间设置为一段时间内随机,保证不会一直死循环
Raft协议
有三种节点:Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票。
来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。Leader 会把修改复制到所有 Follower。等待大多数的 Follower 也进行了修改,然后才将修改提交。此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
ZAB 协议
全称:Zookeeper Atomic Broadcast(Zookeeper 原子广播协议)。ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持 崩溃恢复 和原子广播协议。基于该协议,Zookeeper 实现了一种 主备模式 的系统架构来保持集群中各个副本之间数据一致性。
原子广播
ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个 二阶段提交过程。
对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer ,
然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作(先提交自己,再发送 commit 给所有 Follwer)
Leader 在收到客户端请求之后,会将这个请求封装成一个事务,并给这个事务分配一个全局递增的唯一 ID,称为事务ID(ZXID),ZAB 兮协议需要保证事务的顺序,因此必须将每一个事务按照 ZXID 进行先后排序然后处理。
在 Leader 和 Follower 之间还有一个消息队列,用来解耦他们之间的耦合,解除同步阻塞。
zookeeper集群中为保证任何所有进程能够有序的顺序执行,只能是 Leader 服务器接受写请求,即使是 Follower 服务器接受到客户端的请求,也会转发到 Leader 服务器进行处理。
实际上,这是一种简化版本的 2PC,并不能解决单点问题。
崩溃恢复
当Leader崩溃(与过半的Follower失去联系),就会进入崩溃恢复模式。
ZAB定义两个原则:
- ZAB 协议确保那些已经在 Leader 提交的事务最终会被所有服务器提交。
- ZAB 协议确保丢弃那些只在 Leader 提出/复制,但没有提交的事务。
所以,ZAB 设计了下面这样一个选举算法:能够确保提交已经被 Leader 提交的事务,同时丢弃已经被跳过的事务。
针对这个要求,如果让 Leader 选举算法能够保证新选举出来的 Leader 服务器拥有集群总所有机器编号(即 ZXID 最大)的事务,那么就能够保证这个新选举出来的 Leader 一定具有所有已经提交的提案。
而且这么做有一个好处是:可以省去 Leader 服务器检查事务的提交和丢弃工作的这一步操作。
数据同步
当崩溃恢复之后,需要确认是否完成了数据同步。目的是为了保持数据一致。当所有的 Follwer 服务器都成功同步之后,Leader 会将这些服务器加入到可用服务器列表中。
ZXID
任期:完成本次选举后,直到下次选举前,由同一Leader负责协调写入;
事务计数器:单调递增,每生效一次写入,计数器加一。
脑裂
定义
在一个高可用系统中,当联系着的节点断开联系时,本来为一个整体的系统,分裂成两个独立节点,两个节点开始争抢共享资源造成系统混乱、数据损坏的现象,成为“脑裂”。
原因
主要原因: 心跳检测做主备切换时的“不确定性”
当网络原因,导致心跳检测超时,主备切换的情况下,此时slave已经开始提供服务。但是后续之前被判定“死”的master由于网络恢复重新“复活”,此时系统存在两个“主”,发生脑裂问题;
解决方法
仲裁机制
第三方检测服务器,当slave确定准备接管master 时候,manitor会ping以下master,如果master未回复,则判定其死亡。同时master对外提供服务时候,monitor也会定时ping master和slave,保证出现异常情况下,暂停服务器业务操作,进行处理
仲裁机制的主要问题是monitor存在高可用性能瓶颈
分布式授权承诺机制
假定slave已经提供服务,那么对应服务器获得了颁发的lease。假设master仍然在提供服务,则lease必然是过期的,因此请求失效,因此请求也必然失效。
在请求频繁失效情况下,可以通过监控点触发报警,自动或者人工介入,促使老master转换成slave;