2PC
两阶段提交,包含两个角色(协调者与参与者)
分为准备阶段以及提交阶段
准备阶段
- 协调者发送提交请求给各个参与者
- 参与者执行事务,并记下undo,redo日志
- 执行者将执行结果告诉协调者
提交阶段
第一阶段中所有的参与者均执行成功
- 协调者发送提交信息
- 参与者执行事务提交
- 参与者发送提交完成消息
- 执行者向各个参与者发送执行完成消息
第一阶段中存在执行失败的参与者
- 协调者发送回滚信息
- 参与者执行事务回滚
- 参与者发送回滚完成消息
- 执行者向各个参与者发送回滚完成消息
2PC存在的问题
- 同步堵塞:两个事务竞争同
- 单点问题: 协调者在提交阶段如果出事,则参与者会一直持有资源
- 数据不一致:在提交阶段,如果只有一部分commit消息发出去之后,协调者就挂掉了
- 过于保守:任何一个参与者出现问题,都会影响整个事务的进程
3PC
三阶段提交,主要是解决了2PC没有处理的超时响应问题。
canCommit阶段
协调者发送canCommit消息,然后等待参与者响应
参与者收到消息后,根据自身状况(是否可以正常执行事务),返回yes(进入预备状态)或者no
preCommit阶段
均返回yes
- 协调者发送preCommit请求,并进入prepare阶段
- 参与者收到消息后,执行事务,并记录下redo,undo日志
- 参与者若执行成功,返回一个ack,并等待下阶段的指令
存在返回no或者是超过响应等待时间的
- 协调者发送中断请求(abort请求)
- 参与者收到协调者发送的abort请求后(或者超时未得到响应),执行事务中断。
doCommit阶段
执行提交
- 协调者给参与者发送commit消息。
- 参与者提交事务,并释放占有的资源。
- 参与者成功提交后,向协调者发送ack响应。
- 协调者收到所有参与者发送的事务执行成功消息后,完成事务。
执行中断
- 协调者向所有参与者发送abort请求
- 参与者利用二阶段的undo信息执行事务回滚操作,并在完成回滚后释放所有资源
- 参与者完成回滚后,想协调者发送ack响应
- 协调者收到所有参与者的相应后,执行事务中断
3PC存在的问题
- 同步堵塞:竞争相同的资源,会造成堵塞,但是由于存在超时机制,所以可以减少堵塞的时间。
- 数据不一致:在doCommit阶段,如果协调者发送了abort但是参与者等待响应超时自动提交了事务,则会造成不一致。
2PC和3PC比较
3PC的优点
增加了超时机制
在等待precommit阶段如果超时,则中断事务,减少了堵塞的时间,2pc则是一直等待协调者的commit和abort命令。
在等待commit阶段如果超时,则提交事务,因为经过了cancommit和precommit阶段后,事务成功几率大,所以选择相信提交事务。
增加了一个canCommit阶段
主要是为了doCommit阶段的超时自动提交做铺垫,增加了一个阶段之后,可以增加事务的成功概率。第一个阶段已经说明了节点之间通信是没问题的了
PAXOS (帕克索斯)
为了解决这么一个问题
client1,client2,client3分别往node1,node2,node3发送设置x请求的时,如何保证node1,node2,node3上的最终get(x)的结果一致。
解决方案
各个节点之间互相同步日志流(变化过程),而非状态机(结果)。
这样即使节点宕机,只要重启并重放日志流,也能恢复结果。
这种初始状态 + 日志流的同步方式也叫复制状态机。即一样的初始状态 + 一样的日志流 = 一样的最终状态。
因此,最终的解决方案就是复制日志!复制日志=复制任何数据(复制任何状态机)。因为任何复杂的数据(状态机)都可以通过日志生成!
解决过程
如何让三个节点中写入的设置x值的日志完全一致呢
首先已一个递增的日志序号为基础,假设这个递增序号是从0开始的(当然只要是递增即可,没必要规律)
当node1收到消息后,先询问0号位置的日志记录是否被写入,没有则通知node2和node3,这个位置我占了,你们也和我一起把这条记录写进去。(类似2PC模型,先问再做)
当然这个过程也存在问题,当node1和node2在同时向其他节点发出日志写入的请求时,就会出现冲突,多个节点都想占用0号位置的日志记录。‘
basic paxos算法就是用来解决这个同时发起请求的冲突问题的。
basic paxos算法(每一次解决一次提案)
basic paxos下每个节点充当两个角色,分别为proposer(提议)以及acceptor(决议)
上述node1,node2,node3在收到设置x的请求后,分别向其他两个节点发起设置x的值为XXX的日志提议。
下面简单描述一下如何在各个节点之间互相同步日志流的过程。
该过程分为两个阶段,分别为prepare阶段以及accept阶段,
首先是准备工作
- minProposalId = acceptProposalId = 0
- acceptValue = null
接着介绍prepare所做的事情
- Proposer向所有acceptor发出proposal(n),其中n为提案编号,n可以直接以时间戳作为规则。
- 每个acceptor拿到proposal(n)后判断n和当前该acceptor选定的编号的大小关系。
- acceptor回复已经接收过的最大编号以及value,若为空则返回空。(部分acceptor已经接收了某个提案,不可更改)
然后介绍accept阶段做的事
- proposer向所有acceptor发出accept(n,v)。
- acceptor收到accept(n,v)后判断n和minProposalId的大小关系,若大于,则接收该填并返回。
- proposer判断时候收到了超过半数的yes,若有,则完成提案,否则重新开始prepare流程。
这里图画错了,应该是n < minProposalId以及n >= minProposalId
接下来介绍一些流程图中的判断规则
1、当一个acceptor已经接受了一个proposal(n,v)提案后,后续的proposal(a, b) a>n 的提案的值也会被改为v
即:如果某个值为value的提案已经被选定了,那么每个编号更高的被acceptor选中的提案的值也为value
(解决分区问题,这个问题在prepare阶段解决,因为prepare阶段会询问当前每个acceptor的提案接收情况)
其中A2,A3都已经接受了P2的提案,如果P1不先prepare一下,
就不知道其实其他acceptor已经接收了P2的提案,会导致A1接收错误的P1的提案
2、对于任意一个被接收的提案proposal(n,v),一定存在一个数量大于所有acceptor一半的集合S
S中的每个acceptor都没有接受过编号小于N的提案
S中接受过的最大编号提案
但是这个basic paxos算法有个活锁的问题
当两个线程相互提案的时候,在第一个线程未accept时,第二个线程进行了prepare,然后第一个线程再accept,就会导致accept失败。
然后此时若第二个线程挂起,第一个线程prepare,那么第二个线程accept的时候也会失败,长此往复,就会造成两个线程的活锁。
解决方案为
选举一个proposer的leader,只有leader可以提案,其他proposer的提案都应该转发给leader,由leader来发起。
那么如何解决选主时的活锁呢
Proposer执行Accept阶段失败时,不重新获取提案编号重新执行,直接抛弃该提案
Proposer执行Accept阶段失败时,等待一段时间再新获取提案编号重新执行
或者是 accept失败后,等一段时间再发起下次proposal
对于一轮提案是这么处理的,当升级到多轮提案时,有一个改良multi paxos算法
优化了rtt及数据持久化次数
本来prepare和accept阶段都需要一次rtt和一次数据持久化
multi paxos算法在选举出leader后,只会进行一次prepare,后边的提案都是直接accept
函数由
prepare(n)
accept(n,v)
改为
prepare(n,epoch) 一次
accept(n,v,epoch)
当leader故障时,选举出的新的leader会进行一次prepare,道是minProposalId变化,使旧的leader的广播失效