分布式事务

分布式事务解决方案

  • 事物描述
    • 本地事务
    • 分布式事务
  • 分布式事务解决方案
    • 2PC(两阶段提交)
      • 原理
      • XA方案
      • Seata方案
    • TCC
      • 原理
      • Hmily方案
    • 可靠消息最终一致性
      • 原理
      • RocketMQ方案
    • 最大努力通知
      • 原理
      • MQ的ACK机制方案

事物描述

本地事务

通过关系型数据库控制事务,利用数据库本身的事物特性来实现的,因此叫数据库事务,由于应用主要靠关系数据库控制事物,而数据库通常和应用在同一个服务器,所以基于关系型数据库的事物又被称为本地事务。

数据库事物的四大特性ACID:
1、原子性(Atomicity):原子性是指事物中的全部操作在数据库中是不可分割的,作要么全部成功,要么全部回滚。
2、一致性(Consistency): 一致性是指事物必须使数据库从一个状态变换到另一个一致状态,也就是说一个事物执行之前和执行之后必处于一致性状态。

栗如:用户A和用户B两者的钱加起来一共是50,那么不管A和B之间如何转账,转几次,事物结束后。两用户的钱加起来应该还是50,这就是事物的一致性。

3、隔离性(Isolation):隔离性是指多个用户并发访问数据库时,一个事物的执行不能被其他事物干扰。
4、持久性(Durability):持久性是指一个事物被提交了,那么对数据库中的数据改变就是永久性的,即便数据库系统遇到故障也不会丢失提交事物的操作。
注意:数据库事物在实现时,将一次事务涉及的所有操作全部纳入一个不可分割的执行单元,该执行单元中的所有操作,是要有任何一个操作失败,将全部回滚。
事物的隔离级别:
      1、串行化(Serializable):可避免脏读、不可重复读、幻读的发生。
      2、可重复读(Repeatable read):可避免脏读、不可重复读的发生。
      3、读已提交(Read committed):可避免脏读的发生。
      4、读未提交(Read uncommitted):最低级别,任何情况都无法保证。
这四种隔离级别从高到低为:串行化–>可重复读–>读已提交–>读未提交。
在MySQL数据库中,支持上面四种隔离级别,默认为可重复读,而在Oracle数据库中,只支持串行化和读已提交这两种级别,默认为读已提交。
在MySQL中查看当前事物的隔离级别:select @@tx_isolation;
在MySQL中设置事物的隔离级别:set [glogal|session] transaction isolation leve 隔离级别名称; 或 set tx_isolation=‘隔离级别名称’;
ReadOnly:只读事物:表示这个事物只读取数据但不更新数据,这样可以帮助数据库引擎优化事物。
注意:设置数据库的隔离级别一定要在开启事物之前。
事物并发导致的问题:

1、脏读:脏读是指对于两个事物A,B(需要更新X,Y字段),A读取了已经被B更新X字段,但Y还没有被更新,则A读取的内容就是无效的脏数据。
2、不可重复读:不可重复读是指对于两个事物A,B,A读取了一个字段,然后B更新了该字段后,A再次读取同一字段,值就不同了。
      栗如:事物A在读取某一项数据,而事物B立马修改了这个数据并且提交事物给数据库,事物A再次读取该数据就得到了不同的结果,发生了不可重复读。
      不可重复读和脏读的区别:脏读是某一事物读取了另一个事物未提交的脏数据,而不可重复读是读取了前一事物提交的数据。
3、幻读:幻读是指事物非独立执行时发生的一种现象。对于两个事物A,B,A从一个表中读取了一个字段,然后B在该表中插入了一些新的行后,如果A再次读取同一个表,则会多出几行。

分布式事务

把一个应用系统拆分为可独立部署的多个服务,因此需要服务与服务直接远程协作才能完成事务操作,这种分布式系统环境下由不同的服务之间通过网络协作完成事务,称为分布式事务。

