1 事务的基本概念
1.1 ACID
事务是访问并可能更新数据库中各种数据项的一个程序执行单元。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性: 原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性
个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做 - 一致性
事务必须是使数据库从一个一致性状态变到另一个一致性状态,事务的中间状态不能被观察到的 - 隔离性
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰 - 持久性
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔
离的,并发执行的各个事务之间不能互相干扰
1.2 本地事务
大多数场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务。本地事务的ACID特性是数据库直接提供支持。本地事务应用架构如下所示:
1.3 案例
一个事务单元:下单、减库存、加积分、出库
2 分布式事务应用场景及问题
当下互联网绝大部分公司都进行了数据库拆分和服务化(SOA)、微服务。在这种情况下,完成某一个业务功能可能需要横跨多个服务,操作多个数据库。这就涉及到到了分布式事务,应用需要操作的资源位于多个资源服务器上,而应用需要保证对于多个资源服务器的数据的操作,要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同资源服务器的数据一致性。
2.1 多服务场景下分布式事务
订单、积分、优惠券、出库单,要么全部成功,要么全部失败。
分步式事务就是处理多个进程中的事务,保证这些数据保持数据一致性。
这些事务都处于不同的进程中,不同进程中事务和事务之间没有任何联系。
2.2 多数据源场景下分布式事务
当操作Redis成功MySQL失败时,本地事务能否回滚保证数据一致性?
事务的ACID特性是针对于关系型数据库的,Redis不能使用,MQ不能使用,在这种场景下,本地事务只能回滚MySQL数据,其他的数据不能回滚,数据一致性遭到破坏。
本地事务是不能回滚多个数据源中的事务的,因为多个数据源代表多个进程事务,进程事务之间是相互隔开的。本地事务无法让多个事务进程来达到同时成功同时失败的的效果。
3 分布式事务事务模型
3.1 X/OPEN
X/Open,即现在的open group,是一个独立的组织,主要负责制定各种行业技术标准。X/Open组织主要由各大知名公司或者厂商进行支持,这些公司和厂商不光遵循X/Open组织定义的行业技术标准,也参与到标准的制定。
X/Open制定了分布式事务技术规范。
3.2 DTP模型(分布式事务处理模型)
DTP模型,即Distributed Transaction Processing Reference Model,是X/Open 这个组织定义的一套分布式事务的标准,也就是了定义了规范和API接口,由厂商进行具体的实现。
DTP 定义了五个组件
(1)应用程序(Application Program : AP):定义事务边界(事务开始,结束)
(2)资源管理器 (Resource Manager: RM):任何用来存储数据的服务。
(3)事务管理器(Transaction Manager : TM): 监控事务进度,负责事务提交,回滚。
(4)通信资源管理(Communcation Resource Manager : CRM)
(5)通信协议(负责事务模型之间的通信协议)
TM和RM关系
TM负责RM事务提交、事务回滚
TM是一个服务呢,还是一个jar包呢?
如果TM以一个jar包的形式存在的话,TM融入AP
如果TM以一个服务的形式存在的话,TM就单独部署
-
当TM以jar包形式存在和AP融合在一起,完成分布式事务处理情况
- 如果是多数据源场景分布式事务,处理起来很轻松,比较合适
- 如果是多服务场景分布式事务,可以进行处理,但是比较复杂,TM需要和其他服务的TM保持实时通信,以便于获得其他服务的事务状态。如果服务和服务之间的RPC支持事务传播能力,事务处理相对就会容易。
-
当TM以服务形式存在,完成分布式事务处理情况
- 适合处理多数据源场景分布式事务
- 适合处理多服务场景分布式事务
-
TM以jar包形式存在的技术
- atomikos
- tcc-transaction
- hmily
-
TM以服务形式存在的框架
- 阿里的seata
- codingApi的tx-lcn
全局事务树
当一个DTP模型中,存在多个模型实例时,会形成一种树形条用关系,叫做全局事务树形结构,如下图所示:
对于多服务的分布式事务场景下,TM和AP融合在一起的时候,此AP既是本地事务协调者也是全局事务协调者,下游服务只是事务参与者。
3.3 XA接口规范
TM要管理事务,而RM有很多,比如Redis、MQ、MySQL等,如果没有标准的技术规范会发生什么?每个产品都有自己的一套处理分布式事务的技术,当很多产品一起使用时就会难以融合到一起。
因此open group制定了TM和RM之间接口交互的规范,这样各个产品在处理分布式事务的时候都会实现此接口规范,达到统一技术标准。
XA接口规范定义了RM-TM接口交互的规范(事务注册、开始、回滚、结束)。
3.4 2PC&3PC
3.4.1 2PC
open group组织提出了XA接口规范,在XA接口规范的前提下提出了分布式事务的解决方案,即2PC。
所谓的两阶段提交是TM和RM的关系,不需要程序员去参与。我们所关心的是AP和TM之间的事情,也就是事务的开始和事务的结束。
2PC存在的问题
① 同步阻塞问题。
2PC分布式事务的解决方案是针对数据库提出的,数据库处理事务的时候就是阻塞式处理。
② TM单点故障问题
③ 数据可能出现不一致
但是以上的问题发生的概率极小,因此2PC还是被大量应用在生产环境中,用来在分布式事务场景下,保证数据强一致性。
2PC有一个最大的问题就是性能比较差,同步阻塞会导致服务器卡死,解决方案便是3PC
3.4.2 3PC
3PC是2PC的改进版本。与两阶段提交不同的是,三阶段提交有两个改动点
- 引入超时机制。同时在协调者和参与者中都引入超时机制。
- 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段
zk同步用2PC,选举使用3PC
4 分布式事务解决方案
4.1 JTA
Java事务API(JTA:Java Transaction API)和它的同胞Java事务服务(JTS:Java Transaction Service),为J2EE平台提供了分布式事务服务的能力。 某种程度上,可以认为JTA规范是XA规范的Java版,其把XA规范中规定的DTP模型交互接口抽象成Java接口中的方法,并规定每个方法要实现什么样的功能。
JTA规范提出以后很多厂商实现了JTA规范,比如JBOSS、WEBLOGIC等。项目部署在JBOSS、WEBLOGIC等服务器中可以实现分布式事务支持。tomcat没有实现JTA规范,如何处理分布式事务呢,引入第三方分布式事务处理技术即可,比如Atomikos。
4.2 Atomikos
项目中使用到多数据源的时候大多数采用Atomikos解决分布式事务问题,Atomikos底层是基于XA协议的两阶段提交方案。Atomikos可以认为就是一个jar包
https://www.freesion.com/article/33121426118/
4.3 TX-LCN
TX-LCN由两大模块组成, TxClient、TxManager,TxClient作为模块的依赖框架,提供TX-LCN的标准支持,TxManager作为分布式事务的控制器
事务发起方或者参与都由TxClient端来控制
原理图:
核心步骤
- 创建事务组
是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。 - 加入事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。 - 通知事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。
对于LCN框架来说,控制事务的模式有三种LCN模式、TXC模式、TCC模式。
4.3.1 LCN模式
原理介绍
LCN模式是通过代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理。
模式特点
该模式对代码的嵌入性为低。
该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。
该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。
4.3.2 TXC模式
原理介绍
TXC模式命名来源于淘宝,实现原理是在执行SQL之前,先查询SQL的影响数据,然后保存执行的SQL快照信息和创建锁。当需要回滚的时候就采用这些记录数据回滚数据库,目前锁实现依赖redis分布式锁控制。
业务 | 描述 |
---|---|
update account set price =? where id =? | 拦截每一条SQL |
1 拦截SQL, 解析SQL,执行 update account set price = 100 where id =1 2 查询受影响行数,查询的结果是没有提交之前的数据 select * from account where id =1 3 记录查询结果 200 |
正常业务 |
1 如果TM通知事务提交结束了,那么就删除上面第三步所记录的结果 2 如果TM通知回滚,那么就需要∶ * 通过记录SQL,生成逆向SQL,做补偿 update account set price = 200 where id =1; * 执行SQL |
模式特点
该模式同样对代码的嵌入性低。
该模式仅限于对支持SQL方式的模块支持。
该模式由于每次执行SQL之前需要先查询影响数据,因此相比LCN模式消耗资源与时间要多。
该模式不会占用数据库的连接资源。
LCN模式、TXC模式只能支持关系型数据库,但是真正业务场景中操作的数据源多种多样,如Redis、MySQL、Mongo、MQ等,如果有不同种类的数据源,应该使用TCC模式来解决分布式事务问题。如果对时效性要求不高,也可使用最终消息一致性方案来解决
4.3.3 TCC模式-1
如果多个不同种类的数据源应该使用TCC模式、消息一致性来解决分布式事务
真实业务场景TCC业务补偿机制依靠try、confim、cancel 三类方法
public xxx try(){
//记录日志,日志写入第三方
todo save A 转出了 100 元
todo save B 转入了 100元
//执行转账业务
……
}
public xxx confirm(){
//确认提交
//清空日志
todo save A 转出了 100元
todo save B 转入了100元
}
public xxx cancel(){
//加载日志
A 转账日志log
B 转账日志log
//根据日志进行数据回滚
……
}
这种情况下,每个服务中都需要都有这三个方法。
日志可以用数据库存储,也可以使用ES存储,但是要做到能明确区分所做的事情。
4.3.3 TCC模式-2
TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(业务系统提供的)业务逻辑的调度来实现分布式事务。
主要有三步操作:
Try : 尝试执行业务
Confirm: 确认执行业务
Cancel : 取消执行业务
模式特点
该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。
该模式对有无本地事务控制都可以支持使用面广。
数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。
4.3.3.1 Try
完成所有业务检查(一致性),预留业务资源(准隔离性)
4.3.3.2 Confirm
订单服务内的TCC事务框架会负责跟其他各个服务内的TCC事务框架进行通信,依次调用各个服务的Confirm逻辑
4.3.3.3 cancel
取消Try阶段预留的业务资源
4.3.3.4 遗留问题
(1)cancel或者confirm出现异常了该怎么处理?例如在cancel阶段执行如下三行代码,第二行出现异常了,第三行没跑就退出了,怎么办?
orderClient.cancelUpdateStatus();
accountClient.cancelDecrease();
repositoryClient.cancelDecrease();
需要对此进行业务补偿!
(2)大量逻辑重复
try{
xxclient.try();
}catch(Throwable t){
xxclient.cancel();
throw t;
}
xxclient.confirm();
可以使用AOP切面来避免重复代码。
对于多服务场景下的分布式事务的处理,各个服务所用的TX-LCN框架处理分布式事务的模式可以不同,LCN模式、TXC模式、TCC模式可以混用。
4.4 最终消息一致性
实际系统的开发过程中,可能服务间的调用是异步的,那么如何保证这种异步的各个服务间的分布式事务呢?
中小型企业保持数据一致性用的最多的还是最终消息一致性的方案。消息一致性的思想是把分布式的事务转换成本地事务进行执行。
- 本地消息表作用
一是保证消息不会丢失
二是检测消息有没有被消费,如果没有被消费可重新投递。
下单服务中的下订单和写消息在一个事务中,属于本地事务。
积分服务中的加积分和写消息在一个事务中,属于本地事务。
如果积分服务在业务上执行失败,可以给生产方发送补偿消息,通知生产方进行回滚操作。
如果积分服务在业务上执行失败,又不给生产方发送补偿消息,有没有什么别的方式可以通知到订单服务去回滚呢?
可以借助第三方的一个东西,比如zookeeper,利用其监听的功能,订单服务注册监听器,监听积分服务,当积分服务异常了,订单服务会及时的感知积分服务的状态,做出相应的处理。假如积分服务消费消息重试很多次也没有消费成功,又不想通过人工解决(过了好多天了,消息被清掉了),怎么样做数据的一致性处理呢?
定时扫描订单服务的消息表,把没有处理完的消息或者失败的消息再发送一遍。
5 LCN分布式事务控制效果演示
1)不控制分布式事务,模拟异常
测试结果: 结果本地事务回滚,远程服务事务没有回滚。
2)lcn控制事务(无侵入式控制事务,只需要加注解即可控制),模拟异常
测试结果:本地事务,远程服务事务,都是些回滚。
先启动TXLCN-TM项目(事务协调者服务)访问7970端口,密码codingapi,在项目中有
再启动eureka注册中心
再启动lcn-transaction-demo-parent中的
lcn-transaction-provider项目
lcn-transaction-consumer项目
注释掉consumer中的相关代码
@lcntransaction
@enabletransaction
provider和consumer是注册到TM中的
官网:txlcn.org/zh-cn/docs/principle/lcn.html
代码例子
txlcn-demo项目中的
txlcn-demo-spring的abc三个服务
DemoServiceImpl中的代码
秒杀的时候会将分布式事务解决方案的具体案例。
https://blog.csdn.net/qq_26222859/article/details/52067011
https://www.cnblogs.com/huanzi-qch/p/11057974.html
https://www.cnblogs.com/fangh816/p/13490475.html
https://blog.csdn.net/crazylai1996/article/details/106309461
https://blog.csdn.net/cailianren1/article/details/85283397
https://blog.csdn.net/qq_42556214/article/details/105796048