再谈2PC和3PC

之前的一篇文章感觉分析得不太完整,所以再记录点东西。

故障组合情况

对于多个节点且每个节点有多个可能状态参与的分布式系统来说,假设在有限的某个时间点上发生故障的概率为0,对于coordinator(proposer/master/leader等),在发送接收的一轮交互中,可能在发送消息前(t < t1),发送部分消息(t1 < t < t2),发送所有消息后并且接收消息前(t2 < t < t3),接收到部分消息(t3 < t < t4),接收到所有消息后发生故障(t > t4);对于每个participant(acceptor/follower/slave)来说,在接收发送的一轮交互中,可能在接收消息前(t’ < t1’),接收到消息且未发送应答(t1’ < t’ < t2’),发送应答后(t’ > t2’)发生故障。

coordinator和participants在不同的时间段发生故障的组合会有不同的能够保持全局事务状态一致的故障发生时行为以及恢复策略,而且可能不存在能保持全局事务状态一致的相应行为以及恢复策略。对于发生故障时的行为,在程序实现上我们必须用上面提到的时间段来分析,而且假定coordinator广播消息这个动作的过程中不会出现故障(这其实是比较合理的,因为即使只发送了部分消息也可以看做是有一部分participants没有收到消息,这两种情况对于最终的系统全局状态是一样的),这样程序实现上相应能简化不少。而对于故障恢复的策略以及正确性,我们可以从有节点发生故障后最终整个系统可能处于的全局状态来详细分析论证,虽然对于n个参与节点来说,其状态组合指数级增长,但是其中大多数状态可以用全称量词和存在量词描述,因为很多状态对于恢复策略是一样的。下面以2PC和3PC为例来分析,从中可以比较容易地看出2PC存在的问题,以及3PC为什么能够解决这个问题。2PC和3PC的正常流程可以参考相应的资料,这里不赘述。

故障模型

首先我们这里只考虑fail-recover的故障模型

我们只考虑在有coordinator以及participant挂掉的情况,而且coordinator本身不具有participant的身份。对于没有participant挂掉但是coordinator挂掉的情况,只需要选择新的coordinator并向所有存活的participant发送最后一条日志记录的请求就可以确定发生故障时全局事务的状态,从而恢复,所以比较简单。对于有paticipant挂掉以及coordinator没有挂掉的情况,由于coordinator知道所有participants的响应消息,所以可以决定此次事务的最终状态,可能会阻塞等待participant的恢复,但不会造成不一致。

举一个coordinator没有故障但是paticipant故障的例子:对于2PC的阶段一(即有部分participant还未收到coordinator的proposal消息),如果coordinator未发生故障,但是有participant发生故障,这种情况下,只需要取消此次proposal即可,等到故障的participant恢复后询问coordinator要相应的日志记录,不会造成最终全局事务状态的不一致。(这里关于整个系统能不能progress可能有不同说法,如果我们将故障的participant移除coordinator活跃列表那么接下来的事务(如果这里的事务只是单纯的replication)可以正常进行,但是如果分布式事务本身必须要故障的participant参与,那么整个系统必须阻塞直到participant恢复,但总之不会造成恢复后系统全局状态的不一致)。


2PC故障恢复分析

下面考虑coordinator和participant故障的情况:

  • 1.对于2PC的阶段一(即有部分participant还未收到coordinator的proposal消息)

    • 此时新选出的coordinator询问剩余存活节点的消息后可以直接cancel,因为不可能有节点commit
  • 2.对于2PC的阶段二,情况稍微复杂,故障发生时,所有剩余存活节点可能的状态只能是accept/refuse/commit/abort中的一个,并且只有以下组合
    • (1).存活节点中返回accept的数量满足0 <= n < N(存活节点总数)

      • a. n中除去accept的剩余全是commit => commit
      • b. n中除去accept的剩余全是abort => abort
      • c. n中除去accept的全是refuse => abort
      • d. n中除去accept的剩余部分是abort,部分是abort => abort
    • 以上几种情况下新的coordinator的abort/commit选择在故障节点恢复后都不会造成不一致。
    • (2).存活节点全部返回accept,即n == N
      • 此时故障的participant可能处于的状态有:

        • a. accept
        • b. refuse
        • c. commit
        • d. abort
    • 可以看出,无论新的coordiantor选择commit还是abort,最终participant恢复时有可能是abort或者commit,这样会导致不一致,所以整个系统只有等故障participant恢复之后,新的coordinator才可能继续,整个系统才可能progress。这也是导致2PC缺陷的根本原因。
    • 综合(1)(2)两种情况,在(2)中由于故障的节点可能成为唯一接收到commit/abort消息的节点,所以从剩余节点中我们没办法知道整个系统的状态。因此3PC引入了prepare-commit阶段,在真正提交(commit阶段)之前,让所有节点都能知道整个系统的状态是可以提交(即coordinator收到所有accept)还是cancel(abort,即coordinator没有收到所有accept),然后在commit阶段,如果有节点挂掉了,也可以通过其他其他节点得知整个系统此次事务投票的状态,从而progress。

