在分布式系统中,一个事务可能涉及到集群中的多个节点。单个节点很容易知道本身执行的事务成功仍是失败,但由于网络不可靠难以了解其它节点的执行状态(可能事务执行成功但网络访问超时)。算法
若部分节点事务执行失败进行回滚,而其它节点完成事务提交,则事务会处于部分完成的不一致状态。为了不错误,分布式系统须要使用分布式一致性协议来保证分布式事务的执行。网络
2PC
两阶段提交(2-Phase Commit, 2PC)是一种比较简单的分布式一致性协议。分布式
2PC协议中,每一个事务须要一个协调者来协调各个参与者。每一个事务分为两步执行。学习
- 阶段一: 事务请求
- 协调者向全部参与者发送事务内容,询问是否能够执行事务操做。
- 各参与者执行事务,写事务日志但不进行提交。 各参与者锁定事务相关的资源,保证事务能够正常提交。
- 各参与者向协调者返回响应,YES表示能够提交,NO表示不能够提交。若协调者收到全部参与者的YES回复,则准备进行事务提交。如有参与者回复NO或者超时,则准备回滚事务。
- 阶段二: 提交事务
- 协调者向全部参与者发送提交请求
- 参与者正式提交事务,并在完成后释放相关资源。
- 参与者协调者回复ACK,协调者收到全部参与者的ACK后认为事务提交成功。
- 回滚事务
- 在事务请求阶段如有参与者回复NO或者超时,协调者向全部参与者发出回滚请求
- 各参与者执行事务回滚,并在完成后释放相关资源。
- 参与者协调者回复ACK,协调者收到全部参与者的ACK后认为事务回滚成功。
2PC是一种简单的一致性协议,它存在一些问题:设计
- 单点服务: 若协调者忽然崩溃则事务流程没法继续进行或者形成状态不一致
- 没法保证一致性: 若协调者第二阶段发送提交请求时崩溃,可能部分参与者受到COMMIT请求提交了事务,而另外一部分参与者未受到请求而放弃事务形成不一致现象。
- 阻塞: 为了保证事务完成提交,各参与者在完成第一阶段事务执行后必须锁定相关资源直到正式提交,影响系统的吞吐量。
参与者在完成阶段一的事务执行后等待协调者的下一个请求,若协调者超时则能够自行放弃事务。日志
这种方案仍然有没法保证一致性的缺点,但并不会出现某些资料所述一直锁定资源,没法继续的状况。进程
3PC
三阶段提交协议(3-Phase Commit, 3PC)进一步将事务请求分为两个阶段,能够解决2PC协议阻塞的问题但没法解决单点服务和不一致的问题。事务
3PC协议下事务分三步提交:资源
- CanCommit
- 协调者向全部参与者发送CanCommit请求
- 各参与者判断是否能够完成事务提交,但不执行事务也不锁定资源
- 各参与者根据是否能够完成事务向协调者回复YES或NO
- PreCommit
- 协调者向全部参与者发送PreCommit请求,执行事务预提交
- 各参与者执行事务,写事务日志但不进行提交。 各参与者锁定事务相关的资源,保证事务能够正常提交。
- 各参与者向协调者返回响应。若协调者收到全部参与者的YES回复,则准备进行事务提交。如有参与者回复NO或者超时,则放弃事务。
- DoCommit
- 协调者向全部参与者发送提交请求
- 参与者正式提交事务,并在完成后释放相关资源。
- 参与者协调者回复ACK,协调者收到全部参与者的ACK后认为事务提交成功。如有参与者回复NO或者超时,则回滚事务。
- 参与者进入 PreCommit 状态后,若始终未收到协调者的 DoCommit 请求则会超时后自动执行提交。
三阶段提交协议在CanCommit阶段不锁定资源,解决了阻塞下降吞吐量的问题。开发
若某个参与者进入 PreCommit 后始终未收到协调者的进一步指令则会自动提交,该策略必定程度上避免协调者单点服务问题。
可是 3PC 仍然没法解决数据不一致问题。
Paxos
Paxos 算法的目的在于使分布式系统对于某个值达成一致,好比 Master 选举过程当中保证最终全部节点对 Master 身份达成共识。
做者认为 Paxos 解决的分布式共识问题与分布式事务有着较大不一样。
Paxos 认为信道可能丢失数据可是不会篡改数据(即不存在拜占庭将军问题),实际上咱们也很容易经过校验检查数据是否被篡改。
在介绍Paxos算法以前,咱们先来分析2PC(3PC)协议在分布式共识问题上的不足。
2PC(3PC)协议要求收到全部参与者的 ACK 消息后才认为提交成功,而在Master选举这类分布式共识问题上只须要过半参与者达成一致便可。
而最难以解决的问题在于协调者的单点服务问题,若协调者在过程当中崩溃则集群很难继续达成共识。
所以,关键在于设计在有多个协调者的状况下仍然能够达成共识的协议。
Basic Paxos
Paxos算法中有3个角色:
- Proposer: 负责发起提案,相似于2PC中的协调者
- Acceptor: 负责批准提案,相似于2PC中的参与者
- Learner: 不参与提案过程,只从其它Acceptor那里学习已经过的提案。
咱们重点介绍 Proposer 和 Acceptor 参与的流程,暂时不介绍 Learner。
在集群中每一个进程(节点)可能会扮演其中多个角色。
提案由编号N和值V组成记做(N, V), 每一个提案都的编号N是惟一的。保证编号惟一很是简单,若集群中有k个 Proposer, 那么第i个Proposer提出的第n个提案编号为 i + k * n。
咱们但愿集群最终能够选中一个V,且全部节点知道集群最终选定的V值。
算法作出几个规定:
- 只要集群中有超过半数的Accpetor批准了提案,Proposer 就能够认为集群对接受了提案
- 在一轮投票中,Acceptor老是批准它收到的第一个提案
- 在一轮投票中,Acceptor能够批准多个提案,可是批准提案的值V必须相同
算法分为两个阶段:
- prepare 阶段
- Proposer 选择提案N,向半数以上Acceptor发送请求Prepare(N)
- Acceptor 保存本身受到过的最大请求的编号 maxN 和已接受的编号最大提案 (acceptedN, acceptedV)。
- 若 maxN > N, 那么 Acceptor 返回拒绝响应
- 若 maxN < N, 那么 Acceptor 返回已接受的编号最大提案(acceptN, acceptV),若还没有接受过提案则返回空的成功响应。同时,Acceptor 更新 maxN, 即不会在接受编号小于N的请求
- accept 阶段
- 若 Proposer 收到过半 Acceptor 对 Prepare(N) 返回的ACK响应,那么它会从响应的提案中选出编号最大的一个(acceptN, acceptV), 若响应中不包含提案则由 Proposer 决定提案。决定提案后 Proposer 会向过半 Acceptor 发送 Accept(N, V)请求。
- Acceptor 收到 Accept(N, V) 请求后
- 若 maxN > N, 那么 Acceptor 返回拒绝响应
- 若 maxN < N, 那么 Acceptor 返回成功响应,并更新已接受的编号最大提案 (acceptedN, acceptedV)
- 若 Proposer 未收到过半 Acceptor 对 Accept(N, V) 请求的成功响应,则认为提案被拒绝。
若集群中存在两个 Proposer 依次提出编号递增的提案可能会使 Paxos 算法陷入死循环:
- Proposer1 提出提案 N1, 并收到过半Prepare(N1)响应
- Proposer2 提出提案 N2 (N2 > N1), 并收到过半Prepare(N2)响应
- Proposer1 进入第二阶段, 过半Accept(N1)请求被拒绝 (过半Acceptor 的 maxN = N2)。 Proposer1 提出提案 N3 (N3 > N2) ...
这种状况称为算法陷入活锁,在工程实践中咱们一般选择一个 Proposer 做为 leader。
Paxos 算法能够在数学上证实它的正确性深刻浅出Paxos算法协议。
Paxos 算法实现难度和运行开销很是大,所以开发出 Raft、ZAB等协议用于生产实践。