微服务分布式事务解决方案 TX-LCN 框架
# LCN 是什么?
LCN 框架在2017年6月份发布第一个版本,从开始的1.0,已经发展到了5.0版本。 LCN 名称是由早期版本的 LCN 框架命名,在设计框架之初的1.0 ~ 2.0的版本时框架设计的步骤是如下,各取其首字母得来的LCN命名。
- 锁定事务单元(lock)
- 确认事务模块状态(confirm)
- 通知事务(notify)
LCN 5.0 以后由于框架兼容了 LCN、TCC、TXC 三种事务模式,为了避免区分 LCN 模式,特此将 LCN 分布式事务改名为 TX-LCN 分布式事务框架。
TX-LCN 定位于一款事务协调性框架,框架其本身并不操作事务,而是基于对事务的协调从而达到事务一致性的效果。LCN 并不生产事务,LCN 只是本地事务的协调工。
TX-LCN 支持跨 Web 服务器,微服务之间的 Feign 远程调用,支持集群、熔断。
TX-LCN 文档地址:http://www.txlcn.org/zh-cn/docs/background.html (opens new window)
# LCN 代理连接特点
- 该模式同样对代码的嵌入性低。
- 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
- 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。
- 该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。
# 推荐观看框架原理视频介绍
- 认识分布式事务:https://www.bilibili.com/video/av80626430 (opens new window)
- 了解 TX-LCN 原理 (一):https://www.bilibili.com/video/av80676649 (opens new window)
- 了解 TX-LCN 原理 (二):https://www.bilibili.com/video/av80676836 (opens new window)
- TX-LCN 原理 PDF 版:LCN分布式事务-JN20191229.pdf
综上比较 LCN 对 SQL 上没要求,切无性能影响,代码嵌入性低。
# JeeSite 中如何使用?
JeeSite v4.3+ 全面替换为 Seata 事务。点击 Seata 查看详细文档。
# 配置 TM 服务端
TX-LCN 主要有两个模块,Tx-Client(TC)、Tx-Manager(TM). TC 作为微服务下的依赖,TM 是独立的服务。
修改配置文件 /jeesite-cloud-module-txlcn/../application.yml
1、设置 TM 数据库连接:
spring:
# 数据源配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/jeesite?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
hibernate:
ddl-auto: update
2
3
4
5
6
7
8
9
10
11
上面以 MySQL 举例,其它数据库,Baidu 如:Spring Data JPA Oracle 配置,即可找到相关解决方案。
2、设置 TM 监听IP和端口:
tx-lcn:
# 分布式事务管理配置
manager:
# TM监听IP. 默认为 127.0.0.1
host: 127.0.0.1
# TM监听Socket端口. 默认为 ${server.port} - 100
port: 8070
2
3
4
5
6
7
3、启动 TM 服务
找到 /jeesite-cloud-module-txlcn/../TxLcnApplication.java
类,运行 Application 程序。
启动完成后,浏览器访问:http://127.0.0.1:7970 (opens new window) 密码:thinkgem 即可进入 TM 管理后台。
# 配置 TC 客户端
1、关闭 JTA 开启 LCN 事务
打开分布式统一配置文件 /jeesite-cloud-config/../jeesite-cloud-43/application.yml
如果使用 Nacos 请在 Nacos 里配置该文件。
# 关闭 JTA XA 事务
jdbc:
jta:
enabled: false
# 开启LCN事务
lcn:
enabled: true
2
3
4
5
6
7
8
JeeSite 里的 JTA (XA) 事务是有 Atomikos 实现的,为了不对 LCN 事务所影响,所以需要关闭 JTA 事务。
2、关闭 Ribbon 的重试机制
打开分布式统一配置文件 /jeesite-cloud-config/../jeesite-cloud-43/application.yml
ribbon:
MaxAutoRetriesNextServer: 0
2
为什么要关闭服务调用的重试。远程业务调用失败有两种可能: (1),远程业务执行失败 (2)、远程业务执行成功,网络失败。对于第 2 种,事务场景下重试会发生,某个业务执行两次的问题。如果业务上控制某个事务接口的幂等,则不用关闭重试。
3、pom.xml 增加依赖
<!-- 分布式事务 -->
<dependency>
<groupId>com.jeesite</groupId>
<artifactId>jeesite-cloud-module-txlcn-client</artifactId>
<version>${project.parent.version}</version>
</dependency>
2
3
4
5
6
添加到每个 Web 微服务的 pom.xml 文件中。
4、编写业务测试代码
由于 LCN 代码嵌入性非常低,不需要您关注太多事务方面的东西。开发模式和您本地开发模式一致。
在需要处理事务的方法上添加 @LcnTransaction 注解即可,本地事务注解保留。
JeeSite Cloud 已为您编写好的测试代码,依次打开如下文件:
jeesite-cloud-module-core/../TransTestService.java
jeesite-cloud-module-test1/../TestDataService.java
jeesite-cloud-module-test2/../TestTreeService.java
将每个文件里的 @LcnTransaction 前的 //
注释去掉,并导入包即可
为了方便理解,下面将关键测试代码进行展示,如下:
jeesite-cloud-module-core/../TransTestService.java
/**
* 事务测试,第二个接口调用故意抛出异常
*/
@LcnTransaction
@Transactional(readOnly=false)
public void transTest(TestData testData) {
// 正常保存 testData 数据
testData.setIsNewRecord(true);
testData.setId(IdGen.randomBase62(5));
testData.setTestInput(testData.getId());
testData.setTestTextarea(testData.getId());
testDataService.save(testData);
// 保存 testTree 失败,抛出异常,testData 应回滚
TestTree testTree = new TestTree();
testTree.setIsNewRecord(true);
testTree.setTreeCode(testData.getId());
testTree.setTreeName(testData.getId());
TestTree where = new TestTree();
where.setParentCode(TestTree.ROOT_CODE);
TestTree last = testTreeService.getLastByParentCode(where);
if (last != null){
testTree.setTreeSort(last.getTreeSort()+30);
}
// 设置一个超出数据库范围的值,抛出数据库异常
StringBuilder sb = new StringBuilder();
for (int i=0; i<500; i++){
sb.append("transTest" + i);
}
testTree.setTreeName(sb.toString());
testTreeService.save(testTree);
// 有些情况可能需要手动回滚事务,调用该方法即可
//DTXUserControls.rollbackCurrentGroup();
}
/**
* 事务验证,返回空,则事务回滚成功
*/
public boolean transValid(TestData testData) {
if (StringUtils.isBlank(testData.getId())){
return true;
}
return testDataService.get(testData.getId()) == null;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
jeesite-cloud-module-core/../TransTestController.java
@Controller
@RequestMapping(value = "${adminPath}/trans")
public class TransTestController extends BaseController {
@Autowired
private TransTestService transTestService;
/**
* 查询列表数据
*/
@RequestMapping(value = "test")
@ResponseBody
public String test(TestData testData) {
try{
transTestService.transTest(testData);
}catch (Exception e) {
logger.debug("事务测试信息,报错回滚:" + e.getMessage(), e);
}
boolean bl = transTestService.transValid(testData);
return renderResult(Global.TRUE, "事务测试"+(bl?"成功,数据已":"失败,数据未")+"回滚!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
jeesite-cloud-module-test1/../TestDataService.java
@LcnTransaction
@Transactional(readOnly=false)
public void save(TestData testData) {
super.save(testData);
}
2
3
4
5
jeesite-cloud-module-test2/../TestTreeService.java
@LcnTransaction
@Transactional(readOnly=false)
public void save(TestTree testTree) {
super.save(testTree);
}
2
3
4
5
5、启动各个微服务
访问分布式事务,测试地址:http://127.0.0.1:8980/js/a/trans/test (opens new window)
若提示 “事务测试成功” 即正常运行。