3PC故障恢复分析

  • 1.对于3PC的阶段一(即有部分participant还未收到coordinator的proposal消息)

    • 此时新选出的coordinator询问剩余存活节点的消息后可以直接cancel,因为不可能有节点commit
  • 2.对于3PC的阶段二和阶段三,情况比较复杂,故障发生时,所有剩余存活节点可能的状态只能是accept/refuse/prepare-commit/cancel/commit中的一个,并且只有以下组合
    • (1).存活节点中返回accept的数量满足0 <= n < N(存活节点总数)

      • a. n中除去accept的全是refuse => abort
      • b. n中除去accept的全是cancel => abort
      • c. n中除去accept的部分是refuse,部分是cancel => abort
      • d. n(==0)中除去accept的全是prepare-commit => commit
      • e. n(==0)中除去accept的全是commit => commit
      • f. n(==0)中除去accept的部分是commit,部分是prepare-commit => commit
    • 可以看出,上述所有情况,新的coordinator都可以有确定的abort/commit选择,不会造成故障节点恢复后整个系统的不一致。
    • (2).存活节点全部返回accept,即n == N
      • 此时故障节点可能处于的状态有:
        • a. accept
        • b. refuse
        • c. prepare-commit
        • d. cancel
        • e. 不可能有commit(如果是commit那么必然所有存活的都是prepare-commit,这样就避免了2PC存在的问题!)
    • 可以看出,3PC引入prepare-commit阶段后,(2)中解决了2PC的问题。(2)中a,b,c,d四种可能情况下由于不可能出现故障节点commit的情况,所以新的coordinator都可以采取abort,从而在故障节点恢复后不会造成不一致状态。但是3PC的一个局限在于无法容忍网络分区:比如如果发生了网络分区,其中一部分的coordinator收到那一部分所有存活节点都是prepare-commit,那么会决定commit;但是另外一部分的coordinator收到的存活节点中全是accept,那么会决定abort。这样导致了整个系统状态的不一致。

总结

本文对于每种恢复情况都做了一定考虑,对于只有一个coordinator和participant的情况,我们可以画出系统的全局状态图,从而判断不同故障组合是否会导致状态转换的不确定结果,即最终的全局状态既有commit又有abort,上述的分析本质上也是将一些状态分了类。但是对于多节点的组合,感觉始终没有太严格地形式证明,在思考代码实现的时候也是总感觉不具有百分之百的说服力…状态组合爆炸也是并发与分布式的一个比较难的问题吧。

时间: 2024-10-28 01:57:06

再谈2PC和3PC的相关文章

再谈IO的异步,同步,阻塞和非阻塞

原本转过一个<六种Socket I/O模型幽默讲解>,里面用比喻的方法讲解各种IO,但说到底那个时候我对同步异步这些还是只知其表.还未能完全理解异步和同步,现在觉得清晰一些了.总结一下. 前提概要: IO的过程: 整个IO的过程其实是应用发起IO的请求,到应用获取到IO请求数据的中间过程. 这个中间,其实主要的时间就是系统准备数据的过程.这也是异步技术的优化所在. 对系统调用的理解: 首先,我们要明确一点.IO的操作属于一种系统调用.也就是应用在运行中,进入到内核代码来执行某些重要的操作. 其

再谈ASP防止SQL Injection漏洞的问题

问题 再谈ASP防止SQL Injection漏洞的问题 /**作者:慈勤强Email: cqq1978@Gmail.com*/ 关于Asp的SQL Injection预防问题,似乎已经没什么可说的了.在我做的Asp的项目里面, 都是用自己写的函数来处理客户端提交进来的数据,我的Blog里面也贴过这个函数. 具体可以参考http://blog.csdn.net/cqq/archive/2004/09/23/113786.aspx 不过,从朋友的留言和网上其他的一些讲如何防范SQL Injecti

