分布式事务以及解决方案

一、何为分布式事务

当使用分布式服务或者微服务的时候,各个重要的功能模块被分成不同的服务,不同的服务可能写不同的DB。

例:若就按照最经典的银行转账来说,若A向B转账,A扣除钱,B增加钱,扣钱和增加钱属于不同的服务。

A若先扣完钱,然后通过消息队列或者是直接通知B,但是这个时候因为网络问题,消息发送不出去,重试以后还是发不出去,然后就会导致A扣钱了,B还是没有增加钱,数据无法保证一致性。

若A先发送消息,然后再扣钱。万一消息发送成功了,A扣钱失败,然后就会导致B收到了消息,然后B增加了钱,A并没有扣钱,导致数据还是无法保证一致性。

以上就是经典的分布式事务,无法保证数据的一致性。

二、本地数据库事务

1、ACID(原子性、一致性、持久性、隔离性)

2、InnoDB如何实现数据库的事务

InnoDB是Mysql数据库的一个存储引擎,其还有MyISAM、MEMORY、BDB

原子性和一致性:是通过UndoLog来实现,Undo log的原理是:为了满足事务的原子性,在操作任何数据之前,都将数据库中已有的数据备份在一个地方,这个存储备份数据的地方就是UndoLog,然后对数据进行修改,若修改出现了问题或者rollback了,则可通过UndoLog备份的数据将数据库恢复到修改之前的状态。

持久性:是通过RedoLog实现的,RedoLog记录的是新数据的备份,在提交事务之前,只需要将RedoLog持久化即可,不需要将数据在数据库持久化,当数据库崩溃时,虽然数据库没有持久化,但是RedoLog已经持久化了,可通过RedoLog将数据恢复到最新状态。

隔离性:是通过数据库锁机制实现的。

三、分布式事务的理论基础

1、CAP定理

C(Consistency一致性)、A(Availiability可用性)、P(Partition Tolerace分区容错性)

分布式架构,都得保证分区容错性,某个节点出问题了,不影响整体的使用。所以现在的分布式架构要么选择AP放弃强一致性,要么选择CP,放弃可用性。微服务一般使用的都是AP,放弃了强一致性,用最终一致性代替。而Dubbo使用的是CP,放弃了短暂的可用性,因为Dubbo使用了zookeeper,zookeeper内部节点采用了选举模式,当主节点挂掉的时候,会选举出新的节点,选举的时间内不可用。

一致性分为强一致性(CPA中的C)、弱一致性、最终一致性,最终一致性是对弱一致性的增强。

2、BASE定理

BA(Basically available基本可用)、S(Soft state软状态)、E(Eventually consistent最终一致性)

BASE定理是对CAP定理的一致性和可用性进行均衡后的结果,该理论的核心是:我们无法保证强一致性,但是各应用根据自身情况通过一定的方式达到最终一致性。

3、强一致性

强一致性是基于X/Open组织定义的DTP标准,该标准定义了规范和API接口,其主要分为三个角色,AP(应用程序)、RM(资源管理器)、TM(事务管理器)。举一个下单的例子:

首先应用程序会向事务管理器注册全局事务,然后在对应的资源管理器执行下单或者锁库存的操作,因为资源管理器都实现了XA接口,因此事务管理器能根据执行的结果来决定是继续提交还是回滚。

注意:数据库是资源管理器,但是资源管理器不一定是数据库,例如消息中间件也是资源管理器,mysql和oracle中都对XA进行了实现。 

四、常用的几种分布式事务的处理方案

1、两阶段提交(2pc,也叫XA-transaction) 

 -------全局事务解决方案

其基于XA协议,所以也叫作XA-transaction,其分为两个步骤:

1.1(准备阶段)、事务管理器会要求涉及事务的数据库预操作(precommit),并给出反馈是否可执行,预操作包括根据自身情况判断是否能执行该事务,若能执行则执行undo和redo写入到事务日志中,等待下一步的操作;