CAP定理:
1、一致性(Consistency):写操作后的读操作可用读取到最新的数据状态,当数据分布在多个节点上,从任意节点独到的数据都是最的。
      分布式系统一致性特点:
      1、由于存在数据同步过程,写操作的响应会有一定延迟。
      2、为了保证数据一致性,会对资源暂时锁定,待数据同步完成释放锁定资源。
      3、如果请求数据同步失败的节点则会返回错误信息,一定不会返回旧数据。
2、可用性(Availability): 任何事物操作都可以得到响应,且不会出现响应超时或响应错误。
      分布式系统可用性特点:
      1、所有请求都有响应,且不会出现响应超时或响应错误。
3、分区容错性(Partition tolerance): 分布式系统节点都部署在不同的子网,这就是网络分区,不可避免的会出现由于问题而导致节点之间通信失败,此时仍可以对外提供服务。
      分布式系统可用性特点:
      1、分区容错性时分布式系统具备的基本能力。
如果要实现分区容错性(C)则必须保证数据一致性,在数据同步时,防止向从数据库查询不一致的数据则需要将从数据库锁定,待同步完成后结算,如果同步失败,从数据要返回错误信息或超时。
如果要实现可用性(A)则必须保证数据可用性,不管任何时候都可以向从数据库查询数据,则不会响应超时或返回错误信息。
故在满足分区容错性的前提下C和A 存在矛盾。

CAP组合方式:
AP:放弃一致性,追求分区容错性和可用性。这是很多分布式系统设计时的选择。
CP:放弃可用性,追求一致性和分区容错性,Zookeeper就是追求强一致,一次转账请求,要等待双方银行系统都完成,整个事物才算完成。
AC:放弃分区容错性,即不进行分区,不考虑有网络不通或节点挂掉的问题,可以实现一致性和可用性。常用的关系行数据库满足了AC。

BASE理论:强一致性和最终一致性。
Basically Available(基本可用)、Soft state(软状态)和Eventually Consistent(最终一致性)是哪个短语的缩写。BASE理论是对CAP中的AP的一个扩展,通过牺牲强一致性来获取可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终要达到抑制状态。满足BASE理论的事物,我们称为“柔性事物”。
1、基本可用: 分布式系统在出现故障时,允许损失部分可用性,保证核心功能可用。如:电商网站交易付款出现问题了,商品依然可以正常浏览。
2、软状态: 由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个软状态不影响系统可用性,如订单支付的“支付中”、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。
3、最终一致性: 最终一致是指经过一段时间后,所有节点数据都将会达到一致。如果订单的“支付中”状态,最终会改变为“支付成功”或“支付失败”,是订单状态与实际交易结果达成一致,但需要一定时间延迟、等待。

分布式事务解决方案

2PC(两阶段提交)

原理

2PC两段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare Phase)、提交阶段(commit Pase),2是指两个阶段,P是指准备阶段,C是指提交阶段。

栗如:张三和李四饭店AA聚餐,老板要求先买单才能下单。
准备阶段:老板要求张三付款,张三付款。老板要求李四付款,李四付款。
提交阶段:老板下单,两人就坐吃饭。
栗子中形成量一个事务,若张三或李四其中一人拒绝付款,老板都不会下单,并且会把已收到的钱退回。
      整个事务过程由事务管理器和参与者组成,老板就是事务管理器,张三和李四就是事务参与者,事务管理器决策整个分布式事务的提交和回滚,事务参与者负责自己本地事务的提交和回滚。

在计算机中部分关系数据库如:Oracle、MysSQL支持两阶段提交协议:
1、准备阶段: 事务管理器给每个参与者都发送准备消息,每个数据库参与者都在本地执行事务,并写本地的Undo/Redo日志,此时事务没有提交。
(Undo日志是记录修改前的数据,用于数据库回滚,Redo日志是记录修改后的数据,用于提交事务后写入数据文件)
2、提交阶段(Commit phase): 如果事务管理器收到了参与者的实行失败或超时消息是,直接给每个参与者发送回滚(Rollback)消息,否则,发送提交(Commit)消息,参与者根据事务管理器的指令执行提交或回滚操作,并释放事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源
分布式事务_第1张图片分布式事务_第2张图片

