分布式事务处理
前言
单体事务我们都知道怎么处理,直接一个个注解Transactional搞定,虽然单体事务很好处理,但是一旦到分布式事务上的时候就有点麻烦了。其实就算是在单体服务中一个有事务的方法调其他方法的时候也会出现事务失效不能回滚的问题,当然这不是我们今天讨论的问题。我们在微服务开发中肯定会遇到服务a想要调服务b的情况,在这种情况下如果服务a在调用完服务b后发生的异常如果没有一些事务处理的方案的话服务b是不会回滚的,这样问题就大了。事实上先分布式处理理论上有很多种方式。理论这里就不说了,我们直接代码来看。我们使用的是tcc-lcn分布式事务处理。
准备工作
1. 代码准备。
代码我就不自己写了,在githut上找了一个demo
源码 https://github.com/codingapi/txlcn-demo
2. 数据库准备
创建一个数据库名叫tx-manager
3. 表准备
执行sql脚本。我直接把脚本代码提出来了,可以直接下面代码复制下来粘贴到一个sql文本里。执行就可以个
DROP TABLE IF EXISTS `t_demo`;
CREATE TABLE `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 AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
会生成这样的表,这里t_demo表是我们服务a、b、c维护的表
4.运行redis
准备一个无密码的redis启动,可以在redis.conf文件设置
cmd 启动
修改代码配置改成自己数据库
项目结构
txlcn-demo-tm 模块
主要看一些依赖 配置文件 和启动类
依赖见上图
配置文件别看那么多只需要看看 application.properties
内容如下
##################
# 这个是启动本服务的配置文件,其它的application-xxx.properties 是开发者的个性化配置,不用关心。
# 你可以在 https://txlcn.org/zh-cn/docs/setting/manager.html 看到所有的个性化配置
#################
spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
启动类
package org.txlcn.tm;
import com.codingapi.txlcn.tm.config.EnableTransactionManagerServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableTransactionManagerServer //这个注解看名字就知道启动事务管理服务
public class TransactionManagerApplication {
public static void main(String[] args) {
SpringApplication.run(TransactionManagerApplication.class, args);
}
}
txlcn-demo-tm 模块 就没有其他代码了需要关系了,我们只需要把数据库的连接改成我们的就好了
txlcn-demo-spring-service-a txlcn-demo-spring-service-b txlcn-demo-spring-service-c 模块
都除了业务不同都差多,我们也只看重点 启动类 配置类 service层
启动类
package org.txlcn.demo.servicea;
import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* Description:
* Date: 2018/12/25
*
* @author ujued
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableDistributedTransaction //重点是注解
public class SpringServiceAApplication {
public static void main(String[] args) {
SpringApplication.run(SpringServiceAApplication.class, args);
}
//ribbon 处理 我们不改成Feign
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
txlcn-demo-spring-service-a 模块的service
代码根据参数跑出异常,抛异常数据就会回滚
package org.txlcn.demo.servicea;
import com.codingapi.txlcn.common.util.Transactions;
import com.codingapi.txlcn.tracing.TracingContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.txlcn.demo.common.db.domain.Demo;
import org.txlcn.demo.common.spring.ServiceBClient;
import org.txlcn.demo.common.spring.ServiceCClient;
import java.util.Date;
import java.util.Objects;
/**
* Description:
* Date: 2018/12/25
*
* @author ujued
*/
@Service
@Slf4j
public class DemoServiceImpl implements DemoService {
private final DemoMapper demoMapper;
private final ServiceBClient serviceBClient;
private final ServiceCClient serviceCClient;
private final RestTemplate restTemplate;
@Autowired
public DemoServiceImpl(DemoMapper demoMapper, ServiceBClient serviceBClient, ServiceCClient serviceCClient, RestTemplate restTemplate) {
this.demoMapper = demoMapper;
this.serviceBClient = serviceBClient;
this.serviceCClient = serviceCClient;
this.restTemplate = restTemplate;
}
@Override
public String execute(String value, String exFlag) {
// step1. call remote ServiceD
// String dResp = serviceBClient.rpc(value);
String dResp = restTemplate.getForObject("http://127.0.0.1:12002/rpc?value=" + value, String.class);
// step2. call remote ServiceE
String eResp = serviceCClient.rpc(value);
// step3. execute local transaction
Demo demo = new Demo();
demo.setGroupId(TracingContext.tracing().groupId());
demo.setDemoField(value);
demo.setCreateTime(new Date());
demo.setAppName(Transactions.getApplicationId());
demoMapper.save(demo);
//设置异常
//int i = 1/0;
// 置异常标志,DTX 回滚
if (Objects.nonNull(exFlag)) {
throw new IllegalStateException("by exFlag");
}
return dResp + " > " + eResp + " > " + "ok-service-a";
}
}
txlcn-demo-spring-service-b 模块的service txlcn-demo-spring-service-b 类似就不看了
package org.txlcn.demo.serviceb;
import com.codingapi.txlcn.common.util.Transactions;
import com.codingapi.txlcn.tc.annotation.DTXPropagation;
import com.codingapi.txlcn.tc.annotation.TxcTransaction;
import com.codingapi.txlcn.tracing.TracingContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.txlcn.demo.common.db.domain.Demo;
import java.util.Date;
/**
* Description:
* Date: 2018/12/25
*
* @author ujued
*/
@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";
}
}
调用是a调b、c
测试
依次启动服务
t_demo表
然后我们把表的数据清空测试异常情况
数据库
说明数据已经回滚。完美