spring boot 分布式事务解决方案

对比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. 事务控制原理![在这里插入图片描述](https://img-blog.csdnimg.cn/20201127180818914.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80ODMyMTk5Mw==,size_16,color_FFFFFF,t_70#pic_center)
    

LCN事务控制原理是由事务模块TxClient下的代理连接池与TxManager的协调配合完成的事务协调控制。

TxClient的代理连接池实现了javax.sql.DataSource接口,并重写了close方法,事务模块在提交关闭以后TxClient连接池将执行"假关闭"操作,等待TxManager协调完成事务以后在关闭连接。
二. 调用时序图

  1. 正常 
    

spring boot 分布式事务解决方案_第1张图片2. 异常
spring boot 分布式事务解决方案_第2张图片二. 服务端

tx-manager 4.1.0
三. 客户端

  1. pom添加依赖
    
  2. `

     4.1.0
    
     
    
         org.mybatis.spring.boot
    
         mybatis-spring-boot-starter
    
         1.1.1
    
     
    
     
    
         com.codingapi
    
         transaction-springcloud
    
         ${lcn.last.version}
    
         
    
             
    
                org.slf4j
    
                *
    
             
    
         
    
     
    
     
    
         com.codingapi
    
         tx-plugins-db
    
         ${lcn.last.version}
    
         
    
             
    
                org.slf4j
    
                *
    
             
    
         
    
     `
    
  3. 配置文件

  4. `#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`
  1. 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;

    }

}
  1. 启动类配置代理连接池
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;

 

/**

 * 

ClassName: SysErrorCodeMappingFeign

*

Description: 远程调用错误码服务

*

Author: hurf

*

Date: 2017年12月11日

*/
@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

ClassName: UserTokenService

* @Description

Description: 登录服务

* @Author

Author: hurf

* @Date

Date: 2018年2月6日

*/
@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;

    /**

     * 

Title: 事务参与方

*

Description:

* @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()); }
  1. 效果
    

启动两个微服务,访问调用方接口
1. 正常情况

spring boot 分布式事务解决方案_第3张图片spring boot 分布式事务解决方案_第4张图片

spring boot 分布式事务解决方案_第5张图片
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"测试分布式事务成功";

    }

spring boot 分布式事务解决方案_第6张图片
spring boot 分布式事务解决方案_第7张图片
spring boot 分布式事务解决方案_第8张图片spring boot 分布式事务解决方案_第9张图片

发现mysql已经回滚了,但是redis没有回滚, 目前只支持db分布式事务。

你可能感兴趣的:(#,限流/分布式应用技术,分布式,mysql,java)