XA方案

2PC传统方案是在数据库层面实现的,如Oracle、MySQL都支持2PC协议,为了统一标准减少业内不必要的对接成本,需要指定标准化的处理模型及接口标准,国际开放标准组织Open Group定义了分布式事务处理模型DTP(Distributed Transaction Prosessing Reference Model)。
举个栗子:
分布式事务_第3张图片
流程如下:
1、应用程序(AP)持有用户库和积分库两个数据源。
2、应用程序(AP)通过TM(事务管理器)通知用户库RM(事务参与者)新增用户,同时通知积分库RM为该用户新增积分,RM此时并为提交事务,此时用户和积分资源锁定。
3、TM收到执行回复,只要有一方失败则分别向其他RM发起回滚事务,回滚完毕,资源锁释放。
4、TM收到执行回复,全部成功,此时向所有RM发起条事务,提交完毕,资源锁释放。
DPT模型定义角色:
1、AP(Application Program):应用程序,可以理解为使用DTP分布式事务的程序。
2、RM(Resource Manager):资源管理器,可以理解为事务的参与者,一般情况下指数据库实例,通过资源管理器对该数据库进行控制,资源管理器控制着分支事务。
3、TM(Transaction Manager):事务管理器,负责协调和管理事务,事务管理器控制着全局事务,事务生命周期,并协调各个RM。
全局事务是指分布式事务处理环境中,需要操作多个数据库共同完成一个工作,这个工作即是全局事务。
4、DTP模型定义TM和RM之间通讯的接口规范叫XA,简单理解为数据库提供的2PC接口协议,基于数据库的XA协议来实现2PC又称XA方案。
以上三个角色之间的交互方式:
      1、TM向AP提供应用程序编程接口,AP通过TM提交及回滚事务。
      2、TM交易中间件通过XA接口来通知RM数据库事务的开始、结束以及提交、回滚等。
总结:整个2PC的事务流程
      1、在准备阶段RM执行实际的业务操作,但不提交事务,资源锁定。
      2、在提交阶段TM会接收RM在准备阶段的执行回复,只要有任何一个RM执行失败,TM会通知所有RM执行回滚操作,否则,TM将会通知所有RM提交该事务。提交阶段结束资源锁释放。
XA方案问题(基于数据库来实现的2PC)
      1、需要本地数据库支持XA协议。
      2、资源锁需要等到两个阶段结束才释放,性能较差。

Seata方案

Seata是由阿里中间件团队发起的开源的分布式事务框架。
它通过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工作在应用层的中间件。主要优点是性能好,且不长时间占用连接资源,它以高效并且对业务0入侵的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(APC)及TCC模式的分布式事务解决方案。

Seata是对业务无侵入的(只需要一个注解)把一个分布式事务理解成一个包含了若干分支事务全局事务。全局事务协调旗下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。通常分支事务本身就是一个关系数据库的本地事务如下图:
分布式事务_第4张图片分布式事务_第5张图片
TC(Transaction Coordinator)事务协调器:它是独立的中间件,需要独立部署运行,他维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通信协调各个分支事务的提交和回滚。
TM(Transaction Manager)事务管理器:需要嵌入应用程序工作,他负责开启一个全局事务,并最终向TC发起全局提交或回滚的指令。
RM(Resource Manager)控制分支事务:负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动本地事务的提交和回滚。
举个栗子:
分布式事务_第6张图片
流程如下:
      1、用户服务的TM向TC申请开启了一个全局事务,全局事务创建成功并生成一个全局唯一的XID。
      2、用户服务的RM向TC注册分支事务,该分支事务在用户服务执行新增用户逻辑,并将其纳入XID对应全局事务的管辖。
      3、用户服务执行分支事务,向用户表插入一套记录。
      4、逻辑执行到远程调用积分服务室(XID在微服务调用链路上的上下文中传播)。积分服务的RM向TC注册分支事务,该分支事务执行增加积分的逻辑,并将其纳入XID对应全局事务的管辖。
       5、积分服务执行分支事务,想积分记录表插入一条记录,执行完毕后,返回用户服务。
       6、用户服务分支事务执行完毕。
       7、TM向TC发起针对XID的全局提交回滚决议。
       8、TC调度XID下管辖的全部分支植物完成提交或回滚请求。
