分布式事务解决方案

一、概述

分布式事务:分布式系统会把一个应用拆分为多个可独立部署的服务,此时要完成事务,就需要这些服务之间远程交互完成事务。简单的说跨JVM进程或者跨数据库实例产生分布式事务

典型的分布式事务场景:

  1. 跨库事务:一个应用中某个功能需要操作多个库。
  2. 分库分表
  3. 微服务化

二、分布式事务理论

1.CAP理论

指的是在分布式系统中不可能同时满足以下三点(最多满足两点):

  • 强一致性consistency:用户访问分布式系统的任何节点,得到的数据必须一致,需要保证分布式服务器能实时同步数据。
  • 可用性availability:用户每次访问集群中的任何健康节点,总能得到响应,而不是超时或拒绝。
  • 分区容忍性partition tolerance:集群出现分区时,整个系统任然能对外提供服务,也就是服务器A和服务器B可能因为各种意外情况,导致无法成功进行通信,但任然要能对外提供服务。除非整个系统崩了。

在分布式系统中分区容忍性一定是要有的(不能因为一个点故障,导致分布式系统挂了),另一点只能在一致性和可用性中取舍,为啥呢?看如下一个分布式系统:

服务A和服务B中间的链路发生了故障,此时满足分区容忍度,服务A和服务B任然能单独的提供服务:

①如果再想满足强一致性,假设客户端写了服务A,因为AB此时不能同步,所以不允许其他客户端再去读服务B未同步的数据(违反了可用性),只有当服务之间的链路恢复了且同步完成,才允许访问。

②如果想满足可用性,允许用户去访问服务B,那么就会读取到脏数据,就违反了强一致性。

在绝大多数场景下,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性。

其中,一致性可分为两种:

  • 强一致性:任何一次读都是读到的最新的写的数据,所有操作顺序,都可以看成是顺序执行的,不是并发的。
    • 只要集群内部的数据发生了改变,那么就需要等待集群内其他服务器的数据同步完成之后,才能正常对外提供服务。
  • 弱一致性:某个数据被更改,后续对该数据的读不一定是最新的。
    • 最终一致性:随着时间的前移,不同系统之间的数据会慢慢变成一致的。如果同步到一致后后续没有写操作,也算是一致性系统。

有些系统通过牺牲强一致性,得到可用性,但它并不是完全放弃数据的一致性,而是换为最终一致性。

2.BASE理论