用户交互设计:再谈人机交互中的设计隐喻

文章描述:再谈人机交互中的设计隐喻. 上篇文章<人机交互中的设计隐喻-由Mac的文件替换引出来的话题>发出来以后收到了各种各样的反馈,我索性再写一篇续文,算是集中答复吧. 用户习惯 在所有的反馈中,"用户觉得Windows的做法更好用,所以理应这样设计"的说法可谓最多.那么我们就来看一下,为什么有人会觉得Windows的做法更"好用". 我们来看两个例子. 银行里面用的系统-就是柜台后面业务人员用的那个-基本上还是字符界面,没有漂亮的图标和窗口,甚至可能

网页设计经验分享:再谈网易首页的改版

之所以说再谈网易首页的改版,是因为去年临近奥运会的时候,网易首页做过一次改版,奥运会之后网易首页做了一下小调整,调整后的首页让人感到很困惑.今天在没有任何预告的前提下,网易又上线了新首页. 其间有个小插曲是,新首页刚上线后,很多地区突然无法访问网易首页,我访问的时候是服务器返回500. 网易就此次改版的说明是:"本次改版从整体到细节都有一个质的飞跃,新版继承了网易一贯简约.大气.包容的设计品质,达到以用户为中心提高用户体验.促进频道发展的改版设计目的." 此次改版促进频道发展的目的似乎

走近VB.Net(二) 再谈函数调用

函数 走近VB.Net(二) 再谈函数调用 在VB6中如果你想调用一个对话框,首先你知道要使用vb内置的MsgBox函数,你甚至于使用API,大部分人乐于使用API.如下:Public Declare Function MessageBox Lib "user32" Alias "MessageBoxA" (ByVal hwnd As Long, ByVal lpText As String, ByVal lpCaption As String, ByVal wTy

再谈CMOS密码

对于CMOS而言,相信大家已经不再陌生.但就CMOS密码而言,我想真正了解的人就不太多了,所以我们就做了些实验,研究了一下.以前已经有不少人讨论过了,但我觉得还是有再谈的必要,下面就把其中合适的部分拿出来,以飨各位. 在谈密码之前,还是先说说什么是CMOS(本文所言CMOS均针对Award而言).CMOS实际上存放的是计算机的系统时钟和硬件配置方面的一些信息,供系统引导时读取:同时初始化计算机各个部件的状态,总共有128个字节,存放在RAM芯片中. 好了,先看一个例子,用来向大家说明一下CMOS

再再谈java乱码:GBK和UTF-8互转尾部乱码问题分析(续)

GBK字节码用UTF-8解码 UTF-8 的编码规则 转码实例 解决问题 jdk 18 测试 jdk 1617 jdk 版本的影响 小结 参考 在<再谈java乱码:GBK和UTF-8互转尾部乱码问题分析>我们分析了,如果从一个UTF-8 的字节序列,经过 new String(b,"GBK") 的操作,"可能"(与总字节数有关)会破坏数据.结果可能是,损失最后一个"字". 反过来呢?可能会很惨,大范围溃散... 同时,可参考:一段j

【Go语言】【13】再谈GO语言的结构体

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://qingkechina.blog.51cto.com/5552198/1671842 本文从如下四个方面再领着大家认识结构体 匿名结构体和匿名成员的结构体 值传递和引用传递 再谈嵌套结构体 面向对象 1.匿名结构体和匿名成员的结构体 如上篇所述,一个结构体需要先声明,再初始化,最后把初始化后的结构体赋值给其它变量,例如: /*声明结构体*/ type employee struc

[译]再谈如何安全地在 Android 中存储令牌

本文讲的是[译]再谈如何安全地在 Android 中存储令牌, 原文地址:A follow-up on how to store tokens securely in Android 原文作者:Enrique López Mañas 译文出自:掘金翻译计划 译者: lovexiaov 校对者:luoqiuyu hackerkevin 作为本文的序言,我想对读者做一个简短的声明.下面的引言对本文的后续内容而言十分重要. 没有绝对的安全.所谓的安全是指利用一系列措施的堆积和组合,来试图延缓必然发生的