Seata实现2PC与传统3PC的差别:
      1、架构层次方面:传统2PC方案的RM实际上是在数据库层,RM本质就是数据库资深,通过XA协议实现。而Seata的RM是以JAR包的形式作为中间件层部署在应用程序的一侧的。
      2、两阶段提交方面:传统2PC无论第二阶段的决议是commit或rollback,事务性资源的锁都要保持到两阶段全部完成才释放。而Seata在第一阶段就将本地事务提交,这样就可以省去第二阶段持有锁的时间,整体提高效率。

TCC

原理

TCC是Try、Confirm、Cancel缩写,要求每个分支事务实现三个操作:预处理Try、确认Confirm、撤销Cancel。
事务管理器(TM)首先发起所有的分支事务的预处理操作,任何一个分支事务的预处理操作执行失败,事务管理器将会发起所有分支事务的撤销操作,若预处理全部成功,事务管理器会发起所有分支事务的确认操作,其中确认和撤销失败,事务管理器会进行重试。
分布式事务_第7张图片
分布式事务_第8张图片
TCC分为三个阶段:
       1、Try阶段是业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操作,它和后续的Confirm一起才能真正构成一个完整的业务逻辑。
       2、Confirm阶段是做确认提交,Try阶段所有的分支事务执行成功后开始Confirm。通常情况下,采用TCC则认为Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。若Confirm阶段真的出错了,需要引入重试机制或人工处理。
       3、Cancel阶段是在业务执行错误需要回滚的状态下执行分支事务的业务取消,预留资源释放。通常采用TCC则任务Cancel阶段也是一定成功的。若Cancel阶段真的出错了,需要引入重试机制或人工处理。
       4、TM事务管理器可以实现为独立的服务,也可以让全局事务发起方充当事务管理器角色,事务管理器独立出来是为了称为公用,是为了考虑系统结构和软件复用。
事务管理器在发起全局事务时生成全局事务记录,全局事务记ID贯穿整个分布式事务调用链条,用来记录事务上下文,追踪和记录状态,由于Confirm和Cancle失败需要进行重试,因此需要实现为幂等,幂等性是指同一个操作无论请求多少次,结果都相同。

Hmily方案

Hmily是一个高性能分布式事务TCC开源架构。基于Java语言开发(JDK1.8),支持Dubbo、SpringCloud等RPC框架进行分布式事务。它目前支持一下特性:
      1、支持嵌套事务(Nested transaction support)。
      2、进行事务日志的异步读写,与RPC框架的性能毫无差别。
      3、支持SpringBoot-starter项目启动,使用简单。
      4、RPC框架支持,dubbo、SpringCloud。
      5、本地事务存储支持:redis、mongoDB、Zookeeper、MySQL。
      6、事务日志序列化支持:Java、Hessian。
      7、采用AspectAOP切面思想与Spring无缝继承,天然支持集群。
      8、RPC事务回复,超时异常恢复。
Hmily利用AOP对参与人不是事务的本地方法与远程方法进行拦截处理,通过多方拦截,事务参与者能透明的调用到另一方Try、Confirm、Cancel方法;传递事务上下文;并记录事务日志、酌情进行补偿、重试等。
Hmily不需要事务协调服务,但需要提供一个数据库(MySQL、Mongodb、Zookeeper、Redis)来进行日志存储。
Hmily实现TCC服务与普通服务一样,只需要暴露一个接口,就是他的预处理业务。确认/撤销业务逻辑,只是因为全局事务提交/回滚的需要才提供,因此Confirm/Cancle业务只需要被HmilyTCC事务框架发现即可,不需要被调用他的其他业务所感知。
官网:https://dromara.org/zh-cn/
TCC需要注意三种异常处理分别是空回滚、幂等、悬挂:
空回滚:
      在没有调用TCC资源预处理方法的情况下,调用了二阶段的撤销方法,撤销方法需要识别出这是一个空回滚,然后直接返回成功。
      出现原因是当一个分支事务所在的服务器宕机或网络异常,分支事务调用记录为失败,这是其实是没有执行预处理阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的撤销方法,从而形成空回滚。
      解决思路是要识别出空回滚。需要知道一阶段释放执行,如果执行了,那就是正常回滚。如果没执行,那就是空回滚。前面已经说过事务管理器在发起全局事务时生成事务ID和分支ID,第一阶段Try方法里会插入一条记录,表示一阶段执行了。撤销接口里读取该记录,如果该记录存在,则正常回滚。如果记录不存在,则是空回滚。
