对比LCN和saga(华为apache孵化器项目) ,LCN使用代理连接池封装补偿方法,saga需要手工写补偿方法,相对来说LCN使用更加方便。
参考官方地址:
https://github.com/codingapi/tx-lcn/wiki/TxManager%E5%90%AF%E5%8A%A8%E8%AF%B4%E6%98%8E
1. 原理
1. 事务控制原理
LCN事务控制原理是由事务模块TxClient下的代理连接池与TxManager的协调配合完成的事务协调控制。
TxClient的代理连接池实现了javax.sql.DataSource接口,并重写了close方法,事务模块在提交关闭以后TxClient连接池将执行"假关闭"操作,等待TxManager协调完成事务以后在关闭连接。
2. 调用时序图
1. 正常
2. 异常
2. 服务端
tx-manager 4.1.0
3. 客户端
1. pom添加依赖
<properties> <lcn.last.version>4.1.0</lcn.last.version> </properties> |
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.1.1</version> </dependency> <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> |
2. 配置文件
#Ribbon的负载均衡策略:随机 #ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule #由于springcloud默认是开启的重试机制,开启次机制以后会导致当springcloud请求超时时会重复调用业务模块,从而会引发数据混乱,因此建议将其禁用。对于网络模块超时等故障问题建议使用hytrix方式。 #ribbon.MaxAutoRetriesNextServer=0
tm: manager: url: http://localhost:8899/tx/manager/ ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule MaxAutoRetriesNextServer: 0 init-db:true hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 6000 |
3. Service包下处理http请求和对服务器的连接
package com.svw.tbox.tcloud.commons.ms.service;
import com.codingapi.tx.netty.service.TxManagerHttpRequestService; import com.lorne.core.framework.utils.http.HttpUtils; import org.springframework.stereotype.Service;
@Service publicclass TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{
@Override public String httpGet(String url) { //GET请求前 String res = HttpUtils.get(url); //GET请求后 returnres; }
@Override public String httpPost(String url, String params) { //POST请求前 String res = HttpUtils.post(url,params); //POST请求后 returnres; } } |
package com.svw.tbox.tcloud.commons.ms.service;
import com.codingapi.tx.config.service.TxManagerTxUrlService; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service;
@Service public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService{
@Value("${tm.manager.url}") private String url;
@Override public String getTxUrl() { //load tm.manager.url return url; } } |
4. 启动类配置代理连接池
import javax.sql.DataSource; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.hystrix.EnableHystrix; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.core.env.Environment; import com.alibaba.druid.pool.DruidDataSource;
@SpringBootApplication @EnableDiscoveryClient @EnableHystrix @MapperScan(basePackages = "com.svw.tbox.tcloud.commons.ms.dao") @ComponentScan(basePackages = { "com.svw.tbox.tcloud" }) publicclass MsApplication { …… @Autowired private Environment env;
@Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(env.getProperty("spring.datasource.url")); dataSource.setUsername(env.getProperty("spring.datasource.username"));//用户名 dataSource.setPassword(env.getProperty("spring.datasource.password"));//密码 dataSource.setInitialSize(2); dataSource.setMaxActive(20); dataSource.setMinIdle(0); dataSource.setMaxWait(60000); dataSource.setValidationQuery("SELECT 1"); dataSource.setTestOnBorrow(false); dataSource.setTestWhileIdle(true); dataSource.setPoolPreparedStatements(false); returndataSource; } |
5. 测试代码
调用方tcloud-mds => 参与方tcloud-commons
1. 调用方:tcloud-mds
package com.svw.tbox.tcloud.commons.api.feign;
import org.springframework.cloud.netflix.feign.FeignClient; import com.svw.tbox.tcloud.commons.api.config.TxFeignConfiguration; import com.svw.tbox.tcloud.commons.api.service.SysErrorCodeMappingService;
/** * <p>ClassName: SysErrorCodeMappingFeign</p> * <p>Description: 远程调用错误码服务</p> * <p>Author: hurf</p> * <p>Date: 2017年12月11日</p> */ @FeignClient(value = "tcloud-commons-ms") publicinterface SysErrorCodeMappingFeign extends SysErrorCodeMappingService { } |
2. 事务发起@TxTransaction(isStart=true)
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
import com.codingapi.tx.annotation.TxTransaction; import com.svw.tbox.tcloud.commons.api.entity.SysErrorCodeMapping; import com.svw.tbox.tcloud.commons.api.feign.SysErrorCodeMappingFeign; import com.svw.tbox.tcloud.commons.api.service.CmnService; import com.svw.tbox.tcloud.commons.api.service.JedisTemplate; import com.svw.tbox.tcloud.commons.util.DateUtil; import com.svw.tbox.tcloud.mds.entity.ThUserLogin;
/** * @Title<p>ClassName: UserTokenService</p> * @Description<p>Description: 登录服务</p> * @Author<p>Author: hurf</p> * @Date<p>Date: 2018年2月6日</p> */ @Service publicclass UserTokenService extends CmnService<ThUserLogin>{ @Autowired private JedisTemplate jedisTemplate;
@Autowired private SysErrorCodeMappingFeign sysErrorCodeMappingFeign;
@Transactional @TxTransaction(isStart=true) public String add(SysErrorCodeMapping sysErrorCodeMapping) { // 远程调用新增 sysErrorCodeMappingFeign.add(sysErrorCodeMapping); // 本地新增db insertSelective(ThUserLogin.builder().accessToken(sysErrorCodeMapping.getApiCode()) .refreshToken(sysErrorCodeMapping.getInnerErrorCode()).createBy("测试事务").build()); //本地缓存事务 jedisTemplate.set("isStart", DateUtil.getNow()); // int ii = 1/0;//异常
return"测试分布式事务成功"; } } |
3. 事务参与方tcloud-commons-ms: @Transactional
@RestController publicclass SysErrorCodeMappingController implements SysErrorCodeMappingService {
@Autowired private MsService msService;
@ApiOperation("添加错误码信息") @Override public SystemResponse add(@RequestBody SysErrorCodeMapping sysErrorCodeMapping) { returnmsService.add(sysErrorCodeMapping); } 。。。。。。 |
importcom.codingapi.tx.annotation.ITxTransaction;
@Service @CacheConfig(cacheNames = "sys-code-resource") publicclass MsService implements ITxTransaction{
@Autowired private JedisTemplate jedisTemplate;
@Autowired private SysErrorCodeMappingMapper sysErrorCodeMappingMapper; /** * <p>Title: 事务参与方</p> * <p>Description: </p> * @param sysErrorCodeMapping * @return */ @Transactional public SystemResponse add(SysErrorCodeMapping sysErrorCodeMapping) { //db操作 sysErrorCodeMapping.setVersion(1); sysErrorCodeMapping.setDelFlag(Short.valueOf("0")); sysErrorCodeMapping.setCreatedBy("admin"); sysErrorCodeMapping.setCreateDate(new Date()); sysErrorCodeMappingMapper.insertSelective(sysErrorCodeMapping);
//redis操作 jedisTemplate.set("addTest"+DateUtil.getNow(),"tttttttttttttttttttttt"); return ResultUtil.success(refreshAll()); } |
6. 效果
启动两个微服务,访问调用方接口
1. 正常情况
2. 异常回滚情况
删除刚刚的测试数据,开启异常情况:
@Transactional @TxTransaction(isStart=true) public String add(SysErrorCodeMapping sysErrorCodeMapping) { // 远程调用新增 sysErrorCodeMappingFeign.add(sysErrorCodeMapping); // 本地新增db insertSelective(ThUserLogin.builder().accessToken(sysErrorCodeMapping.getApiCode()) .refreshToken(sysErrorCodeMapping.getInnerErrorCode()).createBy("测试事务").build()); //本地缓存事务 jedisTemplate.set("isStart", DateUtil.getNow()); intii = 1/0;//异常
return"测试分布式事务成功"; } |
发现mysql已经回滚了,但是redis没有回滚 =》 目前只支持db分布式事务。