0%

分布式之CAP原则

天子重英豪,文章教尔曹;万般皆下品,惟有读书高。少小须勤学,文章可立身;满朝朱紫贵,尽是读书人。学问勤中得,萤窗万卷书;三冬今足用,谁笑腹空虚。自小多才学,平生志气高;别人怀宝剑,我有笔如刀。朝为田舍郎,暮登天子堂;将相本无种,男儿当自强。学乃身之宝,儒为席上珍;君看为宰相,必用读书人。莫道儒冠误,诗书不负人;达而相天下,穷则善其身。遗子满赢金,何如教一经;姓名书锦轴,朱紫佐朝廷。古有千文义,须知学后通;圣贤俱间出,以此发蒙童。神童衫子短,袖大惹春风;未去朝天子,先来谒相公。年纪虽然小,文章日渐多;待看十五六,一举便登科。大比因时举,乡书以类升;名题仙桂籍,天府快先登。喜中青钱选,才高压俊英;萤窗新脱迹,雁塔早题名。年小初登第,皇都得意回;禹门三级浪,平地一声雷。一举登科目,双亲未老时;锦衣归故里,端的是男儿。玉殿传金榜,君恩赐状头;英雄三百辈,附我步瀛洲。慷慨丈夫志,生当忠孝门;为官须作相,及第必争先。宫殿召绕耸,街衢竞物华;风云今际会,千古帝王家。日月光天德,山河壮帝居;太平无以报,愿上万年书。久旱逢甘雨,他乡遇故知;洞房花烛夜,金榜挂名时。土脉阳和动,韶华满眼新;一支梅破腊,万象渐回春。柳色浸衣绿,桃花映酒红;长安游冶子,日日醉春风。淑景余三月,莺花已半稀;浴沂谁氏子,三叹咏而归。数点雨余雨,一番寒食寒;杜鹃花发处,血泪染成丹。春到清明好,晴天锦绣纹;年年当此节,底事雨纷纷。风阁黄昏夜,开轩内晚凉;月华在户白,何处递荷香?一雨初收霁,金风特送凉;书窗应自爽,灯火夜偏长。庭下陈瓜果,云端闻彩车;争如郝隆子,只晒腹中书。九日龙山饮,黄花笑逐臣;醉看风落帽,舞爱月留人。昨日登高罢,今朝再举觞;菊荷何太苦,遭此两重阳。北帝方行令,天晴爱日和;农工新筑土,天庆纳嘉禾。檐外三竿日,新添一线长;登台观气象,云物喜呈祥。冬天更筹尽,春附斗柄回;寒暄一夜隔,客鬓两年催。解落三秋叶,能开二月花;过江千尺浪,入竹万竿斜。人在艳阳中,桃花映面红;年年二三月,底事笑春风。院落沉沉晓,花开白雪香;一枝轻带雨,泪湿贵妃妆。枝缀霜葩白,无言笑晓凤;清芳谁是侣,色间小桃红。倾国姿容别,多开富贵家;临轩一赏后,轻薄万千花。墙角一枝梅,凌寒独自开;遥知不是雪,为有暗香来。柯干如金石,心坚耐岁寒;平生谁结友,宜共竹松看。居可无君子,交情耐岁寒;春风频动处,日日报平安。春水满泗泽,夏云多奇峰;秋月扬明辉,冬岭秀孤松。诗酒琴棋客,风花雪月天;有名闲富贵,无事散神仙。道院迎仙客,书道隐相儒;庭裁栖凤竹,池养化龙鱼。春游芳草地,夏赏绿荷池;秋饮黄花酒,冬吟白雪诗。 —— 宋.汪洙 《神童诗》

一、概念

      CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP原则指的是这三个要素最多只能同时实现其中两点,不可能三者兼顾。

  1. 一致性
          对于一个将数据副本分布在不同分布式节点上的系统来说,如果对第一个节点的数据进行了更新操作并且更新成功后,却没有是的第二个节点上的数据得到相应的更新。于是在第二个节点上的数据进行读取操作时,获取的依然是旧数据(脏数据),这就是典型的分布式数据不一致的情况。在分布式系统中,如果能够做到针对一个数据项的更新操作执行成功后,所有的用户都可以读取到更新后的值,那么这样的系统就被认为具有严格的一致性(强一致性)。

  2. 可用性
          可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。其中“有限的时间内”是指对于用户的一个操作请求,系统必须能够在指定的时间内返回对应的处理结果。如果超出这个时间范围,系统则被认为是不可用的。“返回结果”是可用性的一个重要指标,要求系统在完成对用户请求的处理后,返回一个正常的响应结果。正常的响应结果通常能够明确的反应出对请求的处理结果,即成功或失败。

  3. 分区容错性
          分区容错性约束了一个分布式系统需要具有如下特性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性的可用性的服务,除非整个网络环境都发生故障。