幂等
      为了保证TCC二阶段提交重试机制不会引发数据不一致,要求TCC的二阶段预处理、确认、撤销接口保持幂等。这样不会重复使用或释放资源。如果幂等失败,很可能导致数据不一致等严重问题。
      思路执行前查询。
悬挂
      对于一个分布式事务,其二阶段撤销接口比预处理接口先执行。
      出现原因是在RPC调用分支事务Try时,先注册分支事务,在执行RPC调用的网络发生拥堵,通常RPC调用是有超时时间,RPC超时后,事务管理器会通知事务参与者回滚该分布式事务,可能回滚完成后,RPC请求才到达参与者真正执行,而一个预处理方法预留的业务资源,只有该分布式事务才能使用,该分布式第一阶段预留的业务资源就再也没有人能够处理了,对于这种情况,就称为悬挂。即业务资源预留后没法继续处理。
      解决思路如果二阶段执行完成,一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下“分支事务记录”表中是否已经由二阶段事务记录,如果由则不是行预处理。

可靠消息最终一致性

原理

当事务发起方执行完本地事务后并发出一条消息,事务参与者(消费者)一定能够接收消息并处理成功,此方案强调只要消息发给事务参与者最终事务要达成一致。此方案利用消息中间件完成
事务发起者(消息生产者)将消息发给中间件,事务参与者从消息中间件接收消息,事务发起者、事务消费者和消息中间件之间都是通过网络通信,由于网络通信不确定性,会导致分布式事务问题。
在这里插入图片描述
1、本地事务与消息发送的原子问题
      事务发起者在本地事务执行成功后消息必须发出去,否则就丢弃消息。即实现本地事务和消息发送的原子性,要么都成功,要么都失败。
1、如下操作无法保证数据库与发送消息的一致性,因为可能发送消息成功,数据库操作失败。

bean transaction;
//1、发送MQ;
//2、数据库操作;
commit transaction;

2、如下操作,如果发送MQ消息失败,就会抛出异常,导致数据库事务回滚。但如果超时异常,数据库回滚,但MQ消息其实已经正常发送了。同样会导致不一致。

bean transaction;
//1、数据库操作;
//2、发送MQ;
commit transaction;

2、事务参与接收消息的可靠性
      事务参与者必须能够从消息队列接收到消息,如果消息失败可以重复接收消息。
3、消息重复消费问题
      由于网络2的存在,若某个消费节点超时但消费成功,此时消息中间件会重复投递此消息,就导致了消息的重复消费。
      要实现重复消费的问题就要实现事务

解决方案:核心是通过本地事务保证数据业务操作和一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费成功再将消息删除。
分布式事务_第9张图片
      交互流程如下:
            1)在用户服务本地事务新增用户用和“积分消息日志”。(用户表和消息表通过事务保证一致)

begin transaction;
      //1、新增用户;
      //2、存储积分消息日志;
commit transaction;
//3、发送MQ
这种情况下,本地数据库操作与存储积分消息日志处于统一事务中,本地数据库操作与记录消息日志操作具备原子性。

            2)定时对消息日志表中的消息进行扫描并发送至消息中间件,在消息中间件反馈发送成功后删除该消息日志,否则等待定时任务下一周期重试。
            3)使用MQ的ACK(消息确认)机制,消费者监听MQ,如果消费者接收到消息并业务处理完成后想MQ发送ACK,此时说明消费者正常消费消息,MQ将不在想消费者推送消息,否则消费者会不断重试想消费者来发消息。