1.2(提交阶段)、事务管理器根据反馈要求所有的数据库进行提交或者回滚操作。

缺点:

        单点故障:若第二阶段事务管理器挂掉了,资源服务器就会一直阻塞,导致数据库不可用;

        数据不一致:还是在第二阶段,事务管理器通知所有的数据库进行提交操作,由于网络抖动或者其他的原因导致部分数据库进行了提交操作,部分没收到,没提交,导致数据不一致性。

       同步阻塞的问题:在准备就绪以后,资源管理器会一直占用事务涉及的资源,导致资源管理器中的资源一直处于阻塞状态,直到所有的提交结束,才能释放资源。

2、三阶段提交(3pc)  

-------全局事务解决方案

三阶段提交是二阶段提交的基础上进行一些优化:参与者和协调者之间加入了超时的状态;在第一阶段和第二阶段之间加入了一个准备阶段,保证在准备阶段以后所有参与者的状态是一致的。

其步骤分为三个阶段:

CanCommit:事务管理器向所有参与者发送canCommit命令,各参与者检查自身是否能够执行事务的提交操作;

PreCommit:若所有的参与者都跟事务管理器返回的都是YES,则代表都可执行事务的提交操作,然后事务管理器向所有参与者发送preCommit命令,参与者开始执行事务,写undo和redo信息到事务的日志中,然后反馈执行的结果给事务管理器;若有一个返回NO,则事务管理器向所有的参与者发送abort命令,放弃事务的执行;

DoCommit:事务管理器收到所有的YES返回以后,向参与者发送doCommit,各参与者将预操作变成执行的操作,完成事务,此时若参与者超时未收到协调者的消息,也会执行commit操作,因为预提交状态代表都所有的参与者都可执行事务;若事务管理器收到某一个反馈为NO,则向所有参与者发送rollback命令,参与者根据undo和redo来执行回滚。

优点:减少了单点故障和资源阻塞,参与者如果收不到协调者的命令,会执行commit

缺点:还是会有数据一致性问题:当协调者向参与者发送abort命令时,由于网络原因导致超时某些参与者还没收到,则参与者会自己执行commit操作,导致数据不一致性。

3、TCC(补偿事务)

 -------柔性事务解决方案

核心思想:针对每个操作,都要注册一个与之对应的确认和补偿操作。其分为三个步骤:

try阶段:对业务系统做检测并做资源预留;

confirm阶段:对业务系统做业务提交;

cancel阶段:取消业务的执行,同时释放try阶段预留的资源

举个例子:例如你花10块钱买个蛋炒饭,try阶段你的10块钱被冻结,那个蛋炒饭也被冻结,confirm阶段你的钱被付给商家,蛋炒饭给你,如果confirm阶段出错了,就会调用cancel对你的钱和商家的蛋炒饭解冻。

优点:操作简单

缺点:2.3步都可能失败,而且还要写很多补偿代码。

4、本地消息表

   -------基于可靠消息的分布式事务解决方案

该方法也是达到最终一致性的解决分布式事务的一种方案。其核心思想是:将分布式事务拆分成本地事务进行执行。

4.1、消息生产者会在本地建立一张消息表,然后将执行业务和写消息到本地消息表这两个操作都写在一个事务里,这样有一个失败了都事务回滚。

4.2、定时器去轮询数据库消息表状态为待发送的消息,然后发送给MQ

4.3、MQ存储消息,然后返回ack给生产者,生产者收到ack以后将消息表中的消息状态标记为已发送

4.4、MQ发送消息给消费者,消费者消费进行业务处理

该方案的前提是消费者消费做了幂等,不重复消费消息,业务处理逻辑不能有问题,可以系统有问题,但是不能业务逻辑有问题,不然没法做到一致性了。

该方案以及以下的事务消息主要解决的是生产者本地事务执行成功消息发送成功两者一致性的问题。

5、利用消息队列的事务消息功能

   -------基于可靠消息的分布式事务解决方案