二、抉择

CAP抉择 说明
放弃P 如果想避免分区容错性问题的发生,一种做法是将所有的数据(与事务相关的)都放在一台机器上。虽然无法100%保证系统不会出错,但不会碰到由分区带来的负面效果。当然这个选择会严重的影响系统的扩展性。
放弃A 相对于放弃“分区容错性“来说,其反面就是放弃可用性。一旦遇到分区容错故障,那么受到影响的服务需要等待一定的时间,因此在等待期间系统无法对外提供服务。
放弃C 这里所说的放弃一致性,并不是完全放弃数据一致性,而是放弃数据的强一致性而保留数据的最终一致性。以网络购物为例,对只剩下一件库存的商品,如果同时接受到了两份订单,那么较晚的订单将被告知商品告罄。

由于当前网络硬件肯定会出现延迟、丢包等问题,所以分区容忍性是我们必须需要实现的,所以只能在一致性和可用性之间进行权衡。

三、解决方案

  1. 两阶段提交2PC

    ①第一阶段:准备阶段(投票阶段)

    • 协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
    • 参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。
    • 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个同意消息;如果参与者节点的事务操作实际执行失败,则它返回一个中止消息。

②第二阶段:提交阶段(执行阶段)

  • 如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则发送提交(Commit)消息。参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源(必须在最后阶段释放锁资源)。接下来分两种情况分别讨论提交阶段的过程:

    当协调者节点从所有参与者节点获得的相应消息都为同意时:
    Commit

    • 协调者节点向所有参与者节点发出正式提交(commit)的请求。
    • 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
    • 参与者节点向协调者节点发送完成消息。
    • 协调者节点受到所有参与者节点反馈的完成消息后,完成事务。

    如果任一参与者节点在第一阶段返回的响应消息为中止,或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
    rollback

    • 协调者节点向所有参与者节点发出回滚操作(rollback)的请求。
    • 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
    • 参与者节点向协调者节点发送回滚完成消息。
    • 协调者节点受到所有参与者节点反馈的回滚完成消息后,取消事务。

    不管最后结果如何,第二阶段都会结束当前事务。

存在的问题

  • 同步阻塞问题:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
  • 单点故障:由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
  • 数据不一致:在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
  • 二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
  1. 三阶段提交3PC
          Three-phase commit,也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。它同时在协调者和参与者中都引入超时机制,并且在第一阶段和第二阶段中插入一个准备阶段以保证最后提交阶段之前各参与节点的状态是一致的。

三阶段提交

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

  • 事务询问:协调者向参与者发送CanCommit请求,询问是否可以执行事务提交操作,然后开始等待参与者的响应。
  • 响应反馈:参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态,否则反馈No

第二阶段:PreCommit,协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能:

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

  • 发送预提交请求:协调者向参与者发送PreCommit请求,并进入Prepared阶段。
  • 事务预提交:参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
  • 响应反馈:如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

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

  • 发送中断请求:协调者向所有参与者发送abort请求。
  • 中断事务:参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

第三阶段:DoCommit,该阶段进行真正的事务提交,也可以分为以下两种情况。

执行提交

  • 发送提交请求:协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
  • 事务提交:参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
  • 响应反馈:事务提交完之后,向协调者发送Ack响应。
  • 完成事务:协调者接收到所有参与者的ack响应之后,完成事务。

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

  • 发送中断请求:协调者向所有参与者发送abort请求
  • 事务回滚:参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
  • 反馈结果:参与者完成事务回滚之后,向协调者发送ACK消息
  • 中断事务:协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

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

2PC与3PC的区别

相对于2PC,3PC主要解决的单点故障问题,并减少阻塞。因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit,而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作,这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

  1. TCC
          TCC,即Try-Confirm-Cancel,是一个类2PC的柔性事务解决方案,由支付宝提出后得到广泛的实践。它又被称为两阶段补偿事务,第一阶段try只是预留资源,第二阶段要明确的告诉服务提供者,这个资源你到底要不要,对应第二阶段的confirm/cancel,用来清除第一阶段的影响,所以叫补偿型事务,其本质上是另辟蹊径达到了和3PC类似的效果。

TCC

Try

  • 完成所有业务检查
  • 预留必须业务资源

Confirm

  • 真正执行业务
  • 不作任何业务检查
  • 只使用Try阶段预留的业务资源
  • Confirm操作满足幂等性

Cancel:

  • 释放Try阶段预留的业务资源
  • Cancel操作满足幂等性

整个 TCC 业务分成两个阶段完成。

  • 第一阶段:主业务服务分别调用所有从业务的try操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的try操作都调用成功或者某个从业务服务的try操作失败,进入第二阶段。
  • 第二阶段:活动管理器根据第一阶段的执行结果来执行confirm或cancel操作。

四、参考

  1. 参考一
  2. 参考二
  3. 参考三