RocketMQ方案

RocketMQ事务消息设计主要解决了Producer端的消息发送与本地事务执行的原子性问题,RocketMQ的设计中broker与produce端的双向通信能力,使得broker天生可以作为一个事务协调者存在,而RocketMQ本身提供的存储机制为事务消息提供了持久化的能力。RocketMQ的高可用机制和可靠消息设计则为事务消息在系统发生异常时依然能够保证达成事务最终一致性。
在RocketMQ4.3后实现了完整的事务消息,其实是对本地消息表的一个封装,将本地消息表移动到MQ内部,解决Producer端的消息发送与本地事务执行的原子性问题。
分布式事务_第10张图片

1、Producer发送事务消息:Producer(MQ发送者)发送事务消息至MQServer,MQ将消息状态标记为(预备状态),注意此时这条消息消费者(MQ订阅方)是无法消费到的。
2、MQServer回应消息发送成功:MQServer接收到Producer发送给的消息则回应发送成功表示MQ已接收到消息。
3、Producer执行本地事务:Producer端执行业务代码逻辑,通过本地数据库事务控制。
4、消息投递:若Producer本地事务执行成功则自动向MQServer发送Commit消息,MQServer接收到Commit消息后,将消息状态标记为可消费,此时,MQ订阅方正常消费消息。
若Producer本地事务执行失败则自动向MQServer发送rollback消息,MQServer接收到rollback消息后,将删除消息。
MQ订阅方消费消息,消费成功后下则向MQ回应ACK,否则将重复接收消息。这里ACK默认自动回应。
5、事务回查:如果执行Producer端本地事务过程中,执行端挂掉或超时,MQServer将会不停的询问同组其他Producer来获取事务执行状态,者个过程就叫事务回查。MQ会根据事务回查结果来决定是否投递消息。
以上主干流程已由RocketMQ实现,对用户来说需要分别实现本地事务及本地事务回查方法,因此只需要关注事务本身的执行状态即可。(RocketMQ提供RocketMQLocalTransactionListener接口)

最大努力通知

原理

发起通知方通过一定机制最大努力将业务处理结果通知到接收方。
具体包括:
      1、有一定的消息重复通知机制,因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。
      2、消息校对机制,如果尽最大努力也没有通知到接收方,或接收方消费消息后要在此消费,此时可有接收方主动向通知方查询信息来满足需求。
分布式事务_第11张图片

最大努力通知与可靠消息一致性有什么不同

1、解决方案思想不同:
可靠消息一致性,发起方需要保证消息发出去,并且将消息发到接收通知方,消息可靠行关系有发起方来保证。
最大努力通知,发起通方尽最大努力将业务处理结果通知接收方,但消息接收不到,此时需要接收通知方主动调用发起方但接口查询业务处理结果。通知可靠关键在接收方。
2、业务场景不同
可靠消息一致性,管处的是交易过程的事务一致性,以异步的方式完成交易。
最大努力通知关注的是交易后的结果的通知事务,即将交易结果可靠的通知出去。
3、技术解决方向不同
可靠消息一致性要解决消息从发出到接收的一致性,即消息发出并一定被接收到。
最大努力通知无法保证消息从发出到接收的一致性,只提供接收的可靠性机制。可靠机制是,最大努力通知将消息给接收方,当消息无法被接收方接收时,有接收方主动查询消息(业务处理结果)。

MQ的ACK机制方案

利用MQ的ACK机制由MQ向接收方发送通知,流程如下:
1、发起方将通知发给MQ。
注意:如果消息没有发出去可以有接收通知方主动请求发起通知方查询业务执行结果。
2、接收通方监听MQ。
3、接收通方接收消息,业务处理完成回应ACK。
4、接收方若没有回应ACK则MQ会重复通知。(时间间隔1min、5min,逐步拉大通知间隔,直到时间上限)
5、接收方可通过消息校对接口来校对消息一致性。
分布式事务_第12张图片

你可能感兴趣的:(微服务,java,分布式,java,面试)