编辑推荐: |
本文来自于 csdn,将从原理,调用时序图,客户端,启动类配置代理连接池,测试代码和效果这几个方面来阐述分布式事务解决方案LCN。 |
对比LCN和saga(华为apache孵化器项目) ,LCN使用代理连接池封 装补偿方法,saga需要手工写补偿方法,相对来说LCN使用更加方便。
参考官方地址
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
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(),"ttttttttttt ttttttttttt"); 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分布式事务。