很多人对解决分布式系统中一致性问题的难度还没有一个直观的感受,本节详细讲解分布式系统中一致性面临的种种挑战,并且详细说明2PC和3PC这些比较简单的解决方案的原理和存在的问题,为后面引出zk的解决方案做铺垫。

一、前言

分布式系统中,一致性问题是一个比较重要的问题,zookeeper解决的就是分布式系统的一致性问题。下面我们从一致性问题、特定条件下解决一致性问题的两种方法(2PC、3PC)入门,了解最基础的分布式系统理论。

二、一致性

何为一致性问题?简单而言,一致性问题就是相互独立的节点之间如何达成一项决议的问题。分布式系统中,进行数据库事务提交(commit transaction)、Leader选举、序列号生成等都会遇到一致性问题。这个问题在我们的日常生活中也很常见,比如牌友怎么商定几点在哪打几圈麻将.

假设一个具有N个节点的分布式系统,当其满足以下条件时,我们说这个系统满足一致性:

  • 全认同(agreement): 所有N个节点都认同一个结果
  • 值合法(validity): 该结果必须由N个节点中的节点提出
  • 可结束(termination): 决议过程在一定时间内结束,不会无休止地进行下去

有人可能会说,决定什么时候在哪搓搓麻将,4个人商量一下就ok,这不很简单吗?

但就这样看似简单的事情,分布式系统实现起来并不轻松,因为它面临着上一节所说的这些问题:

  • 消息传递异步无序(asynchronous): 现实网络不是一个可靠的信道,存在消息延时、丢失,节点间消息传递做不到同步有序(synchronous)
  • 节点宕机(fail-stop): 节点持续宕机,不会恢复
  • 节点宕机恢复(fail-recover): 节点宕机一段时间后恢复,在分布式系统中最常见
  • 网络分化(network partition): 网络链路出现问题,将N个节点隔离成多个部分
  • 拜占庭将军问题(byzantine failure): 节点或宕机或逻辑失败,甚至不按套路出牌抛出干扰决议的信息

假设现实场景中也存在这样的问题,我们看看结果会怎样:

第一种情况:

我: 老王,今晚7点老地方,搓够48圈不见不散!

……

(第二天凌晨3点) 隔壁老王: 没问题! // 消息延迟

我: ……


第二种情况:

我: 小张,今晚7点老地方,搓够48圈不见不散!

小张: No ……

(两小时后……)

小张: No problem! // 宕机节点恢复

我: ……


第三种情况:

我: 老李头,今晚7点老地方,搓够48圈不见不散!

老李: 必须的,大保健走起! // 拜占庭将军

(这是要打麻将呢?还是要大保健?还是一边打麻将一边大保健……)

还能不能一起愉快地玩耍…

正如上节所说,在分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)中最多只能满足两个。那么,第三个是一定要满足的了。那么强一致性和高可用必定不能同时满足。

对于一致性,2PC、3PC是相对简单的解决强一致性问题的协议,下面我们就来了解2PC和3PC。

三、协调者

在分布式系统中,每一个机器节点虽然都能明确的知道自己执行的事务是成功还是失败,但是却无法知道其他分布式节点的事务执行情况。因此,当一个事务要跨越多个分布式节点的时候(比如,淘宝下单流程,下单系统和库存系统可能就是分别部署在不同的分布式节点中),为了保证该事务可以满足ACID,就要引入一个协调者(Cooradinator)。其他的节点被称为参与者(Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。

四、2PC(二阶提交)

顾名思义它分成两个阶段,先由一方进行提议(propose)并收集其他节点的反馈(vote),再根据反馈决定提交(commit)或中止(abort)事务。我们将提议的节点称为协调者(coordinator),其他参与决议节点称为参与者(participants, 或cohorts)。

在阶段1中,协调者发起一个提议,分别问询各参与者是否接受。只要有一个参与者没有准备好就中止。

值得注意的是,二阶段提交协议的第一阶段准备阶段不仅仅是回答YES or NO,还是要执行事务操作的,只是执行完事务操作,并没有进行commit还是roolback。也就是说,一旦事务执行之后,在没有执行commit或者roolback之前,资源是被锁定的。这会造成阻塞

1)协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。

2)参与者节点执行询问发起为止的所有事务操作,并将Undo信息(用于失败时的回滚)和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)

3)各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。

在阶段2中,协调者根据参与者的反馈,提交或中止事务,如果参与者全部同意则提交,只要有一个参与者不同意就中止。释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)

image

问题:

1、同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

2、单点故障。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

3、数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。

二阶段无法解决的问题:协调者在发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

为了解决这些问题,衍生出了对2PC的改进3PC。我们接下来看看3PC是如何解决这些问题的。

五、3PC(三阶段提交)

3PC最关键要解决的是单点和阻塞。

所以3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommitPreCommitDoCommit三个阶段。在第一阶段,只是询问所有参与者是否可可以执行事务操作,并不在本阶段执行事务操作。当协调者收到所有的参与者都返回YES时,在第二阶段才执行事务操作,然后在第三阶段在执行commit或者rollback

image

5.1 CanCommit阶段

3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

但是此时不执行事务的操作,也就时说不会锁住资源。

5.2 PreCommit阶段

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。此时会执行事务操作和将undo和redo信息记录到事务日志中。如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

5.3 doCommit阶段

加入协调接收到所有参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。

协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。 )

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。

但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

六、总结

了解了2PC和3PC之后,我们可以发现,无论是二阶段提交还是三阶段提交都无法彻底解决分布式的一致性问题。Google Chubby的作者Mike Burrows说过, there is only one consensus protocol, and that’s Paxos” – all other approaches are just broken versions of Paxos. 意即世上只有一种一致性算法,那就是Paxos,所有其他一致性算法都是Paxos算法的不完整版。