JeeSite 4.x

Spring Boot 最好的快速开发平台

微服务分布式事务解决方案 TX-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

推荐观看框架原理视频介绍

流行的分布式解决方案的特点

TCC 特点

  • 该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。
  • 该模式对有无本地事务控制都可以支持使用面广。
  • 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高,需要些更多的代码。

TXC 逆向 SQL 特点

  • 该模式对代码的嵌入性低。
  • 该模式仅限于对支持 SQL 方式的模块支持。
  • 该模式由于每次执行 SQL 之前需要先查询影响数据,因此相比 LCN 要消耗资源与时间要多,对性能有影响。
  • 该模式不会占用数据库的连接资源,但中间状态可见(任何情况下都可读取未提交的数据)

LCN 代理连接特点

  • 该模式同样对代码的嵌入性低。
  • 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
  • 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。
  • 该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。

综上比较 LCN 对 SQL 上没要求,切无性能影响,代码嵌入性低,推荐使用

JeeSite 中如何使用?

配置 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

上面以 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

3、启动 TM 服务

找到 /jeesite-cloud-module-txlcn/../TxLcnApplication.java 类,运行 Application 程序。

启动完成后,浏览器访问:http://127.0.0.1:7970 密码:thinkgem 即可进入 TM 管理后台。

配置 TC 客户端

1、关闭 JTA 开启 LCN

打开分布式统一配置文件 /jeesite-cloud-config/../cloud-config/application.yml

jdbc:
  # JTA 分布式事务
  jta:
    enabled: false
  # LCN 分布式事务
  lcn:
    enabled: true

JeeSite 里的 JTA (XA) 事务是有 Atomikos 实现的,为了不对 LCN 事务所影响,所以需要关闭 JTA 事务。

2、关闭 Ribbon 的重试机制

打开分布式统一配置文件 /jeesite-cloud-config/../cloud-config/application.yml

ribbon:
  MaxAutoRetriesNextServer: 0

为什么要关闭服务调用的重试。远程业务调用失败有两种可能: (1),远程业务执行失败 (2)、远程业务执行成功,网络失败。对于第 2 种,事务场景下重试会发生,某个业务执行两次的问题。如果业务上控制某个事务接口的幂等,则不用关闭重试。

3、pom.xml 增加依赖

<!-- 分布式事务 -->
<dependency>
    <groupId>com.jeesite</groupId>
    <artifactId>jeesite-cloud-module-txlcn-client</artifactId>
    <version>${project.parent.version}</version>
</dependency>

添加到每个 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;
}

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?"成功,数据已":"失败,数据未")+"回滚!");
	}

}

jeesite-cloud-module-test1/../TestDataService.java

@LcnTransaction
@Transactional(readOnly=false)
public void save(TestData testData) {
		super.save(testData);
}

jeesite-cloud-module-test2/../TestTreeService.java

@LcnTransaction
@Transactional(readOnly=false)
public void save(TestTree testTree) {
		super.save(testTree);
}

5、启动各个微服务

访问分布式事务,测试地址:http://127.0.0.1:8980/js/a/trans/test

若提示 “事务测试成功” 即正常运行。

关注 JeeSite 公众号,了解最新动态