一、基于XA的两阶段提交方案
两阶段提交方案应用非常广泛,几乎所有商业OLTP数据库都支持XA协议。但是两阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。
二、TCC解决方案
TCC方案在电商、金融领域落地较多。TCC方案其实是两阶段提交的一种改进。其将整个业务逻辑的每个分支显式的分成了Try、Confirm、Cancel三个操作。Try部分完成业务的准备工作,confirm部分完成业务的提交,cancel部分完成事务的回滚。基本原理如下图所示。
事务开始时,业务应用会向事务协调器注册启动事务。之后业务应用会调用所有服务的try接口,完成一阶段准备。之后事务协调器会根据try接口返回情况,决定调用confirm接口或者cancel接口。如果接口调用失败,会进行重试。
微服务倡导服务的轻量化、易部署,而TCC方案中很多事务的处理逻辑需要应用自己编码实现,复杂且开发量大。
三、分布式事务中间件解决方案
分布式事务中间件其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果。典型代表有:阿里的GTS(https://www.aliyun.com/aliware/txc)、开源应用LCN。
其实现原理如下
-
LCN分布式事务框架
a. 在设计框架之初的1.0 ~ 2.0的版本时,框架设计的步骤是如下的,各取其首字母得来的LCN命名。锁定事务单元(lock)、确认事务模块状态(confirm)、通知事务(notify)
b. 创建事务组:是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。
c. 添加事务组:添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息添加通知给TxManager的操作。
d. 关闭事务组:是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager的动作。当执行完关闭事务组的方法以后,TxManager将根据事务组信息来通知相应的参与模块提交或回滚事务
tx-lcn官方地址:https://www.txlcn.org/
tx-lcn Github地址:https://github.com/codingapi/tx-lcn
tx-lcn服务下载地址:https://pan.baidu.com/s/1cLKAeE#list/path=%2F
tx-lcn服务源码地址:https://github.com/codingapi/tx-lcn/tree/master/tx-manager
-
LCN应用 - 搭建tx-manager服务
LCN是通过一个独立的微服务tx-manager作为分布式事务控制服务端(事务协调器)。需要执行分布式事务控制的微服务应用都通过远程服务调用的方式,在tx-manager上标记事务组,在执行事务处理后,将本地事务状态发送到tx-manager中对应的事务组上,tx-manager会根据具体的状态来通知相应的微服务应用提交或回滚。
tx-manager也是使用Spring Cloud开发的一个微服务应用,在搭建过程上是非常简单的。
下载tx-manager事务协调器zip压缩包:https://pan.baidu.com/s/1cLKAeE#list/path=%2F
压缩包解压后内容如下
修改application.properties配置文件,提供本地微服务应用的Eureka注册中心配置、redis配置。其中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#######################
#eureka 地址
eureka.client.service-url.defaultZone=http://127.0.0.1:8761/eureka/
eureka.instance.prefer-ip-address=true
#############################redis-start#########################
##redis 单点环境配置
#redis
spring.redis.database=0
spring.redis.timeout=0
spring.redis.host=192.168.1.136
spring.redis.port=6379
spring.redis.pool.max-active=100
spring.redis.pool.max-wait=3000
spring.redis.pool.max-idle=200
spring.redis.pool.min-idle=50
spring.redis.pool.timeout=600
##############################redis-end##########################
###############################LCN-start########################
tm.transaction.netty.delaytime = 5
tm.transaction.netty.hearttime = 15
tm.redis.savemaxtime=30
tm.socket.port=9999
tm.socket.maxconnection=100
tm.compensate.auto=false
tm.compensate.notifyUrl=http://ip:port/path
tm.compensate.tryTime=30
tm.compensate.maxWaitTime=5000
logging.level.com.codingapi=debug
将修改后的application.properties配置文件打包到tx-manager-x.x.x.jar中,替代jar中原有的默认配置文件。
使用命令:java -jar tx-manager-x.x.x.jar启动微服务。
测试tx-manager事务协调器是否启动成功可以访问http://ip:8899/。如下结果代表事务协调器启动成功
在微服务中使用LCN实现分布式事务管理
在所有需要处理分布式事务的微服务中增加下述依赖:为统一资源版本,使用properties统一管理版本信息
<properties>
<lcn.last.version>4.1.0</lcn.last.version>
</properties>
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>transaction-springcloud</artifactId>
<version>${lcn.last.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>tx-plugins-db</artifactId>
<version>${lcn.last.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
在全局配置文件中增加下述配置
# 定义事务协调器所在位置。根据具体环境定义其中的IP地址和端口。
tm.manager.url=http://127.0.0.1:8899/tx/manager/
使用LCN做分布式事务管理时,微服务应用内必须提供一个用于获取txUrl(txUrl就是全局配置文件中定义的tm.manager.url)的类型实现,这个类可以使用独立应用定义,在微服务应用中引入。本案例中为了方便,直接在所有的微服务应用中提供对应代码实现。具体如下
@Service
public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService {
@Value("${tm.manager.url}")
private String url;
@Override
public String getTxUrl() {
return url;
}
}
在分布式事务管理代码中增加注解@TxTransaction。在业务调用方增加的注解需要属性isStart=true。而被调用方则不需要定义任何的注解属性。如:
交易服务调用了订单服务,那么交易服务中代码
@TxTransaction(isStart=true)
@Transactional
public void trade() {
//本地调用
tradeDao.save();
//远程调用方
orderService.order();
}
订单服务中代码
@Transactional
@TxTransaction
public void order() {
//本地调用
orderDao.save();
}