分布式系统理论之一致性、2PC、3PC
分布式系统的特性
- 分布性:分布式系统中的多台计算机之间在空间位置上可以随意分布,系统中的多台计算机之间没有主、从之分,即没有控制整个系统的主机,也没有受控的从机。
- 透明性:系统资源被所有计算机共享。每台计算机的用户不仅可以使用本机的资源,还可以使用本分布式系统中其他计算机的资源(包括CPU、文件、打印机等)。
- 同一性:系统中的若干台计算机可以互相协作来完成一个共同的任务,或者说一个程序可以分布在几台计算机上并行地运行。
- 通信性:系统中任意两台计算机都可以通过通信来交换信息。
和集中式系统相比,分布式系统的性价比更高、处理能力更强、可靠性更高、也有很好的扩展性。但是,分布式在解决了网站的高并发问题的同时也带来了一些其他问题。首先,分布式的必要条件就是网络,这可能对性能甚至服务能力造成一定的影响。其次,一个集群中的服务器数量越多,服务器宕机的概率也就越大。另外,由于服务在集群中分布是部署,用户的请求只会落到其中一台机器上,所以,一旦处理不好就很容易产生数据一致性问题。
分布式(distributed)是指在多台不同的服务器中部署不同的服务模块,通过远程调用协同工作,对外提供服务。
集群(cluster)是指在多台不同的服务器中部署相同应用或服务模块,构成一个集群,通过负载均衡设备对外提供服务。
一致性
-
数据一致性
数据一致性其实是数据库系统中的概念。我们可以简单的把一致性理解为正确性或者完整性,那么数据一致性通常指关联数据之间的逻辑关系是否正确和完整。我们知道,在数据库系统中通常用事务(访问并可能更新数据库中各种数据项的一个程序执行单元)来保证数据的一致性和完整性。而在分布式系统中,数据一致性往往指的是由于数据的复制,不同数据节点中的数据内容是否完整并且相同。 -
一致性模型
(1)强一致性:当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。但是这种实现对性能影响较大。
(2)弱一致性:系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。但会尽可能保证在某个时间级别(比如秒级别)之后,可以让数据达到一致性状态。
(3)最终一致性:弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延迟,系统负载和复制副本的个数影响。DNS是一个典型的最终一致性系统。
2PC
二阶段提交协议主要分为来个阶段:准备阶段和提交阶段。先由一方进行提议(propose)并收集其他节点的反馈(vote),再根据反馈决定提交(commit)或中止(abort)事务。我们将提议的节点称为协调者(coordinator),其他参与决议节点称为参与者(participants, 或cohorts).
首先协调者会询问两个参与者是否能执行事务提交操作。如果两个参与者能够执行事务的提交,先执行事务操作,然后返回YES,如果没有成功执行事务操作,就返回NO。
当协调者接收到所有的参与者的反馈之后,开始进入事务提交阶段。如果所有参与者都返回YES,那就发送COMMIT请求,如果有一个人返回NO,那就返送roolback请求。
阶段一:提交事务请求
事务询问
协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。
执行事务
参与者节点执行事务操作,并将Undo信息和Redo信息写入日志。
各参与者向协调者反馈事务询问的响应
参与者根据事务执行与否向协调者发送Yes或No响应。
阶段二:执行事务提交
阶段二会根据上一阶段的反馈来决定是否可以进行事务的提交,分两种情况:
1. 执行事务提交
假如协调者从所有参与者获得的反馈都是Yes响应,那么就会执行事务提交。
发送提交请求
协调者向所有参与者发出Commit的请求。
事务提交
参与者接收到Commit请求后,正式执行事务提交操作,并在完成后释放在整个事务期间内占用的资源。
反馈事务提交结果
参与者完成事务提交后向协调者发送Ack消息。
完成事务
协调者受到所有参与者反馈的Ack消息后,完成事务。
2. 中断事务
假如任何一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者无法接收到所有参与者的反馈响应,那么就会中断事务。
发送回滚请求
协调者向所有参与者发出Rollback的请求。
事务回滚
参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
反馈事务回滚结果
参与者完成事务回滚后向协调者发送Ack消息。
中断事务
协调者受到所有参与者反馈的Ack消息后,完成事务中断。
值得注意的是,二阶段提交协议的第一阶段准备阶段不仅仅是回答YES or NO,还是要执行事务操作的,只是执行完事务操作,并没有进行commit还是roolback。也就是说,一旦事务执行之后,在没有执行commit或者roolback之前,资源是被锁定的。这会造成阻塞。
存在的问题
2PC在执行过程中可能发生协调者或者参与者突然宕机的情况,在不同时期宕机可能有不同的现象。
情况一:协调者挂了,参与者没挂
这种情况其实比较好解决,只要找一个协调者的替代者(备份节点)。当他成为新的协调者的时候,询问所有参与者的最后那条事务的执行情况,他就可以知道是应该做什么样的操作了。所以,这种情况不会导致数据不一致。
情况二:参与者挂了,协调者没挂
这种情况其实也比较好解决。如果协调者挂了。那么之后的事情有两种情况:
第一个是挂了就挂了,没有再恢复。那就挂了呗,反正不会导致数据一致性问题。
第二个是挂了之后又恢复了,这时如果他有未执行完的事务操作,直接取消掉,然后询问协调者目前我应该怎么做,协调者就会比对自己的事务执行记录和该参与者的事务执行记录,告诉他应该怎么做来保持数据的一致性。
情况三:参与者挂了,协调者也挂了
这种情况比较复杂,我们分情况讨论。
- 协调者和参与者在第一阶段挂了。
由于这时还没有执行commit操作,新选出来的协调者可以询问各个参与者的情况,再决定是进行commit还是roolback。因为还没有commit,所以不会导致数据一致性问题。 -
第二阶段协调者和参与者挂了,挂了的这个参与者在挂之前并没有接收到协调者的指令,或者接收到指令之后还没来的及做commit或者roolback操作。
这种情况下,当新的协调者被选出来之后,他同样是询问所有的参与者的情况。只要有机器执行了abort(roolback)操作或者第一阶段返回的信息是No的话,那就直接执行roolback操作。如果没有人执行abort操作,但是有机器执行了commit操作,那么就直接执行commit操作。这样,当挂掉的参与者恢复之后,只要按照协调者的指示进行事务的commit还是roolback操作就可以了。因为挂掉的机器并没有做commit或者roolback操作,而没有挂掉的机器们和新的协调者又执行了同样的操作,那么这种情况不会导致数据不一致现象。
第二阶段协调者和参与者挂了,挂了的这个参与者在挂之前已经执行了操作。但是由于他挂了,没有人知道他执行了什么操作。
这种情况下,新的协调者被选出来之后,如果他想负起协调者的责任的话他就只能按照之前那种情况来执行commit或者roolback操作。这样新的协调者和所有没挂掉的参与者就保持了数据的一致性,我们假定他们执行了commit。但是,这个时候,那个挂掉的参与者恢复了怎么办,因为他之前已经执行完了之前的事务,如果他执行的是commit那还好,和其他的机器保持一致了,万一他执行的是roolback操作那?这不就导致数据的不一致性了么?虽然这个时候可以再通过手段让他和协调者通信,再想办法把数据搞成一致的,但是,这段时间内他的数据状态已经是不一致的了!
所以,2PC协议中,如果出现协调者和参与者都挂了的情况,有可能导致数据不一致。
3PC
三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。在第一阶段,只是询问所有参与者是否可可以执行事务操作,并不在本阶段执行事务操作。当协调者收到所有的参与者都返回YES时,在第二阶段才执行事务操作,然后在第三阶段在执行commit或者rollback。
阶段一:canCommit
事务询问
协调者向参与者发送canCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
响应反馈
参与者接到canCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态,否则反馈No。
阶段二:preCommit
正常情况下,包含两种可能:
1. 执行事务预提交
发送预提交请求
协调者向参与者发送preCommit请求,并进入prepared阶段。
事务预提交
参与者接收到preCommit请求后,会执行事务操作,并将Undo和Redo信息记录到事务日志中。
响应反馈
如果参与者成功的执行了事务操作,则返回Ack响应,同时开始等待最终指令:Commit或Abort。
2. 中断事务
假如任何一个参与者想协调者反馈了No响应,或者在等待超时之后,协调者无法接收到所有参与者的反馈响应,那么就会中断事务。
发送中断请求
协调者向所有参与者发送Abort请求。
中断事务
无论是收到来自协调者的Abort请求,或是等待协调者请求过程中出现超时,参与者都会中断事务。
阶段三:doCommit
同样存在两种情况:
1.执行提交
发送提交请求
协调接收到参与者发送的Ack响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
事务提交
参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
响应反馈
事务提交完之后,向协调者发送Ack响应。
完成事务
协调者接收到所有参与者的Ack响应之后,完成事务。
2.中断事务
发送中断请求
协调者向所有参与者发送Abort请求
事务回滚
参与者接收到Abort请求之后,利用其在阶段二记录的Undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
反馈结果
参与者完成事务回滚之后,向协调者发送Ack消息
中断事务
协调者接收到参与者反馈的Ack消息之后,执行事务的中断。
需要注意的是,一旦进入第三阶段,可能存在以下两种故障:
协调者出现问题
协调者和参与者之间的网络出现问题
无论哪种情况出现,最终都会导致参与者无法及时接收到来自协调者的doCommit或是abort请求,针对这样的异常情况,参与者都会在等待超时之后,继续进行事务提交。
3PC解决协调者和参与者都挂的情况:
第二阶段协调者和参与者挂了,挂了的这个参与者在挂之前已经执行了操作。但是由于他挂了,没有人知道他执行了什么操作。
这种情况下,当新的协调者被选出来之后,他同样是询问所有的参与者的情况来觉得是commit还是roolback。这看上去和二阶段提交一样啊?他是怎么解决一致性问题的呢?
看上去和二阶段提交的那种数据不一致的情况的现象是一样的,但仔细分析所有参与者的状态的话就会发现其实并不一样。我们假设挂掉的那台参与者执行的操作是commit。那么其他没挂的操作者的状态应该是什么?他们的状态要么是prepare-commit要么是commit。因为3PC的第三阶段一旦有机器执行了commit,那必然第一阶段大家都是同意commit。所以,这时,新选举出来的协调者一旦发现未挂掉的参与者中有人处于commit状态或者是prepare-commit的话,那就执行commit操作。否则就执行rollback操作。这样挂掉的参与者恢复之后就能和其他机器保持数据一致性了。
简单概括一下就是,如果挂掉的那台机器已经执行了commit,那么协调者可以从所有未挂掉的参与者的状态中分析出来,并执行commit。如果挂掉的那个参与者执行了rollback,那么协调者和其他的参与者执行的肯定也是rollback操作。
所以,再多引入一个阶段之后,3PC解决了2PC中存在的那种由于协调者和参与者同时挂掉有可能导致的数据一致性问题。