分布式事务-tx-lcn。
1.介绍
LCN并不生产事务,LCN只是本地事务的协调工,LCN是一个高性能的分布式事务框架,兼容dubbo、springcloud框架,支持RPC框架拓展,支持各种ORM框架、NoSQL、负载均衡、事务补偿
a、强一致性,通过TxManager协调控制与事务补偿机制确保数据一致性(主要特点,强一致性,比消息事务强的方面)。
b、易用性,仅需要在业务方法上添加相应注解即可,有个简易的可视化界面。
c、高可用,项目模块不仅可高可用部署,事务协调器也可集群化部署。
2.流程图
3.关键组件:
eureka-server:服务注册与发现。
txlcn-tm:txlcn的事务管理器,可直接用的jar包(https://download.csdn.net/download/leadseczgw01/12543652,需修改配置文件地址和端口)。
service-a:springcloud 2.1.0,服务a,操作数据库及调用服务b1。
service-b1:springcloud 2.1.0,服务b1,操作数据库。
redis
4.github代码:https://github.com/kickTec/springCloudDemo/tree/tx-lcn
5.service-a/service-b1关键依赖
<!-- txlcn分布式事务管理 -->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!--lombok插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
6.service-a关键代码
@LcnTransaction//分布式事务
@Override
public String txlcn(String exFlag) {
//先调用本地服务,新增一个用户user PS:调用EntityManager的merge,传进去的实体字段是什么就保存什么
TbUser tbUser = new TbUser();
tbUser.setUsername("tx-lcn-"+exFlag);
tbUser.setPassword("123456");
TbUser user = entityManager.merge(tbUser);
System.out.println(user);
//调用B服务,新增一个用户描述description
TbDescription description1 = bFeign.txlcn(user.getId());
System.out.println(description1);
if("catchNull".equals(exFlag) && description1 == null){
throw new RuntimeException("服务2异常!");
}
if("numExcepiton".equals(exFlag)){
int num = 1/0;
}
//根据标识,是否抛出异常
if (!StringUtils.isEmpty(exFlag)) {
return "操作成功,请查看一下数据库验证!";
} else {
throw new RuntimeException("rollback transactional by exFlag");
}
}
service-b1关键代码
@LcnTransaction//分布式事务
@Override
public TbDescription txlcn(Integer userId){
TbDescription tbDescription = null;
try{
tbDescription = entityManager.find(TbDescription.class, userId);
if(tbDescription == null){
tbDescription = new TbDescription();
tbDescription.setId(userId);
tbDescription.setUserId(userId);
tbDescription.setDescription("服务B设置的描述");
}else{
tbDescription.setDescription(tbDescription.getDescription()+",服务B设置的描述");
}
tbDescription = entityManager.merge(tbDescription);
int num = 1/0;
}catch (Exception e){
tbDescription = null;
System.out.println(e.getMessage());
}
return tbDescription;
}
7.执行流程:
7.1 数据库文件使用https://github.com/kickTec/springCloudDemo/tree/tx-lcn中的test.sql。
7.2 依次启动eureka 、txlcn-tm、service-a、service-b1;
7.3 正常业务:调用a服务的txlcn接口,会在本地持久化一条tbuser数据(username为tx-lcn-参数);再调用b1服务的txlcn接口,新增一条tbdescription数据(userId为前面a服务持久化的userId)。
调用A服务接口:http://192.168.0.18:10081/txlcn?exFlag=kenick01
A服务打印的日志:
b1服务日志:
7.4 异常情况
a服务本地持久化成功,并且调用b服务成功,后续发送异常;调用a接口:http://192.168.0.18:10081/txlcn?exFlag=numExcepiton。
a服务日志:
b服务日志:
另一种事务异常情况:
a服务持久化数据成功,b服务持久化数据失败。在b服务的业务中增加产生异常的代码,注意:如果b服务产生的异常在controller层被捕获了(很有可能),此时A服务获取到B服务的结果,但是没捕获到异常,会造成A服务不会回滚(若B服务controller层不捕获异常,则可以回滚,这个应该机制问题,感觉后续可以优化,影响不大)。
int num = 1/0; // 抛出异常
a服务日志:
b服务日志:
评价:通过中间件tx-lcn-tm管理事务,使用代理本地数据库连接的方式,控制不同服务的事务提交和回滚,能够实现教好的强一致性;不足之处,不同服务之间会增加额外的通信开销,同时由于多个服务必须都处理完毕,事务才会提交会回滚,会锁定资源比较长时间,这点需要注意。
本文地址:https://blog.csdn.net/leadseczgw01/article/details/106918201