分布式与一致性协议之CAP(三)

CAP

ACID理论:CAP的"酸",追求一致性。

提到ACID,它很容易理解,在单机上实现也不难,比如可以通过锁、时间序列等机制保障操作的顺序执行,让系统实现ACID特性。但是一说要实现分布式系统的ACID特性比较难实现呢?

ACID理论是对事务特性的抽象和总结,方便我们实现事务。可以这样理解:如果实现了操作的ACID特性,那么旧实现了事务。二大多数人觉得比较难,是因为分布式系统涉及多个节点间的操作。加锁、时间序列

等机制只能保证单个节点上操作的ACID特性,无法保证节点间操作的ACID特性。那么怎么做才会让实现不那么难呢?答案是通过分布式事务协议实现,比如二阶段提交协议和TCC(Try-Confirm-Cancel),不过在介绍二阶段提交协议和TCC之前,咱们先继续看看苏秦的故事,看这回苏秦又遇到了什么事。

最近呢,秦国按耐不住自己躁动的新,开始骚扰魏国边境,魏王头疼,向苏秦求助,苏秦认为"三晋一家亲",建议魏王联合赵、韩一起对抗秦国。但是这三个国家实力都很弱,需要大家都同一联合,一致行动,如果有

任何一方不方便行动,就取消整个计划。根据侦察情况,明天发动反攻的胜算比较大。所以苏秦想协调赵、魏、韩明天一起行动,如图所示,那么对于苏秦来说,他面临的问题是,如何高效协同赵、魏、韩一起行动,

并且保证当有一方不方便行动时,取消整个计划。苏秦面临的这个新问题,就是典型的如何实现分布式事务的问题。赵、魏、韩明天攻打秦国,这三个操作组成一个分布式事务,要么全部执行,要么全部不执行。

了解了这个问题之后,我们来看看如何通过二阶段提交协议和TCC帮助苏秦解决这个难题

二阶段提交协议

二阶段提交协议,顾名思义,就是通过二阶段的协商来完成一个提交操作,那么具体是怎么操作的呢?

首先,苏秦发消息给赵,赵接收到消息后就扮演协调者(Coordinator)的身份联系魏和韩发起二阶段提交,如图所示。

赵发起二阶段提交后,先进入提交请求阶段(又称投票阶段)。为了方便演示,我们先假设赵、魏、韩明天都能去攻打秦国,大致步骤如图所示。

也就是说,第一步,赵分别向魏、韩发送消息:"明天攻打秦国,方便么?"

第二步,赵、魏、韩分别评估明天能否去攻打秦国,如果能,就预留时间并锁定,不再安排其他军事活动

第三步,赵得到全部的回复结果(包括他自己的评估结果),都是YES

赵收到所有回复后,进入提交执行阶段(又称完成阶段),大致步骤如图所示

首先,赵按照"要么全部执行,要么放弃"的原则,统计投票结果,因为所有的回复结果都是YES,所以赵决定执行分布式事务:明天攻打秦国。

然后,赵通知魏、韩:"明天攻打秦国"。接到通知之后,魏、韩执行事务,明天攻打秦国。最后,魏、韩执将执行事务的结果返回给赵。

这样依赖,赵就将事务执行的结果(也就是赵、魏、韩明天一起攻打秦国)返回给苏秦,那么,这时苏秦就解决了问题,协调好了明天的作战计划。

在这里,赵采用的方法就是二阶段提交协议,在这个协议中:

  • 1.可以将"赵明天带兵攻打秦国、魏明天攻打秦国、韩明天带兵攻打秦国"理解成一个分布式事务操作
  • 2.可以将赵、魏、韩理解为分布式系统的3个节点,其中,赵是协调者。将苏秦理解为业务,也就是客户端
  • 3.可以将消息理解为网络消息
  • 4.可以将"评估明天是否方便,预留时间"理解为评估事务中选哟操作的对象和对象状态,是否准备好,能否提交新操作。

需要注意的是,在第一个阶段,每个参与者投票表决事务是放其还是提交。一旦参与者投票要求提交事务,那么就不允许放弃事务。也就是说,在一个参与者投票要求提交事务之前,它必须保证能够执行提交协议中

它自己的那一部分,即是参与者出现故障或者中途被替换掉。这个特性是我们需要在代码实现时保障的。

