框架定位
LCN并不生产事务,LCN只是本地事务的协调工
TX-LCN定位于一款事务协调性框架,框架其本身并不操作事务,而是基于对事务的协调从而达到事务一致性的效果。
解决方案
在一个分布式系统下存在多个模块协调来完成一次业务。那么就存在一次业务事务下可能横跨多种数据源节点的可能。TX-LCN将可以解决这样的问题。
例如存在服务模块A 、B、 C。A模块是mysql作为数据源的服务,B模块是基于redis作为数据源的服务,C模块是基于mongo作为数据源的服务。若需要解决他们的事务一致性就需要针对不同的节点采用不同的方案,并且统一协调完成分布式事务的处理。
准备工作
jdk1.8、mysql、redis、idea
1、数据库脚本
CREATE DATABASE `txlcn-demo` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `txlcn-demo`;
CREATE TABLE IF NOT EXISTS `t_demo`(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`kid` varchar(45) DEFAULT NULL,
`demo_field` varchar(255) DEFAULT NULL,
`group_id` varchar(64) DEFAULT NULL,
`unit_id` varchar(32) DEFAULT NULL,
`app_name` varchar(128) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
2、pom依赖
父依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>tx-lcn-demo</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<modules>
<module>demo-service-a</module>
<module>demo-service-b</module>
<module>demo-service-c</module>
<module>demo-service-common</module>
</modules>
<artifactId>txlcn-client-demo</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
common依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>txlcn-client-demo</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>demo-service-common</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<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>
</dependencies>
</project>
service依赖
serviceA,serviceB,serviceC的三个依赖是一样的,只有artifactId改变了
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>txlcn-client-demo</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>demo-service-c</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>demo-service-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
</dependencies>
</project>
3、common代码
config
package com.demo.txlcn.common.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* 描述:
*
* @author 含光
* @email jarvan_best@163.com
* @date 2021/2/24 7:43 下午
* @company 数海掌讯
*/
@Configuration
public class RestTemplateConfiguration {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
entity
package com.demo.txlcn.common.entity;
import com.codingapi.txlcn.common.util.id.RandomUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 描述:
*
* @author 含光
* @email jarvan_best@163.com
* @date 2021/2/24 3:13 下午
* @company 数海掌讯
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Demo {
private Long id;
private String kid = RandomUtils.randomKey();
private String demoField;
private String groupId;
private Date createTime;
private String appName;
}
mapper
package com.demo.txlcn.common.mapper;
import com.demo.txlcn.common.entity.Demo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
/**
* 描述:
*
* @author 含光
* @email jarvan_best@163.com
* @date 2021/2/24 3:13 下午
* @company 数海掌讯
*/
@Mapper
public interface BaseDemoMapper {
@Insert("insert into t_demo(kid, demo_field, group_id, create_time,app_name) values(#{kid}, #{demoField}, #{groupId}, #{createTime},#{appName})")
void save(Demo demo);
}
4、三个服务的applicationl.yml
复制一份更改下面三个参数即可
spring.application.name=demo-service-a
server.port12011
txlcn-demo库账号密码
日志库账号密码
server:
port: 12011
spring:
application:
name: demo-service-a ##服务名称
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://【你的数据库地址】:3306/txlcn-demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
username: 【你的数据库名称】
password: 【你的数据库密码】
tx-lcn:
ribbon:
loadbalancer:
dtx:
# 是否启动LCN负载均衡策略(优化选项,开启与否,功能不受影响)
enabled: true
logger:
# 开启日志,默认为false
enabled: true
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://【你的数据库地址】:3306/tx_logger?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull
username: 【你的数据库名称】
password: 【你的数据库密码】
client:
# 默认之配置为TM的本机默认端口
manager-address: 127.0.0.1:8070
ribbon:
ReadTimeout: 60000
ConnectTimeout: 60000
5、service代码
controller
package com.demo.txlcn.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 描述:
*
* @author 含光
* @email jarvan_best@163.com
* @date 2021/2/24 3:26 下午
* @company 数海掌讯
*/
@RestController
public class DemoController {
private final DemoService demoService;
@Autowired
public DemoController(DemoService demoService) {
this.demoService = demoService;
}
/**
* 正常情况返回 ok-service-b > ok-service-c > ok-service-a
* http://localhost:12011/txlcn?value=4561
* @param value 保存值
* @param exFlag 是否抛出异常
* @param flag
* @return
*/
@RequestMapping("/txlcn")
public String execute(@RequestParam("value") String value, @RequestParam(value = "ex", required = false) String exFlag
, @RequestParam(value = "f", required = false) String flag) {
return demoService.execute(value, exFlag, flag);
}
}
mapper
@Mapper
public interface DemoMapper extends BaseDemoMapper {
}
demoService
public interface DemoService {
String execute(String value, String ex, String exValue);
}
serviceImpl
package com.demo.txlcn.demo;
import com.codingapi.txlcn.common.util.Transactions;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.codingapi.txlcn.tracing.TracingContext;
import com.demo.txlcn.common.entity.Demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Objects;
/**
* 描述:
*
* @author 含光
* @email jarvan_best@163.com
* @date 2021/2/24 3:29 下午
* @company 数海掌讯
*/
@Service
public class DemoServiceImpl implements DemoService {
@Resource
private DemoMapper demoMapper;
@Autowired
private RestTemplate template;
@LcnTransaction
@Transactional(rollbackFor = Exception.class)
@Override
public String execute(String value, String exFlag, String flag) {
String dResp = template.getForObject("http://localhost:12012/txlcn?value=" + value, String.class);
String eResp = template.getForObject("http://localhost:12013/txlcn?value=" + value, String.class);
Demo demo = new Demo();
demo.setGroupId(TracingContext.tracing().groupId());
demo.setDemoField(value);
demo.setCreateTime(new Date());
demo.setAppName(Transactions.getApplicationId());
demoMapper.save(demo);
if (Objects.nonNull(exFlag)) {
throw new IllegalStateException("exception by exFlag");
}
return dResp + " > " + eResp + " > " + "ok-service-a";
}
}
工程结构
6、启动类增加EnableDistributedTransaction注解并启动tm和三个测试服务
启动完成到tm后台查看,正常结果如下
7、测试
正常情况,事务正常提交
正常访问 http://localhost:12011/txlcn?value=测试分布式事务-lcn
事务全部提交,txlcn-demo表中增加三条数据
数据库txlcn-demo中增加如下三条数据