TX-LCN分布式事务框架
背景:
LCN框架在2017年6月份发布第一个版本,从开始的1.0,已经发展到了5.0版本。
LCN名称是由早期版本的LCN框架命名,在设计框架之初的1.0 ~ 2.0的版本时框架设计的步骤是如下,各取其首字母得来的LCN命名。
锁定事务单元(lock)
确认事务模块状态(confirm)
通知事务(notify)
5.0以后由于框架兼容了LCN、TCC、TXC三种事务模式,为了避免区分LCN模式,特此将LCN分布式事务改名为TX-LCN分布式事务框架。
框架定位:
LCN并不生产事务,LCN只是本地事务的协调工
TX-LCN定位于一款事务协调性框架,框架其本身并不操作事务,而是基于对事务的协调从而达到事务一致性的效果。
在一个分布式系统下存在多个模块协调来完成一次业务。那么就存在一次业务事务下可能横跨多种数据源节点的可能。TX-LCN将可以解决这样的问题。
例如存在服务模块A 、B、 C。A模块是mysql作为数据源的服务,B模块是基于redis作为数据源的服务,C模块是基于mongo作为数据源的服务。若需要解决他们的事务一致性就需要针对不同的节点采用不同的方案,并且统一协调完成分布式事务的处理。
方案:
若采用TX-LCN分布式事务框架,则可以将A模块采用LCN模式、B/C采用TCC模式就能完美解决。
原理:
TX-LCN由两大模块组成, TxClient、TxManager,TxClient作为模块的依赖框架,提供TX-LCN的标准支持,TxManager作为分布式事务的控制放。事务发起方或者参与反都由TxClient端来控制。
原理图如下:
核心步骤
创建事务组
是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。
加入事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。
通知事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。
一、pom文件引入依赖
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>transaction-springcloud</artifactId>
<version>${lcn.last.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>tx-plugins-db</artifactId>
<version>${lcn.last.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<properties>
<!-- LCN分布式事务版本 -->
<lcn.last.version>4.1.0</lcn.last.version>
</properties>
二、配置文件application.yaml
##################
# 这个是启动本服务的配置文件,其它的application-xxx.properties 是开发者的个性化配置,不用关心。
# 你可以在 https://txlcn.org/zh-cn/docs/setting/client.html 看到所有的个性化配置
#################
spring.application.name=txlcn-demo-spring-service-a
server.port=12011
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
## TODO 你的配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/txlcn-demo?\
characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.hikari.maximum-pool-size=20
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
logging.level.com.codingapi.txlcn=DEBUG
# 关闭Ribbon的重试机制(如果有必要)
ribbon.MaxAutoRetriesNextServer=0
ribbon.ReadTimeout=5000
ribbon.ConnectTimeout=5000
三、调用关系说明:
1、SpringServiceA -> DemoController的txlcn的Mapping是调用发起方法,代码如下。
@RestController
public class DemoController {
private final DemoService demoService;
@Autowired
public DemoController(DemoService demoService) {
this.demoService = demoService;
}
@RequestMapping("/txlcn")
public String execute(
@RequestParam("value") String value,
@RequestParam(value = "ex", required = false) String exFlag) {
return demoService.execute(value, exFlag);
}
}
2、demoService.execute(value, exFlag)代码:
@Service
@Slf4j
public class DemoServiceImpl implements DemoService {
private final DemoMapper demoMapper;
private final ServiceBClient serviceBClient;
private final ServiceCClient serviceCClient;
@Autowired
public DemoServiceImpl(
ClientDemoMapper demoMapper,
ServiceBClient serviceBClient,
ServiceCClient serviceCClient) {
this.demoMapper = demoMapper;
this.serviceBClient = serviceBClient;
this.serviceCClient = serviceCClient;
}
@Override
@LcnTransaction
public String execute(String value) {
// ServiceB
String dResp = serviceBClient.rpc(value);
// ServiceC
String eResp = serviceCClient.rpc(value);
// Local transaction
Demo demo = new Demo();
demo.setGroupId(DTXLocalContext.getOrNew().getGroupId());
demo.setDemoField(value);
demo.setAppName(Transactions.APPLICATION_ID_WHEN_RUNNING);
demo.setCreateTime(new Date());
demoMapper.save(demo);
// 置异常标志,DTX 回滚
if (Objects.nonNull(exFlag)) {
throw new IllegalStateException("by exFlag");
}
return dResp + " > " + eResp + " > " + "ok-service-a";
}
}
3、ServiceBClient.rpc(value)代码:
@Service
@Slf4j
public class DemoServiceImpl implements DemoService {
private final DemoMapper demoMapper;
@Autowired
public DemoServiceImpl(DemoMapper demoMapper) {
this.demoMapper = demoMapper;
}
@Override
@TxcTransaction(propagation = DTXPropagation.SUPPORTS)
@Transactional
public String rpc(String value) {
Demo demo = new Demo();
demo.setGroupId(TracingContext.tracing().groupId());
demo.setDemoField(value);
demo.setAppName(Transactions.getApplicationId());
demo.setCreateTime(new Date());
demoMapper.save(demo);
return "ok-service-b";
}
}
4、ServiceCClient.rpc(value)代码:
@Service
@Slf4j
public class DemoServiceImpl implements DemoService {
private final DemoMapper demoMapper;
private ConcurrentHashMap<String, Long> ids = new ConcurrentHashMap<>();
@Autowired
public DemoServiceImpl(DemoMapper demoMapper) {
this.demoMapper = demoMapper;
}
@Override
@TccTransaction(propagation = DTXPropagation.SUPPORTS)
@Transactional
public String rpc(String value) {
Demo demo = new Demo();
demo.setDemoField(value);
demo.setCreateTime(new Date());
demo.setAppName(Transactions.getApplicationId());
demo.setGroupId(TracingContext.tracing().groupId());
demoMapper.save(demo);
ids.put(TracingContext.tracing().groupId(), demo.getId());
return "ok-service-c";
}
public void confirmRpc(String value) {
log.info("tcc-confirm-" + TracingContext.tracing().groupId());
ids.remove(TracingContext.tracing().groupId());
}
public void cancelRpc(String value) {
log.info("tcc-cancel-" + TracingContext.tracing().groupId());
Long kid = ids.get(TracingContext.tracing().groupId());
demoMapper.deleteByKId(kid);
}
}
5、启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableDistributedTransaction
public class SpringServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(SpringServiceAApplication.class, args);
}
}
事务参与方,txlcn-demo-spring-service-b
项目配置文件
##################
# 这个是启动本服务的配置文件,其它的application-xxx.properties 是开发者的个性化配置,不用关心。
# 你可以在 https://txlcn.org/zh-cn/docs/setting/client.html 看到所有的个性化配置
#################
spring.application.name=txlcn-demo-spring-service-b
server.port=12002
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
## TODO 你的配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/txlcn-demo\
?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
#spring.datasource.hikari.maximum-pool-size=20
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
logging.level.com.codingapi.txlcn=DEBUG
启动类:
@SpringBootApplication
@EnableDiscoveryClient
@EnableDistributedTransaction
public class SpringServiceBApplication {
public static void main(String[] args) {
SpringApplication.run(SpringServiceBApplication.class, args);
}
}
事务参与方,txlcn-demo-spring-service-c
配置文件:
##################
# 这个是启动本服务的配置文件,其它的application-xxx.properties 是开发者的个性化配置,不用关心。
# 你可以在 https://txlcn.org/zh-cn/docs/setting/client.html 看到所有的个性化配置
#################
spring.application.name=txlcn-demo-spring-service-c
server.port=12003
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
## TODO 你的配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/txlcn-demo\
?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.hikari.maximum-pool-size=20
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
logging.level.com.codingapi.txlcn=DEBUG
四、启动SpringCloud微服务
事务参与方 ServiceB
事务参与方 ServiceC
事务发起方 ServiceA
更多参考官方文档:
http://www.txlcn.org/zh-cn/index.html