分布式事务设计-三段式提交

前面的文章提到的两段式提交,已经能够很好得解决分布式事务了,然而两阶段在即使只有一个进程发生故障时(协调员发生故障了),那么整个系统中也会存在较长时间的block(参与者进程需要block直至协调员恢复,或者与其他参与者进程进行协同终止协议,直至获得决定)。但如果只有个别进程发生故障,就要长时间得被block住这就令人无法忍受,这个block时间能否减少。于是,3段式提交设计出现。在实际应用中,由两阶段引起的block的情况很少见,因此即使被block住了,时间稍微长一点的block开销也是可以接受的。因此两阶段是实际系统中经常被采用的方法。但三阶段作为一种理论上的设计也很有借鉴意义。

三阶段基础架构

假设系统中只会有服务器进程发生故障,但不会有通信故障,也就是说这是一个理想的强连通网络拓结构,任何站点的故障都不会影响另外两点的链路通信。这个假设意味着什么呢?意味着如果一个服务器进程p,它在等待来自q进程的消息时发生了timeout,那么我们就可以知道q发生了故障,其它任何进程也无法和q进行通信。

在两阶段中,如果一个可工作的参与者进程处于不确定周期,那么它就都处于block状态。由于timeout,它其实此时已经知道,此时协调员已经故障了(由于网络拓扑结构的强连通性假设),但此时它也不能单方面得决定ABORT,因为有可能协调员在故障之前已经做出了COMMIT的决定,并且有的参与者进程已经开始实施达成该决定,说不定COMMIT操作都已经完成了。

如果任何一个可工作的进程处在不确定状态,那么任何进程(不管是可正常工作的还是已经发生故障的)都不可能确定该决定。

有了这个特性,就可以解决进程中的block问题,设想一下,如果一个处于不确定状态下的进程timeout了,在两阶段下它有两种选择,要么继续等待直至恢复与协调员的通讯,要么与其他参与者进行协同终止协议。这两种选择都需要该进程继续block。而如果具有了三阶段特性,那么该进程知道此时整个系统的其他任何进程都没有真正得开始实施事务操作(具体的COMMIT或者ABORT操作),因此进程可以直接ABORT ACCEPT,无需继续block等待。发生故障的进程恢复后,也能够得知:原来在我故障的时候,大家都决定ABORT了,我知道了,我该干嘛干嘛去了。

其思路很简单。两阶段为什么不满足?来看一下,在参与者还都是不确定状态时,协调员发送了COMMIT DECISION消息。如果一个参与者进程p先收到了该消息,另一个参与者q此时还未收到该消息,那么p马上就开始实施达成这个决定(申请锁,往数据库里写数据等等一系列操作紧锣密鼓得进行着),但此时q还在处在不确定状态,静悄悄得毫无声息。而三阶段则巧妙得避免了这种情况,它增加了一个阶段,在收到了所有的参与者的YES投票之后,协调员发送一个PRE-COMMIT消息给所有的参与者,当某一参与者进程收到PRE-COMMIT消息之后它得知其他进程已经协商出了COMMIT决定,于是他就可以脱离不确定状态了。但是需要注意的是,此时p并不进行任何实质具体的决定达成实施工作,他只是做出承诺,如果自己一切正常,那么它将会在未来实施达成该决定。

每个参与者进程都会回复确认这个PRE-COMMIT。当协调员已经收到了所有参与者进程的PRE-COMMIT确认后,它就可以确认已经没有参与者进程还处在不确定期内。然后它就发送COMMIT决定到所有的参与者进程,然后参与者进程就可以开始实施达成该决定了。此刻没有参与者进程处在不确定期了,决定当然可以开始实施达成了。

如果参与者投票是NO,三阶段的处理方式就和两阶段相同。三阶段的协议框架如下:

1. 协调员进程发送投票请求给所有的参与者进程

2. 当一个参与者进程收到了投票请求,它会根据自己的情况回应YES 或者NO。如果参与者回应NO,那么参与者实施ABORT ACCEPT的决定,然后停止该参与者事务进程。(为什么要停止参与者进程呢?因为实际实现的时候,每个事务往往是分配了一个专门的进程来处理的,停止了进程也就意味着该事务的处理停止了)。

3. 协调员进程收集所有来自参与者进程的投票信息,如果参与者进程有人投了NO,或者协调员进程本身基于某种考虑也投了NO,那么协调员进程就发送ABORT DECISION给所有偷了YES的参与者进程,各个参与者进程接受了ABORT DECISON决定,实施该决定需要进行的操作(释放锁啊等等),然后停止参与者进程。否则协调员进程就发送PRE-COMMIT信息给所有的参与者进程。

4. 一个投了YES的参与者进程等待来自协调员的PRE-COMMIT或者ABORT决定消息。如果收到了ABORT,那么就实施该决定并停止该事务进程。如果收到了PRE-COMMIT,那么参与者进程就回复消息给协调员。

5. 协调员收集所有的消息,如果所有的参与者进程都回复消息,那么协调员进程就发送COMMIT消息给所有的参与者进程,然后停止该事务的协调员进程的工作。

6. 参与者进程等待来自协调员的COMMIT消息,收到消息之后就开始实施该决定然后停止该事务的实施进程。

这就是三阶段设计,当然它实施起来也需要考虑Timeout,终止协议,日志恢复等一系列细节问题,这些细节大家可以参考之前两阶段的文章。三阶段相对于两阶段减少了由故障引起的block的等待时间,但它增加了网络开销。如果一个系统的故障率不是那么高,两阶段还是要优于三阶段的。实际上大多数的系统的故障率的开销还是比较小的,这也是大部分的分布式事务协议(本质上也是一种分布式一致性协议,因为它使得各个服务器都实施达成一个一致的决定)实现还是基于两阶段的较多。两阶段和三阶段也是理解一致性算法的一个基础。

猜你喜欢

转载自yale.iteye.com/blog/1541527