一、需求
扣减库存服务和生成订单服务对应不同数据库,Spring本地事务@Transactional并不能解决跨库跨服务保证数据一致性。分布式事务一般包含事务的发起者和参与者、关系型数据库资源服务以及事务管理器;Distributed Transaction Framework流行的主要有TX-LCN和阿里的seata框架,下一篇会调研下seata框架。
TX-LCN分布式事务框架是一款开源分布式事务框架,由两大模块组成TxClient和TxManager,TxClient扮演发起者和参与者,TxManager扮演事务管理器协调事务;从开发角度讲,TxClient指的是是我们自己的服务系统,TxManager是事务中心的协调系统;从Github RELEASE版本看,最新是5.0.2.RELEASE(支持LCN TXC TCC 三种事务模式),项目为了稳定使用v4.1.0(默认只支持LCN模式);LCN模式基本原理是代理切面拦截所有数据库链接的提交和回滚,由代理连接对象控制本地事务的真正的提交、回滚和释放。若是存在与非关系型数据库redis,就需要TCC模式补偿操作,来保证非关系redis和关系mysql整体一致性。
二、开始准备
1、准备mysql和redis环境,通过spring initializr快速准备eureka注册中心。
2、下载[v4.1.0][]版本,tx-lcn-4.1.0是spring boot项目需要eureka注册中心服务和redis,运行启动类com.codingapi.tm.TxManagerApplication。
3、访问http://127.0.0.1:8899/TxManager管理界面,注意两个属性负载均衡服务器地址的端口和当前连接数,这是实验成功的截图,一开始当前连接数应该是0。
4、springcloud LCN分布式事务v4.0 示例demo
根据教程引导创建相应的数据库和修改配置,重点标注重要配置。我们重点关系jdbc版本的springcloud-jdbc-demo,它涉及到了5个业务模块,工程从1到5对应端口port:8081到8085,控制器的接口前缀是localhost:port/demo,分list列表接口和save接口;在save方法中,在demo3(调用4和5,自己)和demo1(调用2和3,自己)是事务发起方,两者差别是demo3注释了异常能正常返回insert 3条数据,而demo1打开异常触发分布式事务回滚insert数据;demo2、demo4和demo5仅仅是事务参与方。
feign.hystrix.enabled=false
spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.url= jdbc:mysql://localhost:3306/test
spring.datasource.username= root
spring.datasource.password=root
spring.datasource.initialize = true
init-db= true
spring.application.name = demo3
server.port = 8083
#${random.int[9000,9999]},注册中心端口要对应
eureka.client.service-url.defaultZone=http://127.0.0.1:8761/eureka/
feign.hystrix.enabled=true
# 关于**springcloud-hystrix机制,选择信号量隔离** http://www.jianshu.com/p/b8d21248c9b1
hystrix.command.default.execution.isolation.strategy= SEMAPHORE
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000
#Ribbon的负载均衡策略,重试次数为0
ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
ribbon.MaxAutoRetriesNextServer=0
#**txmanager地址端口指的是TxManager管理界面的负载均衡服务器地址的端口**
tm.manager.url=http://127.0.0.1:8899/tx/manager/
logging.level.com.codingapi=debug
以demo1的DemoServiceImpl异常触发分布式为例,重点是@TxTransaction(isStart = true)标注事务发起方,否则ThreadLocal不会有groupid,那就不会有事务组,更不可能实现回滚事务。
未标记发起方出现异常则groupId为空情况:
2020-05-30 17:30:06.387 DEBUG 4964 --- [nio-8084-exec-8] c.c.t.s.interceptor.TransactionAspect : annotation-TransactionRunning-start---->
2020-05-30 17:30:06.387 DEBUG 4964 --- [nio-8084-exec-8] c.c.t.a.s.impl.AspectBeforeServiceImpl : around--> groupId-> null,txTransactionLocal->null
2020-05-30 17:30:06.387 DEBUG 4964 --- [nio-8084-exec-8] c.c.t.d.aspect.DataSourceAspect : getConnection-start---->
2020-05-30 17:30:06.387 DEBUG 4964 --- [nio-8084-exec-8] c.c.tx.datasource.AbstractResourceProxy : loadConnection -> null !
DemoServiceImpl
package com.example.demo.service.impl;
import com.example.demo.client.Demo2Client;
import com.example.demo.client.Demo3Client;
import com.example.demo.dao.TestDao;
import com.example.demo.entity.Test;
import com.example.demo.service.DemoService;
import com.codingapi.tx.annotation.TxTransaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* Created by lorne on 2017/6/26.
*/
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private TestDao testDao;
@Autowired
private Demo2Client demo2Client;
@Autowired
private Demo3Client demo3Client;
@Override
public List<Test> list() {
return testDao.list();
}
@Override
**@TxTransaction(isStart = true)**
@Transactional
public int save() {
int rs2 = demo2Client.save();
int rs3 = demo3Client.save();
int rs1 = testDao.save();
int v = 100/0;
return rs1+rs2+rs3;
}
}
访问demo1的save接口
//访问save接口:http://localhost:8081/demo/save,触发异常
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sat May 30 17:33:11 CST 2020
There was an unexpected error (type=Internal Server Error, status=500).
Demo3Client#save() failed and fallback failed.
触发回滚JdbcDemo2Application控制台正确日志:
#触发回滚
2020-05-30 17:33:11.654 DEBUG 4468 --- [ntLoopGroup-2-1] c.c.tx.netty.handler.TransactionHandler : TxManager-response->{"a":"t","c":0,"t":"9Fxhh19M","k":"62kQVGPh"}
2020-05-30 17:33:11.654 INFO 4468 --- [ool-1-thread-19] c.c.t.c.service.impl.ActionTServiceImpl : accept notify data ->{"a":"t","c":0,"t":"9Fxhh19M","k":"62kQVGPh"}
lcn transaction over, res -> groupId:3OBwlhvN and state is rollback
2020-05-30 17:33:11.657 DEBUG 4468 --- [ Thread-28] c.c.t.d.relational.LCNDBConnection : lcnConnection closed groupId:3OBwlhvN
2020-05-30 17:33:11.658 INFO 4468 --- [ool-1-thread-19] c.c.t.c.service.impl.ActionTServiceImpl : accept notify response res ->1
2020-05-30 17:33:11.658 DEBUG 4468 --- [ool-1-thread-19] .c.t.c.s.i.TransactionControlServiceImpl : send notify data ->{"p":{"d":"1"},"a":"t","k":"62kQVGPh"}
2020-05-30 17:33:11.659 DEBUG 4468 --- [ntLoopGroup-2-1] c.c.tx.netty.handler.TransactionHandler : TxManager-response->{"d":"","k":"62kQVGPh"}
#clent和manager的心跳数据
2020-05-30 17:33:26.659 DEBUG 4468 --- [ntLoopGroup-2-1] c.c.tx.netty.handler.TransactionHandler : hart data --->{"p":"{}","a":"h","k":"h"}
2020-05-30 17:33:26.659 DEBUG 4468 --- [ntLoopGroup-2-1] c.c.tx.netty.handler.TransactionHandler : TxManager-response->{"d":"5","k":"h"}
三、4.0与5.0版本差别
5、0版本从1月份开始大量提交,并已经交由codingApi团队开发维护,两个版本的注解源码是不同的。
/** **4.0版本**
* Created by lorne on 2017/6/26.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TxTransaction {
/**
* 是否LCN事务发起方
* @return true 是:是发起方 false 否:是参与方
*/
boolean isStart() default false;
/**
* 回滚异常
* @return
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* 不回滚异常
* @return
*/
Class<? extends Throwable>[] noRollbackFor() default {};
}
/****5.0版本**
* Created by lorne on 2017/6/26.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TxTransaction {
/**
* 事务模式 transaction type
*
* @return lcn, tcc, txc
* @see Transactions
*/
String type() default Transactions.LCN;
/**
* 分布式事务传播行为
*
* @return 传播行为
* @see DTXPropagation
*/
DTXPropagation propagation() default DTXPropagation.REQUIRED;
}
四、参考资料
1、 分布式事务从0到1-认识分布式事务
2、 codingapi/tx-lcn
3、 springcloud LCN分布式事务v4.0 示例demo