BASE理论是对CAP理论中AP的一个扩展,通过牺牲强一致性来获得可用性,允许数据在一段时间内是不一致的:

  • Basically Available(基本可用):分布式系统在出现故障时,允许损失部分可用性,保证核心可用。
  • Soft State(软状态):在一定时间内,允许出现中间状态,这个状态不影响系统可用性,如订单的“支付中”。
  • Eventually Consistency(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到一致。

满足BASE理论的事务称之为“柔性事务”:

  • 刚性事务满足CAP的CP,低并发,适合短事务。
  • 柔性事务满足BASE理论(基本可用,最终一致),即满足CAP的AP,高并发。

BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。

 

 

三、分布式事务解决方案之XA协议

在分布式系统中,每个节点都知道自己的操作结果(成功或者失败),却无法知道其他节点操作的结果,当一个事务跨越多个节点时,为了保证事务的执行(保证事务的原子性与一致性),而引入一个管理者来管理所有参与者的操作结果,并指示他们是否要将操作结果进行真正的提交或者回滚

比如订单和支付节点,订单调用支付系统,他们各自都有一个事务,管理者就是将这两个事务合二为一,变为一个真正的原子事务。

XA协议有2PC、3PC。

1.2PC

二阶段提交(2PC):它将事务的过程分为两个阶段来进行处理,准备阶段提交阶段

举例:张三和李四好久不见,老友约起聚餐,饭店老板要求先买单,才能出票。这时张三和李四分别抱怨近况不如意,囊中羞涩,都不愿意请客,这时只能只有张三和李四都付款,老板才能出票安排就餐。但由于张三和李四都是铁公鸡,形成了尴尬的一幕:

【准备阶段】:老板要求张三付款,张三付款。老板要求李四付款,李四付款。
【提交阶段】:老板出票,两人拿票纷纷落座就餐。

例子中形成了一个事务,若张三或李四其中一人拒绝付款,或钱不够,店老板都不会给出票,并且会把已收款退回。

店老板就是事务管理器,张三、李四就是事务参与者,事务管理器负责决策整个分布式事务的提交和回滚,事务参与者负责自己本地事务的提交和回滚

1.第一阶段(准备阶段)

  • 管理器向所有参与者发送事务内容,询问是否可以完成该事务,并等待答复。
  • 各参与者执行事务操作,将undo和redo信息记入事务日志中(但不提交事务,相当于提前锁定资源)。
  • 如参与者执行成功,给管理器反馈同意,否则反馈失败。

2.第二阶段(提交执行阶段)

当所有的参与者都是同意时,证明该事务执行成功,然后:

  • 管理器向所有参与者发出正式提交的请求。
  • 参与者正式提交事务,并释放在整个事务期间内占用的资源

如果有任意参与者在第一阶段返回的响应为中止,或者询问超时:

  • 管理器者向所有参与者发送回滚请求。
  • 参与者利用undo日志信息执行回滚,并释放事务期间内占用的资源

二阶段提交能保证数据的强一致性(刚性事务),但也有几个缺点:

  • 性能低下:执行过程中,所有节点都是事务阻塞型的,当参与者占用公共资源时,其他节点如果想访问,就不得不阻塞。
  • 可靠性低:如果某个参与者发生故障,管理器还要给他设置超时时间,并且等待它,没有意义。如果管理器发生故障,参与者会一直等待下去。需要额外的备机进行容错。
  • 数据一致性问题:在第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就会导致节点间数据的不一致问题。

它不适合高并发高性能的场景。

seata实现2PC 

Seata 是什么

Seata是开源的分布式事务解决方案,提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA 和XA事务模式,为用户打造一站式的分布式解决方案。

  • 对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
  • 高性能:减少分布式事务解决方案所带来的性能消耗

专业术语:

  • TC(Transaction Coordinator)事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚,它是一个独立的中间件
  • TM(Transaction Manager)事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务,具体是交给TC去执行的。
  • RM(Resource Manager)资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提;提交或回滚。

Seata中的AT模式是在传统2PC的基础上演进的:

AT模式运行机制:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交释放本地锁和连接资源(不同点)。 
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

在AT模式下,用户只需关注自己的业务SQL,用户的业务SQL作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。

传统的2PC事务的资源会一直占有,直到提交或回滚,而Seata的做法是阶段1就提交了,整体提高效率。

举例:

  1. 用户服务的TM向TC申请一个全局事务,全局事务创建成功并生成一个全局唯一的XID
  2. RM向TC注册分支事务,该分支事务执行新增用户的逻辑,并将其纳入XID对应全局事务的管辖
  3. 用户服务执行分支事务,向用户表插入一条记录,并提交
  4. 逻辑执行到远程调用积分服务时(XID在微服务调用链路的上下文中传播)。积分服务的RM向TC注册分支事务,该分支事务执行增加积分的逻辑,并将其纳入XID对应全局事务的管辖。
  5. 积分服务执行分支事务,向积分记录表插入一条记录,并提交,执行完毕后,返回用户服务。
  6. 用户服务分支事务执行完毕。
  7. TM向TC发起针对XID的全局提交或回滚决议。
  8. TC调度XID下管辖的全部分支事务完成提交或回滚请求。

积分服务中也有TM,但是由于没有用到,因此直接可以忽略。

具体Seata的使用方式请看:传送门

2.3PC

三阶段提交(3PC):是两阶段提交的改进版本。

  • 在协调者和参与者中都加入了超时机制。
  • 将第一阶段增加了一个询问阶段,如果因为网络断开等现象,无法得到某些服务的回应,就不会执行准备阶段。

1:CanCommit阶段

  • 协调者询问所有参与者,询问是否可以提交事务,并等待所有参与者的答复
  • 参与者收到CanCommit请求后,如果认为可以执行事务操作,则反馈yes并进入预备状态,否则反馈no

2:PreCommit阶段

  • 所有参与者都返回了yes,协调者预执行事务,进入PreCommit阶段。和2PC的准备阶段是相同的操作
  • 假如任何一个参与者向协调者发送了no响应,或没有收到它的响应(超时),那么就执行事务的中断。不执行任何事务操作。

3:DoCommit阶段

  • 所有参与者都返回ack响应,执行真正的事务提交,通知所有参与者提交
  • 任何一个参与者返回no,中断事务,通知他们回滚

注意:进入阶段3后,无论协调者出现问题,或者协调者与参与者网络出现问题,都会导致参与者无法接收到协调者发出的doCommit请求或abort请求。此时,参与者都会在等待超时之后,继续执行事务提交

2PC和3PC的区别:

优点:相比2PC,3PC降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务。

缺点:数据不一致问题依然存在,当在参与者收到preCommit请求后等待doCommite 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。

四、事务补偿TCC

TCC是一种应用层面侵入业务代码的两阶段提交,是目前最火的一种柔性事务方案,不会一直持有资源的锁,其核心思想:针对每个操作,都要注册一个与其对应的确认和补偿操作。

1.第一阶段

Try(尝试):主要针对系统做业务检查(一致性)及资源预留(加锁,锁住资源,隔离性)

2.第二阶段

本阶段根据第一阶段的结果,决定是执行confirm还是cancel

  • Confirm(确认)︰执行真正的业务,只使用Try阶段预留的业务资源,再释放锁
  • Cancle(取消)︰释放预留资源,出问题,释放锁

分支事务成功的情况:

 分支事务失败的情况:

1.如果Confirm或Cancel操作执行失败,将会不断重试直到执行完成。所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等。

2.还有如果RM遇到网络问题,没有执行try阶段,此时事务管理器可能会发送cancel请求,这时要注意空回滚问题。

3.如果因为网络问题或超时,cancel在try之前处理,此问题称为悬挂,即资源预留后没法继续处理,一般cancel控制了空回滚问题就不会出现,而try还是会出现

幂等性:同一个操作无论请求多少次,其结果都是相同的

举例:A转账30元给B,A和B服务不在一个服务。

方案1:

账户A

try:检查余额是否够30元
    扣减30元

confirm:空

cancel:增加30元

账户B

try:增加30元

confirm:空

cancel:减少30元

方案1的问题说明:

  1. 如果B的try没有执行,则在cancel阶段会多扣30元,空回滚问题
  2. B的try、confirm、cancel都没有实现幂等性
  3. 账户B增加的30元,可能被其他人消费了,回滚的时候就会出现负数

优化方法:

账户A

try:
    try悬挂处理
    检查余额是否够30元
    扣减30元

confirm:空

cancel:
    幂等校验
    cancel空回滚处理
    增加可用余额30

账户B

try:空

confirm:
    confirm幂等校验
    正式增加30元

cancel:
    空

可以看到,TCC方案有一个中间状态,它只保证数据的最终一致性

特点:

TCC 事务机制相比于上面介绍的 XA 事务机制,有以下优点:

  • 性能提升:具体业务来实现控制资源锁的粒度变小,不会锁定整个资源
  • 数据最终一致性:基于Confirm和Cancel的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
  • 可靠性:解决了XA协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。

缺点:TCC的Try、Confirm和Cancel操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。

五、可靠消息最终一致性

指事务发起方完成本地事务后进行提交,然后发送一个消息到事务参与方,指示事务参与方执行它的本地事务逻辑。此方法强调的是该消息一定能够让事务参与方接收到,且最终事务要达到一致

注意该方法实现的分布式事务不能回滚,适合用在被调用方的服务不影响调用方服务的场景下。它体现的是整体事务一定能提交的一个场景。 

如:

张三给李四转账,张三首先扣自己账户的余额,然后再发消息通知李四增加自己的余额,这是可以的(李四无论如何都要执行成功);但如果反过来,李四先增加自己的余额,再通知张三扣减余额,这是不行的,因为张三的余额可能不够,事务就执行不了。

 可靠性最终一致性要解决的问题:

1.本地事务与消息发送的原子性问题

事务管理者一定要保证本地事务执行和消息发送是原子的,不能本地事务执行完了,但是消息没发出去或者消息发出去了本地事务执行失败回滚的情况,否则会导致数据的不一致。

2.事务参与方接收消息的可靠性

事务参与方一定要能够从mq接收到消息,如果接收失败可以重复接收消息。

3.消息幂等性

由于网络问题,mq可能会重复投递消息,导致消费者重复消费。

可靠消息最终一致性具体的解决方案:

1.本地消息表

它的核心思想就是将分布式事务拆分成本地事务进行处理。

事务管理者在数据库额外新增一个事务消息表,将要发出去的消息先插入到该表中。插入消息的sql语句和自己的业务逻辑放在一个事务中,这也就保证了业务处理成功,消息一定持久化了。事务管理方开启一个事务,定时的将数据库的消息发送到MQ。

比如用户服务和积分服务,用户服务添加新用户,添加后还要远程调用积分服务给新用户增加积分:

1.事务管理者通过将消息持久化到数据库的步骤和处理自己的业务逻辑组成一个事务,保证新增用户成功,消息一定持久化了。并通过一个定时任务定时的扫描发送状态为未消费的消息。达到可靠生产

2.事务参与者如果收到消息,是手动应答,并且需要等待增加积分的事务处理完了才发送ACK,rabbitmq收到ACK后转发给事务管理者,将消息数据库的状态改为已经消费了。如果消费者事务处理失败就返回NACK。定时任务还会一直发送消息。到达可靠消费

 如果事务参与者处理失败,则回复Nack,将消息重新入队消费(注意幂等性)。如果一直错误,可以将消息放入死信队列,进行人工补偿。

该方案的优点是比较成熟,耦合度低,缺点是需要额外创建表,且定时任务不定时的查找数据库带来了一定的消耗。

2.MQ事务

基于MQ的分布式事务方案其实是对本地消息表的封装,将本地消息表基于MQ内部,其他方面的协议基本与本地消息表一致。

最重要的是事务回查机制,它保证了事务的可靠生产与可靠消费。

如果commit/rollback消息丢失,通过回查机制可以发现。

如果MQ到事务参与者的commit/rollback消息丢失,也可以通过回查机制发现。

 

六、最大努力通知

最大努力通知方案的目标,就是发起通知方通过一定的机制,最大努力将业务处理结果通知到接收方。它主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接、商户通知等等跨平台、跨企业的系统间业务交互场景

如果接收方一直收不到结果,消息通知方应该提供接口供接收方主动查询结果

最大努力通知和可靠消息一致性的区别:可靠消息一致性通过事务管理者发送事务消息,保证事务的可靠性,而最大努力通知靠事务的参与者保证消息的可靠性,如果消息发不出去接收不到,还要提供接口保证可靠性。

七、Saga模式

Seata Saga 模式

 Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,如果某个参与者出现错误回滚,会反向的回滚前面的参与者提交的事务。

 

Saga 正向服务与补偿服务也需要业务开发者实现。因此是业务入侵的。

Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。

Saga模式的优势是:

  • 一阶段提交本地数据库事务,无锁,高性能;
  • 参与者可以采用事务驱动异步执行,高吞吐;
  • 补偿服务即正向服务的“反向”,易于理解,易于实现;

缺点:Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。后续会讲到对于缺乏隔离性的应对措施。

与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持:

  • 空补偿:逆向操作早于正向操作时;
  • 防悬挂控制:空补偿后要拒绝正向操作
  • 幂等

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值