CAP
Consistency。一致性。写操作后的读操作,必须返回最新写入的值。对于单个节点的系统来说,这点很容易满足。但对于多节点的集群环境来说,如果写操作往节点1写入,而读操作去节点2读取,就无法满足这个要求。
Availability。可用性。所有的用户请求都能得到响应。
Partition tolerance。分区容错性。即使在某些节点无法响应的情况下,用户操作能让能正确执行。
ACID
事务是数据库从一个稳定状态变迁到另一个稳定状态的保证,具备 ACID 这 4 个特性:
原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚到事务开始前的状态。
一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性限制没有被破坏。
隔离性(Isolation):两个事务的执行是互不干扰的,两个事务时间不会互相影响。
持久性(Durability):在事务完成以后,该事务对数据库所作的更改便持久地保存在数据库之中,并且是完全的。
BASE
BASE代表的是Basically Available,Soft state,Eventually consistent。是ACID的替代方案
BASE和ACID正好相反。ACID强制每个操作后都要满足一致性,BASE则允许出现暂时的不一致。对于一致性的妥协让BASE能够提供比ACID更为强大的扩展性。
分布式事务处理模型(Distributed Transaction Processing,简称X/Open DTP)
X/Open,即现在的open group,是一个独立的组织,主要负责制定各种行业技术标准
- AP(Application Program):即应用程序,可以理解为使用DTP分布式事务的程序。用于定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作。
- RM(Resource Manager):即资源管理器,可以理解为事务的参与者,如数据库、文件系统等,并提供访问资源的方式。通过资源管理器对其进行控制,资源管理器控制着分支事务。
- TM(Transaction Manager):事务管理器,负责分配事务唯一标识,协调和管理事务,监控事务的执行进度,并负责事务的提交、回滚等。
- CRM(Communication Resource Manager):通信资源管理器,控制一个TM域(TM domain)内或者跨TM域的分布式应用之间的通信
- CP(Communication Protocol):通信协议, 提供CRM提供的分布式应用节点之间的底层通信服务。
1)TM向AP提供应用程序编程接口,AP通过TM提交及回滚事务。
2)TM交易中间件通过XA接口来通知RM数据库事务的开始、结束以及提交、回滚等。
DTP模型定义TM和RM之间通讯的接口规范叫XA,基于XA 协议来实现2PC又称为XA方案。
XA协议
准确讲XA是一个规范、协议,由Oracle Tuxedo系统提出的XA分布式事务协议。它只是定义了一系列的接口,MySQL中InnoDB存储引擎支持XA协议
XA 协议通过每个 RM(Resource Manager,资源管理器)的本地事务隔离性来保证全局隔离,并且需要通过串行化隔离级别来保证分布式事务一致性,是基于资源层的底层分布式事务解决方案
XA协议定义了分布式事务参与方的两个角色:
事务协调者(TM=Transaction Manager)
资源管理器/事务参与者(RM=Resource Manager)
XA协议包括两阶段提交(2PC)和三阶段提交(3PC)两种实现
二阶段提交协议(Two-phase Commit Protocol,简称 2PC)
流程:应用程序向事物管理器(Transaction Manager,简称 TM、协调者)提交请求,发起分布式事物
-
prepare 询问 RM 是否准备好提交事务
第一阶段:TM联络所有资源管理器(Resource Manager,简称 RM、参与者),通知准备提交事物;各RM返回准备成功/失败/响应超时视为失败 的消息给TM -
commit/rollback 通知 RM 提交/ 回滚事务
第二阶段:TM判断所有RM准备成功,通知RM执行事物提交;任一RM失败,则通知所有RM进行回滚
问题:
-
协调者不宕机,参与者宕机
-
协调者宕机,参与者不宕机
-
协调者宕机,参与者也宕机
缺点:
- 1、同步阻塞问题
两阶段提交方案下全局事务的ACID特性,执行过程中,所有参与节点都是事务阻塞型的。当RM占有公共资源时,其他第三方访问公共资源不得不处于阻塞状态
- 2、单点故障
由于协调者的重要性,一旦协调者TM发生故障。参与者RM会一直阻塞下去(只有TM有超时机制,RM没收到请求会一直等待)。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
- 3、数据不一致
数据不一致是在分布式体系下。如果是单机模式或是主从模式,在两阶段提交的整个过程中,一直会持有资源的锁,表现为强一致性
在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障
-
a).这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象
-
b).当TM发送commit请求后宕机,RM接收到commit请求后也宕机,即使选举出新的TM,也没人知道这个事物是处于提交了还是未提交状态
三阶段提交协议(Three-phase Commit Protocol,简称 3PC)
-
CanCommit阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
1.事务询问 协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
2.响应反馈 参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No
-
PreCommit阶段
协调者根据参与者的反应情况来决定是否可以继续事务的PreCommit操作。根据响应情况,有以下两种可能。
假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。
1.发送预提交请求 协调者向参与者发送PreCommit请求,并进入Prepared阶段。
2.事务预提交 参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
3.响应反馈 如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
1.发送中断请求 协调者向所有参与者发送abort请求。
2.中断事务 参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
-
doCommit阶段
该阶段进行真正的事务提交,也可以分为以下两种情况。
Case 1:执行提交
1.发送提交请求 协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。 2.事务提交 参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。 3.响应反馈 事务提交完之后,向协调者发送Ack响应。 4.完成事务 协调者接收到所有参与者的ack响应之后,完成事务。
Case 2:中断事务 协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。
1.发送中断请求 协调者向所有参与者发送abort请求 2.事务回滚 参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。 3.反馈结果 参与者完成事务回滚之后,向协调者发送ACK消息 4.中断事务 协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
- 三阶段提交针对两阶段提交做了改进:
引入超时机制。
在2PC中,只有TM拥有超时机制,3PC同时在TM和RM中都引入超时机制。一旦事物参与者迟迟没有接到协调者的commit请求,会自动进行本地commit。这样有效解决了协调者单点故障的问题。
在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
缺点:
- 设计、实现复杂
- 数据不一致
如果进入PreCommit后,协调者发出的是abort请求,假设只有一个参与者收到并进行了abort操作,而其他对于系统状态未知的参与者会根据3PC选择继续Commit,此时系统状态发生不一致性。
补偿事务 (Try-Confirm-Cancel, 简称TCC)
实现方式是在代码层面人为实现。2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务
A、B、C三个服务的执行。如果三个try都成功,才执行confirm做正式提交。如果任何一个服务出问题,整个事务管理器就执行calcel
1、try过程的本地事务,是保证资源预留的业务逻辑的正确性。
2、confirm/cancel执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务。
saga事务模型
https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf
saga事务模型又叫长事务,long live transaction,是一种分布式异步事务,最终一致性事务,是一种柔性事务。描述的是另外一种在没有两阶段提交的的情况下解决分布式系统中复杂的业务事务问题方案。
组成:
每个Saga由一系列sub-transaction Ti 组成。每个本地事务Ti有对应的补偿事务Ci,补偿动作用于撤销Ti造成的结果。和TCC相比,Saga没有“预留”动作,它的Ti就是直接提交到库。
Saga两种恢复策略:
- backward recovery,向后恢复,补偿所有已完成的事务(回滚操作)
执行顺序:T1 -> T2 -> T3(异常)-> C3 -> C2 -> C1
这种做法的效果是哪个sub-transaction出现异常了,撤销掉之前所有成功的子事务,使得整个Saga的执行结果撤销。
- forward recovery,向前恢复,重试失败的事务,假设每个子事务最终都会成功(重试操作)
执行顺序:T1, T2, T3, …Tj(失败),Tj(重试),Tj(失败),Tj(重试),…, Tn,
步骤中出现了失败情况一直进行重试,该情况下不需要Ci。若一直重试还失败,则需人工干预,所以这种模式逻辑上要确保子事务最终会成功,
2种最流行的实现方式
- 事件/编排Choreography
没有中央协调器(没有单点风险)时,每个服务产生并聆听其他服务的事件,并决定是否应采取行动。
举个栗子:
订单服务保存新订单,将状态设置为pengding挂起状态,并发布名为ORDER_CREATED_EVENT的事件。
支付服务监听ORDER_CREATED_EVENT,并公布事件BILLED_ORDER_EVENT。
库存服务监听BILLED_ORDER_EVENT,更新库存,并发布ORDER_PREPARED_EVENT。
货运服务监听ORDER_PREPARED_EVENT,然后交付产品。最后,它发布ORDER_DELIVERED_EVENT。
最后,订单服务侦听ORDER_DELIVERED_EVENT并设置订单的状态为concluded完成。
假设库存服务在事务过程中失败了。进行回滚:
库存服务产生PRODUCT_OUT_OF_STOCK_EVENT
订购服务和支付服务会监听到上面库存服务的这一事件:
①支付服务会退款给客户。
②订单服务将订单状态设置为失败。
- 命令/协调orchestrator
这里我们定义了一个协调器,全权负责告诉每个参与者什么时候该做什么。saga协调器orchestrator以命令/回复的方式与每项服务进行通信,告诉他们应该执行哪些操作。
1.订单服务保存pending状态,并要求订单Saga协调器(简称OSO)开始启动订单事务。
2.OSO向收款服务发送执行收款命令,收款服务回复Payment Executed消息
3.OSO向库存服务发送准备订单命令,库存服务将回复OrderPrepared消息
4.OSO向货运服务发送订单发货命令,货运服务将回复Order Delivered消息。
OSO订单Saga协调器必须事先知道执行“创建订单”事务所需的流程(通过读取BPM业务流程XML配置获得)。如果有任何失败,它还负责通过向每个参与者发送命令来撤销之前的操作来协调分布式的回滚。
实现方式 | 优点 | 缺点 |
---|---|---|
事件/编排设计 | 1、低耦合,所有参与者都是松散耦合的,他们彼此之间没有直接的调用 2、开发简单,只需要关注需要订阅的事件 3、维护风险小 | 无管理,整个流程松散,不易跟踪,添加流程或者回滚得理清整条事件线,测试麻烦,可能会有循环依赖互相订阅彼此的事件 |
协调器设计 | 1、避免服务之间的循环依赖关系,因为saga协调器会调用saga参与者,但参与者不会调用协调器 2、只需要执行命令/回复(其实回复消息也是一种事件消息),降低参与者的复杂性 3、事务复杂性保持线性,逻辑线清晰,添加新逻辑,回滚操作更容易管理 4、回滚流程能够轻松中断 5、容易mock测试更简单 | 协调器的单点故障风险,协调器集中过多逻辑会显得很臃肿,与微服务理念不兼容 |
有点像用if else 和设计模式的感觉,一个大而全,一个小而活
回到我们的业务场景,
那么对于TCC还是Saga来说,try失败了都会进入cancel阶段,但是好巧不巧在还没有来得及进行cancel回滚处理的时候,有用户来读取B、C的数据。在这种情况下TCC和Saga就会返回不一样的结果。
对于TCC来说,try只是一个冻结的操作,所以他操作的也只是一个**“冻结字段”**,该字段并不会影响用户查询的实际数据,所以是可以返回正确的结果的。
对于Saga来说,try则是对数据的一个直接操作,会更改用户想要访问的数据,那么这个时候就会返回给用户脏数据。会有脏读那么就说明Saga框架下数据是有「隔离性」的问题的
Saga相对于TCC主要在隔离性上的缺失
比如我们的业务场景,服务A调用了服务B、C,如果发生了以下的情况:
1)对服务B try成功了,但是服务C try失败了;
2)此时有用户来读取服务B和服务C的数据;
3)A、B、C进行回滚;
对于TCC还是Saga来说,try失败了都会进入cancel阶段,但是好巧不巧在还没有来得及进行cancel回滚处理的时候,有用户来读取B、C的数据。在这种情况下TCC和Saga就会返回不一样的结果。对于TCC来说,try只是一个冻结的操作,所以他操作的也只是一个冻结字段,该字段并不会影响用户查询的实际数据。对于Saga来说,try则是对数据的一个直接操作,会更改用户想要访问的数据,那么这个时候就会返回给用户脏数据,这将导致数据隔离性问题。
究其原因,是因为TCC是两阶段提交事务而Saga是一阶段提交事务。
本地消息表
本地消息表采用BASE原理,保证事务最终一致
本地消息表的方案:
- 将本事务外操作,记录在消息表中
- 其他事务,提供操作接口
- 定时任务轮询本地消息表,调用操作接口,传入未执行的消息。按操作接口处理成功/失败标识,更新消息状态。
- 定时任务按照一定的周期反复执行
- 对于屡次失败的消息,可以设置最大失败次数,超过最大失败次数的消息,不进行接口调用,可等待人工处理
优点: 避免了分布式事务,实现了最终一致性
缺点: 实时性不高,有数据隔离性问题,注意幂等性操作
对比
协议 | 优点 | 缺点 |
---|---|---|
2PC | 逻辑简单 | 只有协调者有超时机制存在单点故障,同步阻塞,数据一致性无法保证 |
3PC | 双边超时机制,避免阻塞 | 3次请求可能出现延迟性能下降,数据一致性无法保证 |
TCC | 多个独立的本地事务不会阻塞,无数据隔离性问题,而且保证最终一致性 | 业务逻辑复杂,业务入侵大,开发维护成本高 |
Saga | 模型比TCC更简单,一阶段事务提交,无锁,适用于长流程业务逻辑;适用于事件驱动异步执行,吞吐量更高,最终一致性 | 有数据隔离性问题,可能产生脏数据、更新丢失、模糊读取等问题;补偿失败的场景下可能需要人工介入 |