还需注意的是,在第二个阶段,事务的每个参与者执行最终统一的决定,提交事务或者放弃事务。这个约定是为了实现ACID中的原子性。

二阶段提交协议最早是用来实现数据库的分布式事务的,不过现在最常用的是XA协议。XA协议是X/Open国际联盟基于二阶段提交协议提出的,也叫X/Open DTP(Distributed Transaction Processing)模型,比如

MySQL就通过MySQL XA实现了分布式事务(MySQL中的XA事务需要将事务隔离级别设置为串行化)。

但是不管是原始的二阶段提交协议,还是XA协议都存在一些问题:

  • 1.在提交请求阶段,需要预留资源,在资源预留期间,其他人不能操作(比如XA协议在第一阶段会将相关资源锁定):
  • 2.数据库是独立的系统。
    因为上面这两点,我们无法根据业务特点弹性地调整锁地粒度,而这些都会影响数据库地并发性能。那用什么办法可以解决这些问题呢?答案就是TCC

TCC

TCC是Try(预留)、Confirm(确认)、Cancel(撤销)3个操作的合称,它包含了预留、确认(或撤销)两个阶段。那么如何使用TCC协议解决苏秦面临的问题呢?

首先,我们进入预留阶段,大致步骤如图所示

第一步,苏秦分别通知赵、魏、韩预留明天的时间和相关资源。然后诉求你注册确认操作(明天攻打秦国)和撤销操作(取消明天攻打秦国)。

第二步,苏秦收到赵、魏、韩的预留答复,都是Success.

如果预留节点的执行都没有问题,则进入确认阶段,大致步骤如图所示

第一步,苏秦执行确认操作,通知赵、魏、韩攻打秦国

第二步,收到确认操作的响应,完成分布式事务。

如果预留阶段执行出错,比如赵的一部分军队还在赶来的路上,无法出兵,那么就将进入撤销阶段,大致步骤如图所示

第一步,苏秦执行撤销操作,通知赵、魏、韩取消明天攻打秦国的计划

第二步,收到撤销操作的响应。

在经过了预留和确认(或撤销)阶段的协商,苏秦实现这个分布式事务:赵、魏、韩三国,要么明天一起进攻,要么明天都按兵不动。其实在我看来,TCC本质上是补偿事务,它的核心思想是为每个操作都注册一个与其对应的确认操作和补偿操作(也就是撤销操作)。它是业务层面的协议,你也可以将TCC理解为编程模型。TCC的3个操作是需要在业务代码中编码实现的,为了实现一致性,确认操作和补偿操作必须是幂等的,因为这两个操作可能需要失败重试。

另外,TCC不依赖于数据库的事务,而是在业务中实现了分布式事务,这样能减轻数据库的压力,但对业务代码的入侵性更强,实现的复杂度

也更高。所以推荐在需要分布式事务能力的时候,优先考虑线程的事务型数据库,比如MySQL XA,在现有的事务型数据库不能满足业务需求

的时候,再考虑基于TCC实现分布式事务。

补充

最后补充一下,三阶段提交协议虽然针对二阶段提交协议的"协调者故障,参与者长期锁定资源"的痛点引入了询问阶段和超时机制来减少资源

被长时间锁定的情况,不过这会导致集群各节点在正常运行的情况下,使用更多的消息进行协商,增加了系统负载和响应延迟。因此,不建议

使用三阶段提交协议,

注意

可以将ACID特性理解为CAP中一致性的边界,最强的一致性,也就是CAP的"酸"(Acid)。根据CAP理论,如果分布式系统中实现了一致性,

那么可用性必然受到影响。比如,如果出现一个节点故障,则整个分布式事务的执行都是失败的。实际上,绝大部分场景对一致性要求没那么高,短暂的不一致时能接受的,另外,基于可用性和并发性能的考虑,建议在开发实现分布式系统时,如果不是必须,尽量不要实现ACID而是考虑实现最终一致性。

相关推荐
chuanauc9 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴25 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao32 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78735 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野6 小时前
【Java|集合类】list遍历的6种方式
java·python·list
二进制person7 小时前
Java SE--方法的使用
java·开发语言·算法
小阳拱白菜8 小时前
java异常学习
java