目前事务消息都是依赖于消息队列,目前已知支持事务消息的消息队列就是RocketMQ,其从4.3(2018年8月发布)版本以后就支持事务消息了,目前(2019年9月)最新版本的是4.5。

5.1、何为事务消息?

生产者发送事务消息给消息队列broke以后,broke将消息持久化并标记为待发送,这时broke不会将消息推送给消费者,消费者主动拉取的时候也看不到标记为待发送的消息。等broke根据生产者的反馈以后将消息的状态修改为已发送以后,该消息就跟可被消费者消费或者被broke推送给对应的消费者。

5.2、事务消息如何解决分布式事务

5.2.1、生产者发送Half消息给MQ;

5.2.2、MQ收到消息以后将消息本地存储为待发送;

5.2.3、生产者收到Half消息发送结果的回执以后,根据回执结果进行下一步,如果回执是失败的,则没有下一步,如果回执是成功的,则生产者执行本地事务,根据执行结果发送commit或者rollback给MQ;

5.2.4、MQ根据收到的执行结果进行下一步,如果收到的是commit,则将half消息标记为可发送,将消息下发给consumer端,如果收到的是rollback,则删除本地的half消息。如果MQ一直收不到生产者端的执行结果,会自己间隔一定时间去询问执行结果。

6、基于成熟的分布式事务解决框架Seata

Seata是2019.1阿里和蚂蚁金服开源的分布式事务解决方案。

Seata是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT,TCC,SAGA和HA事务模式,为用户打造一站式的分布式解决方案。通过提供的事物模式我们可以看出Seata针对我们上面提出的解决方案123都做出了具体的实现。

分布式事务处理方案需要一个一致性ID+三组件,其中一致性ID就是一个全局唯一的事物ID,也就是XID。三组件分别如下:

TC:事务协调器,维护全局事务,驱动全局事务的提交或者回滚;

TM:事务管理器,协调全局事务的范围,开始全局事务的提交或者回滚;

RM:资源管理器,控制分支事务,负责分支注册分支事务和报告。

大致过程如下:

  • TM向TC申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的XID
  • XID在微服务调用链路的上下文中传播
  • RM向TC注册分支事务,将其纳入XID对应全局事务的管辖
  • TM向TC发起针对XID的全局提交或回滚决议
  • TM调度XID下管辖的全部分支事务完成提交或回滚请求

 使用注意:

使用主要是修改conf目录下的file.conf配置文件(主要修改file.conf文件下的两个模块,service模块、store模块)和registry.conf中seata注册的注册中心的地址;

1、需要修改service模块中的自定义事务组名称;

2、需要修改store模块中事务日志存储方式(db or file),一般都是选择db,如果选择了db,则需要配置数据库连接信息;

3、需要修改registry.conf,在其中配置Seata的注册中心,将Seata服务端注册在注册中心,市面上常见的注册中心都支持,例如nacos,eureka,redis,consul,zk等。

如何使用?

咱们常用的@Transaciton是Spring控制的本地事务,而Seata自己控制全局事务的是@GlobalTransaction,我们只需要使用该注解放在业务开启的service对应的方法上即可。例如常见的下单-->扣库存-->扣钱-->更新订单状态,我们可以在下单的时候加上@GlobalTransaction。同时在yml文件中配置自定义事务组名称,这个名称要和file.conf下的service中自定义事务组名一样。

在下单购买商品的业务场景下,TC就是seata服务器;TM就是带有@GlobalTransaction注解的方法;RM就是数据库,也就是事务参与方。

对应的执行流程如下:

  • TM开启分布式事务(TM向TC注册全局事务记录),相当于注解 @GlobelTransaction注解
  • 按业务场景,编排数据库,服务等事务内部资源(RM向TC汇报资源准备状态)
  • TM结束分布式事务,事务一阶段结束(TM通知TC提交、回滚分布式事务)
  • TC汇总事务信息,决定分布式事务是提交还是回滚
  • TC通知所有RM提交、回滚资源,事务二阶段结束
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值