一、分布式事物产生的背景
在微服务环境下,因为会根据不同的业务会拆分成不同的服务,比如会员服务、订单服务、商品服务等,让专业的人做专业的事情,每个服务都有自己独立的数据库,并且是独立运行,互不影响。
服务与服务之间通讯采用RPC远程调用技术,但是每个服务中都有自己独立的数据源,即自己独立的本地事务。两个服务相互通讯的时候,两个本地事务互不影响,从而出现分布式事务产生的原因。
传统项目大部分情况下,不会产生分布式事务,但是在项目中如果采用多数据源方式。 分布式环境
分布式事务案例说明-下单扣库
二、分布式事物
1、分布式系统
部署在不同结点上的系统通过网络交互来完成协同工作的系统。比如:充值加积分的业务,用户在充值系统向自己的账户充钱,在积分系统中自己积分相应的增加。
充值系统和积分系统是两个不同的系统,一次充值加积分的业务就需 要这两个系统协同工作来完成。
2、事物
所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。(执行单个逻辑功能的一组指令或操作称为事务)
原子性(Atomicity):执行单元中的操作要么全部执行成功,要么全部失败。如果有一部分成功一部分失败那么成功的操作要全部回滚到执行前的状态
一致性(Consistency):执行一次事务会使用数据从一个正确的状态转换到另一个正确的状态,执行前后数据都是完整的。
隔离性(Isolation):在该事务执行的过程中,任何数据的改变只存在于该事务之中,对外界没有影响,事务与事务之间是完全的隔离的。只有事务提交后其它事务才可以查询到最新的数据。
持久性(Durability):事务完成后对数据的改变会永久性的存储起来,即使发生断电宕机数据依然在。
3、本地事物
本地事务就是用关系数据库来控制事务,关系数据库通常都具有ACID特性,传统的单体应用通常会将数据全部存储在一个数据库中,会借助关系数据库来完成事务控制。
4、分布式事物
在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。
这里强调的是多个系统通过网络协同完成一个事务的过程,并不强调多个系统访问了不同的数据库,即使多个系统访问的是同一个数据库也是分布式事务,
如下图:
另外一种分布式事务的表现是,一个应用程序使用了多个数据源连接了不同的数据库,当一次事务需要操作多个数据源,此时也属于分布式事务,当系统作了数据库拆分后会出现此种情况。
三、解决分布式事务基本思路
1、ACID酸碱平衡理论
如何保证强一致性呢?关系型数据库天生就是解决具有复杂事务场景的问题,关系型数据库完全满足ACID的特性。
数据库管理系统中事务(transaction)的四个特性(分析时根据首字母缩写依次解释):
原子性(Atomicity):执行单元中的操作要么全部执行成功,要么全部失败。如果有一部分成功一部分失败那么成功的操作要全部回滚到执行前的状态
一致性(Consistency):执行一次事务会使用数据从一个正确的状态转换到另一个正确的状态,执行前后数据都是完整的。
隔离性(Isolation):在该事务执行的过程中,任何数据的改变只存在于该事务之中,对外界没有影响,事务与事务之间是完全的隔离的。只有事务提交后其它事务才可以查询到最新的数据。
持久性(Durability):事务完成后对数据的改变会永久性的存储起来,即使发生断电宕机数据依然在。
所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。(执行单个逻辑功能的一组指令或操作称为事务)
2、CAP(帽子 原理)
由于对系统或者数据进行了拆分,我们的系统不再是单机系统,而是分布式系统,图解CAP理论·
一致性: (Consistency):服务A、B、C三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。
可用性:(Availability):服务A、B、C三个结点,其中一个结点宕机不影响整个集群对外提供服务,如果只有服务A结点,当服务A宕机整个系统将无法提供服务,增加服务B、C是为了保证系统的可用性。
P: Partition tolerance,分区容忍性。尽管网络上有部分消息丢失,但系统仍然可继续工作。
CAP原理证明,任何分布式系统只可同时满足以上两点,无法三者兼顾。由于关系型数据库是单节点无复制的,因此不具有分区容忍性,但是具有一致性和可用性,而分布式的服务化系统都需要满足分区容忍性,
那么我们必须在一致性和可用性之间进行权衡。如果在网络上有消息丢失,也就是出现了网络分区,则复制操作可能会被延后,如果这时我们的使用方等待复制完成再返回,则可能导致在有限时间内无法返回,就失去了可用性:而如果 使用方不等待复制完成,而在主分片写完后直接返回,则具有了可用性,但是失去了一致性。
CAP有哪些组合方式?
1、CA:放弃分区容忍性,加强一致性和可用性,关系数据库按照CA进行设计。
2、AP:放弃一致性,加强可用性和分区容忍性,追求最终一致性,很多NoSQL数据库按照AP进行设计。
说明:这里放弃一致性是指放弃强一致性,强一致性就是写入成功立刻要查询出最新数据。追求最终一致性是指允许暂时的数据不一致,只要最终在用户接受的时间内数据 一致即可。
3、CP:放弃可用性,加强一致性和分区容忍性,一些强一致性要求的系统按CP进行设计,比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。
说明:由于网络问题的存在CP系统可能会出现待等待超时,如果没有处理超时问题则整理系统会出现阻塞。
总结: 在分布式系统设计中AP的应用较多,即保证分区容忍性和可用性,牺牲数据的强一致性(写操作后立刻读取到最新数据),
保证数据最终一致性。比如:订单退款,今日退款成功,明日账户到账,只要在预定的用户可以接受的时间内退款事务走完即可。
3、Base(碱)
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的简写,由 eBay 架构师 Dan Pritchett 于 2008 年在《BASE: An Acid Alternative》(论文地址点 这里)论文中首次提 出。BASE 思想与 ACID 原理截然不同,它满足 CAP 原理,通过牺牲强一致性获得可用性, 一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求。
BASE 模型包含如下三个元素:
- BA:(Basically Available ),基本可用。
- S:( Soft State),软状态,状态可以在一段时间内不同步。
- E:(Eventually Consistent ),最终一致,在一定的时间窗口内, 最终数据达成一致即可。
关于最终一致的几种变种参见上面,在实际系统实践中,可以将若干变种结合起来,来实现各种业务需求。
4、柔性事务和刚性事务
柔性事务满足BASE理论(基本可用,最终一致)
刚性事务满足ACID理论
柔性事务分为
1. 两阶段型
2. 补偿型
3. 异步确保型
4. 最大努力通知型几种。 由于支付宝整个架构是SOA架构,因此传统单机环境下数据库的ACID事务满足了分布式环境下的业务需要,以上几种事务类似就是针对分布式环境下业务需要设定的
分析案例图:
四、分布式事务解决方案
1、传统模式Jta+Atomikos
传统项目中,比如项目中使用到多数据源的时候大多数采用jta+Atomikos解决分布式事务问题,jta+Atomikos底层是基于XA协议的两阶段提交方案。
XA协议:XA 事务的基础是两阶段提交协议。需要有一个事务协调者来保证所有的事务参与者都完成了准备工作(第一阶段)。
如果协调者收到所有参与者都准备好的消息,就会通知所有的事务都可以提交了(第二阶段)。Mysql 在这个XA事务中扮演的是参与者的角色,而不是协调者(事务管理器)。
JTA:JTA(java Transaction API)是JavaEE 13 个开发规范之一。java 事务API,允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据。
JDBC驱动程序的JTA支持极大地增强了数据访问能力。事务最简单最直接的目的就是保证数据的有效性,
数据的一致性 Atomikos:Atomikos TransactionsEssentials 是一个为Java平台提供增值服务的并且开源类事务管理器
2、两阶段提交协议(2PC)
参考:https://en.wikipedia.org/wiki/Two-phase_commit_protocol
第一阶段: 准备阶段:协调者向参与者发起指令,参与者评估自己的状态,如果参与者评估指令可以完成,则会写redo或者undo日志,让后锁定资源,执行操作,但并不提交。
第二阶段:提交(commit)/回滚(rollback)阶段:如果每个参与者明确返回准备成功,则协调者向参与者发送提交指令,参与者释放锁定的资源,如何任何一个参与者明确返回准备失败,则协调者会发送回滚指令,
参与者取消已经变更的事务,释放锁定的资源。
两阶段提交方案应用非常广泛,几乎所有商业OLTP数据库都支持XA协议。但是两阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。
缺点:如果协调者宕机,参与者没有协调者指挥,则会一直阻塞。整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。
优点:实现强一致性,部分关系数据库支持(Oracle、MySQL等)。
一个下单减库存的例子:
1、 应用程序连接两个数据源。
2、 应用程序通过事务协调器向两个库发起prepare,两个数据库收到消息分别执行本地事务(记录日志),但不提交,如果执行成功则回复yes,否则回复no。
3、 事务协调器收到回复,只要有一方回复no则分别向参与者发起回滚事务,参与者开始回滚事务。
4、 事务协调器收到回复,全部回复yes,此时向参与者发起提交事务。如果参与者有一方提交事务失败则由事务协调器发起回滚事务。
3、三阶段提交协议(3PC)
参考: https://en.wikipedia.org/wiki/Three-phase_commit_protocol
三阶段提交协议是两阶段提交协议的改进版本。它通过超时机制解决了阻塞的问题,并且把两个阶段增加为三个阶段:
询问阶段:协调者询问参与者是否可以完成指令,协调者只需要回答是还是不是,而不需要做真正的操作,这个阶段超时导致中止。
准备阶段:如果在询问阶段所有的参与者都返回可以执行操作,协调者向参与者发送预执行请求,然后参与者写redo和undo日志,执行操作,但是不提交操作;
如果在询问阶段任何参与者返回不能执行操作的结果,则协调者向参与者发送中止请求,这里的逻辑与两阶段提交协议的的准备阶段是相似的,这个阶段超时导致成功
提交阶段:如果每个参与者在准备阶段返回准备成功,也就是预留资源和执行操作成功,协调者向参与者发起提交指令,参与者提交资源变更的事务,释放锁定的资源;
如果任何一个参与者返回准备失败,也就是预留资源或者执行操作失败,协调者向参与者发起中止指令,参与者取消已经变更的事务,执行undo日志,释放锁定的资源,这里的逻辑与两阶段提交协议的提交阶段一致
4、 2PC和3PC的区别
增加了一个询问阶段,询问阶段可以确保尽可能早的发现无法执行操作而需要中止的行为,但是它并不能发现所有的这种行为,
只会减少这种情况的发生在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,协调者和参与者都继续提交事务,默认为成功,
这也是根据概率统计上超时后默认成功的正确性最大 三阶段提交协议与两阶段提交协议相比,具有如上的优点,但是一旦发生超时,
系统仍然会发生不一致,只不过这种情况很少见罢了,好处就是至少不会阻塞和永远锁定资源。
5、事务补偿(TCC)
TCC事务补偿是基于2PC实现的业务层事务控制方案,它是Try、Confirm和Cancel三个单词的首字母,含义如下:
1、 Try 检查及预留业务资源
完成提交事务前的检查,并预留好资源。
2、Confirm 确定执行业务操作
对try阶段预留的资源正式执行。
3、Cancel 取消执行业务操作 对try阶段预留的资源释放。
下边用一个下单减库存的业务为例来说明:
1、Try
下单业务由订单服务和库存服务协同完成,在try阶段订单服务和库存服务完成检查和预留资源。
订单服务检查当前是否满足提交订单的条件(比如:当前存在未完成订单的不允许提交新订单)。
库存服务检查当前是否有充足的库存,并锁定资源。
2、Confirm
订单服务和库存服务成功完成Try后开始正式执行资源操作。
订单服务向订单写一条订单信息。
库存服务减去库存。
3、Cancel
如果订单服务和库存服务有一方出现失败则全部取消操作。
订单服务需要删除新增的订单信息。
库存服务将减去的库存再还原。
优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。
缺点:开发成本高,每个事务操作每个参与者都需要实现try/confirm/cancel三个接口。
注意:TCC的try/confirm/cancel接口都要实现幂等性,在为在try、confirm、cancel失败后要不断重试。
什么是幂等性?
幂等性是指同一个操作无论请求多少次,其结果都相同。
幂等操作实现方式有:
1、 操作之前在业务方法进行判断如果执行过了就不再执行。
2、 缓存所有请求和处理的结果,已经处理的请求则直接返回结果。
3、在数据库表中加一个状态字段(未处理,已处理),数据操作时判断未处理时再处理。
优点:让应用自己定义数据库操作的粒度,使得降低锁冲突、提高吞吐量成为可能
缺点:
- 对应用的侵入性强。业务逻辑的每个分支都需要实现try、confirm、cancel三个操作,应用侵入性较强,改造成本高。
- 实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求,confirm和cancel接口必须实现幂等
TCC 事务应用场景
我们通过用户下单使用余额+红包支付来看一下TCC事务的具体应用。
假设用户下单操作来自3个系统下单系统、资金账户系统、红包账户系统,下单成功需要同时调用资金账户服务和红包服务完成支付
假设购买商品1000元,使用账户红包200元,余额800元,确认支付。
-
Try操作
tryX 下单系统创建待支付订单
tryY 冻结账户红包200元
tryZ 冻结资金账户800元 -
Confirm操作
confirmX 订单更新为支付成功
confirmY 扣减账户红包200元
confirmZ 扣减资金账户800元 -
Cancel操作
cancelX 订单处理异常,资金红包退回,订单支付失败
cancelY 冻结红包失败,账户余额退回,订单支付失败
cancelZ 冻结余额失败,账户红包退回,订单支付失败
代码实现地址:参考https://www.liangzl.com/get-article-detail-525.html
6、消息队列实现最终一致
本方案是将分布式事务拆分成多个本地事务来完成,并且由消息队列异步协调完成,如下图:下边以下单减少库存为例来说明:
1、订单服务和库存服务完成检查和预留资源。
2、订单服务在本地事务中完成添加订单表记录和添加“减少库存任务消息”。
3、 由定时任务根据消息表的记录发送给MQ通知库存服务执行减库存操作。
4、库存服务执行减少库存,并且记录执行消息状态(为避免重复执行消息,在执行减库存之前查询是否执行过此消息)。
5、库存服务向MQ发送完成减少库存的消息。
6、订单服务接收到完成库存减少的消息后删除原来添加的“减少库存任务消息”。
实现最终事务一致要求:预留资源成功理论上要求正式执行成功,如果执行失败会进行重试,要求业务执行方法实现幂等。
优点 :
由MQ按异步的方式协调完成事务,性能较高。
不用实现try/confirm/cancel接口,开发成本比TCC低。
缺点:
此方式基于关系数据库本地事务来实现,会出现频繁读写数据库记录,浪费数据库资源,另外对于高并发操作不是最佳方案。
7、基于LCN框架解决分布式事务
LCN官网 https://www.txlcn.org/
"LCN并不生产事务,LCN只是本地事务的搬运工"
兼容 dubbo、springcloud、motan 框架,支持各种关系型数据库
LCN框架底层实现原理参考官方文档:
详细参考: https://github.com/codingapi/tx-lcn/wiki/LCN%E5%8E%9F%E7%90%86
TC微服务模块
微服务示例架构
- 服务甲作为DTX发起方,远程调用服务乙
TxMassage的原理图:
SpringCloud2.0整合LCN
目前LCN版本已经升级为4.0了,但是官方没有SpringCloud2.0的demo案例。
因为LCN本身是开源的,网上有大牛对LCN框架源码做修改,可以支持SpringCloud2.0版本。
整合步骤:
1、搭建一个注册中心
工程截图:
application.yml
server.port=8081
spring.application.name=eureka-server
eureka.instance.hostname=localhost
eureka.client.serviceUrl.defaultZone= http://${eureka.instance.hostname}:${server.port}/eureka
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
pom文件
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
</parent>
<!-- 管理依赖 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!--SpringCloud eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2、导入LCN框架代码tx-lcn-2.0
官方下载只能支持springboot1.x,被大牛修改之后,兼容springboot2.x
工程截图:
tx-manager工程截图:
tx-manager工程application.properties
修改注册中心地址,redis地址
#######################################txmanager-start#################################################
#服务端口
server.port=8899
#tx-manager不得修改
spring.application.name=tx-manager
spring.mvc.static-path-pattern=/**
spring.resources.static-locations=classpath:/static/
#######################################txmanager-end#################################################
#zookeeper地址
#spring.cloud.zookeeper.connect-string=127.0.0.1:2181
#spring.cloud.zookeeper.discovery.preferIpAddress = true
#eureka 地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8080/eureka/
eureka.instance.prefer-ip-address=true
#######################################redis-start#################################################
#redis 配置文件,根据情况选择集群或者单机模式
##redis 集群环境配置
##redis cluster
#spring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
#spring.redis.cluster.commandTimeout=5000
##redis 单点环境配置
#redis
#redis主机地址
spring.redis.host=localhost
#redis主机端口
spring.redis.port=6379
#redis链接密码
#spring.redis.password=123456
spring.redis.pool.maxActive=10
spring.redis.pool.maxWait=-1
spring.redis.pool.maxIdle=5
spring.redis.pool.minIdle=0
spring.redis.timeout=0
#####################################redis-end###################################################
#######################################LCN-start#################################################
#业务模块与TxManager之间通讯的最大等待时间(单位:秒)
#通讯时间是指:发起方与响应方之间完成一次的通讯时间。
#该字段代表的是Tx-Client模块与TxManager模块之间的最大通讯时间,超过该时间未响应本次请求失败。
tm.transaction.netty.delaytime = 15
#业务模块与TxManager之间通讯的心跳时间(单位:秒)
tm.transaction.netty.hearttime = 15
#存储到redis下的数据最大保存时间(单位:秒)
#该字段仅代表的事务模块数据的最大保存时间,补偿数据会永久保存。
tm.redis.savemaxtime=30
#socket server Socket对外服务端口
#TxManager的LCN协议的端口
tm.socket.port=9999
#最大socket连接数
#TxManager最大允许的建立连接数量
tm.socket.maxconnection=100
#事务自动补偿 (true:开启,false:关闭)
# 说明:
# 开启自动补偿以后,必须要配置 tm.compensate.notifyUrl 地址,仅当tm.compensate.notifyUrl 在请求补偿确认时返回success或者SUCCESS时,才会执行自动补偿,否则不会自动补偿。
# 关闭自动补偿,当出现数据时也会 tm.compensate.notifyUrl 地址。
# 当tm.compensate.notifyUrl 无效时,不影响TxManager运行,仅会影响自动补偿。
tm.compensate.auto=true
#事务补偿记录回调地址(rest api 地址,post json格式)
#请求补偿是在开启自动补偿时才会请求的地址。请求分为两种:1.补偿决策,2.补偿结果通知,可通过通过action参数区分compensate为补偿请求、notify为补偿通知。
#*注意当请求补偿决策时,需要补偿服务返回"SUCCESS"字符串以后才可以执行自动补偿。
#请求补偿结果通知则只需要接受通知即可。
#请求补偿的样例数据格式:
#{"groupId":"TtQxTwJP","action":"compensate","json":"{"address":"133.133.5.100:8081","className":"com.example.demo.service.impl.DemoServiceImpl","currentTime":1511356150413,"data":"C5IBLWNvbS5leGFtcGxlLmRlbW8uc2VydmljZS5pbXBsLkRlbW9TZXJ2aWNlSW1wbAwSBHNhdmUbehBqYXZhLmxhbmcuT2JqZWN0GAAQARwjeg9qYXZhLmxhbmcuQ2xhc3MYABABJCo/cHVibGljIGludCBjb20uZXhhbXBsZS5kZW1vLnNlcnZpY2UuaW1wbC5EZW1vU2VydmljZUltcGwuc2F2ZSgp","groupId":"TtQxTwJP","methodStr":"public int com.example.demo.service.impl.DemoServiceImpl.save()","model":"demo1","state":0,"time":36,"txGroup":{"groupId":"TtQxTwJP","hasOver":1,"isCompensate":0,"list":[{"address":"133.133.5.100:8899","isCompensate":0,"isGroup":0,"kid":"wnlEJoSl","methodStr":"public int com.example.demo.service.impl.DemoServiceImpl.save()","model":"demo2","modelIpAddress":"133.133.5.100:8082","channelAddress":"/133.133.5.100:64153","notify":1,"uniqueKey":"bc13881a5d2ab2ace89ae5d34d608447"}],"nowTime":0,"startTime":1511356150379,"state":1},"uniqueKey":"be6eea31e382f1f0878d07cef319e4d7"}"}
#请求补偿的返回数据样例数据格式:
#SUCCESS
#请求补偿结果通知的样例数据格式:
#{"resState":true,"groupId":"TtQxTwJP","action":"notify"}
tm.compensate.notifyUrl=http://s3.zhubanxian.com/compernstate?action=notify
#补偿失败,再次尝试间隔(秒),最大尝试次数3次,当超过3次即为补偿失败,失败的数据依旧还会存在TxManager下。
tm.compensate.tryTime=30
#各事务模块自动补偿的时间上限(毫秒)
#指的是模块执行自动超时的最大时间,该最大时间若过段会导致事务机制异常,该时间必须要模块之间通讯的最大超过时间。
#例如,若模块A与模块B,请求超时的最大时间是5秒,则建议改时间至少大于5秒。
tm.compensate.maxWaitTime=5000
#######################################LCN-end#################################################
logging.level.com.codingapi=debug
启动注册中心,启动tx-manger,启动之前先启动redis服务,在启动tx-manger,否则会报错
访问:http://localhost:8899/
效果图:
3、创建订单服务
工程目录结构
注意下面2个类必须引入,不引人要报错,这个类要在官网的demo案例里面找
LCN基本配置代码
TxManagerHttpRequestServiceImpl类代码:
@Service
public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{
@Override
public String httpGet(String url) {
System.out.println("httpGet-start");
String res = HttpUtils.get(url);
System.out.println("httpGet-end");
return res;
}
@Override
public String httpPost(String url, String params) {
System.out.println("httpPost-start");
String res = HttpUtils.post(url,params);
System.out.println("httpPost-end");
return res;
}
}
TxManagerTxUrlServiceImpl类代码
@Service
public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService {
@Value("${tm.manager.url}")
private String url;
@Override
public String getTxUrl() {
System.out.println("load tm.manager.url ");
return url;
}
}
application.yml文件
###服务启动端口号
server:
port: 8010
###服务名称(服务注册到eureka名称)
spring:
application:
name: antmeite-order
#数据库连接信息
datasource:
name: test
url: jdbc:mysql://127.0.0.1:3306/clouddb03?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=TRUE
username: root
password: root
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
###服务注册到eureka地址
eureka:
client:
service-url:
defaultZone: http://localhost:8080/eureka
###设置feign客户端超时时间
ribbon:
###指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间。
ReadTimeout: 100000
###指的是建立连接后从服务器读取到可用资源所用的时间。
ConnectTimeout: 100000
#tx-manger
tm:
manager:
url: http://127.0.0.1:8899/tx/manager/
新增maven依赖
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>transaction-springcloud</artifactId>
<version>4.1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>tx-plugins-db</artifactId>
<version>4.1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
代码:
@RestController
public class IOrderServiceImpl extends BaseApiService implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockFeign stockFeign;
// 下单扣库存
/**
* 使用@TxTransaction解决分布式事务 isStart true 是:是发起方 false 否:是参与方
* 同理生产者 同样配置集成即可,只需要把Transaction 中的isStart改为fasle即可。
* @param i
* @return
* @throws Exception
*/
@TxTransaction(isStart = true)
@Transactional
@GetMapping(value = "/addOrderAndStock")
public ResponseBase addOrderAndStock(int i) throws Exception {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setName("蚂蚁课堂永久会员充值");
orderEntity.setOrderCreatetime(new Date());
// 价格是300元
orderEntity.setOrderMoney(300d);
// 状态为 未支付
orderEntity.setOrderState(0);
Long commodityId = 30l;
// 商品id
orderEntity.setCommodityId(commodityId);
// 1.先下单,创建订单
int orderResult = orderMapper.addOrder(orderEntity);
System.out.println("orderResult:" + orderResult);
if (orderResult <= 0) {
return setResultError("下单失败!");
}
// 2.下单成功后,调用库存服务
ResponseBase inventoryReduction = stockFeign.inventoryReduction(commodityId);
if (inventoryReduction.getRtnCode() != 200) {
// 1.使用手动事务 -
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// 2.获取将异常抛出给上一层,外面回滚。
throw new Exception("调用库存服务接口失败,开始回退订单事务代码");
}
int reuslt = 1 / i;
return setResultSuccess("下单成功!");
}
}
同理搭建库存服务,然后测试
代码参考:https://github.com/yhcxy/config