SpringCloud基于LCN的分布式事务

LCN是国产开源的分布式事务处理框架。LCN即:lock(锁定事务单元)、confirm(确认事务模块状态)、notify(通知事务)。

LCN的实现是基于3PC的算法,结合TCC的补偿机制。

LCN的核心步骤

核心步骤
   1、创建事务组
         是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。
    2、添加事务组
           添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息添加通知给TxManager的操作。
    3、关闭事务组
        是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager的动作。当执行完关闭事务组的方法以后,            TxManager将根据事务组信息来通知相应的参与模块提交或回滚事务。

LCN正常执行序列图(来源于官方):

è¿éåå¾çæè¿°

LCN异常执行序列图(来源于官方):

è¿éåå¾çæè¿°

使用实例:

一、下载LCN工程

在LCN的github下载:https://github.com/codingapi/tx-lcn/

二、配置的LCN的tx-manager事务协调器(application.properties)

#######################################txmanager-start#################################################
#服务端口
server.port=7000

#tx-manager不得修改
spring.application.name=tx-manager

spring.mvc.static-path-pattern=/**
spring.resources.static-locations=classpath:/static/
#######################################txmanager-end#################################################


#zookeeper地址
#spring.cloud.zookeeper.connect-string=127.0.0.1:2181
#spring.cloud.zookeeper.discovery.preferIpAddress = true

#eureka 地址
eureka.client.service-url.defaultZone=http://user:zj123@localhost:8761/eureka/
eureka.instance.prefer-ip-address=true

#######################################redis-start#################################################
#redis 配置文件,根据情况选择集群或者单机模式

##redis 集群环境配置
##redis cluster
#spring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003
#spring.redis.cluster.commandTimeout=5000

##redis 单点环境配置
#redis
#redis主机地址
spring.redis.host=ip
#redis主机端口
spring.redis.port=6379
#redis链接密码
#spring.redis.password=
spring.redis.pool.maxActive=10
spring.redis.pool.maxWait=-1
spring.redis.pool.maxIdle=5
spring.redis.pool.minIdle=0
spring.redis.timeout=0
#####################################redis-end###################################################

#######################################LCN-start#################################################
#业务模块与TxManager之间通讯的最大等待时间(单位:秒)
#通讯时间是指:发起方与响应方之间完成一次的通讯时间。
#该字段代表的是Tx-Client模块与TxManager模块之间的最大通讯时间,超过该时间未响应本次请求失败。
tm.transaction.netty.delaytime = 5

#业务模块与TxManager之间通讯的心跳时间(单位:秒)
tm.transaction.netty.hearttime = 15

#存储到redis下的数据最大保存时间(单位:秒)
#该字段仅代表的事务模块数据的最大保存时间,补偿数据会永久保存。
tm.redis.savemaxtime=30

#socket server Socket对外服务端口
#TxManager的LCN协议的端口
tm.socket.port=9999

#最大socket连接数
#TxManager最大允许的建立连接数量
tm.socket.maxconnection=100

#事务自动补偿 (true:开启,false:关闭)
# 说明:
# 开启自动补偿以后,必须要配置 tm.compensate.notifyUrl 地址,仅当tm.compensate.notifyUrl 在请求补偿确认时返回success或者SUCCESS时,才会执行自动补偿,否则不会自动补偿。
# 关闭自动补偿,当出现数据时也会 tm.compensate.notifyUrl 地址。
# 当tm.compensate.notifyUrl 无效时,不影响TxManager运行,仅会影响自动补偿。
tm.compensate.auto=false

#事务补偿记录回调地址(rest api 地址,post json格式)
#请求补偿是在开启自动补偿时才会请求的地址。请求分为两种:1.补偿决策,2.补偿结果通知,可通过通过action参数区分compensate为补偿请求、notify为补偿通知。
#*注意当请求补偿决策时,需要补偿服务返回"SUCCESS"字符串以后才可以执行自动补偿。
#请求补偿结果通知则只需要接受通知即可。
#请求补偿的样例数据格式:
#{"groupId":"TtQxTwJP","action":"compensate","json":"{\"address\":\"133.133.5.100:8081\",\"className\":\"com.example.demo.service.impl.DemoServiceImpl\",\"currentTime\":1511356150413,\"data\":\"C5IBLWNvbS5leGFtcGxlLmRlbW8uc2VydmljZS5pbXBsLkRlbW9TZXJ2aWNlSW1wbAwSBHNhdmUbehBqYXZhLmxhbmcuT2JqZWN0GAAQARwjeg9qYXZhLmxhbmcuQ2xhc3MYABABJCo/cHVibGljIGludCBjb20uZXhhbXBsZS5kZW1vLnNlcnZpY2UuaW1wbC5EZW1vU2VydmljZUltcGwuc2F2ZSgp\",\"groupId\":\"TtQxTwJP\",\"methodStr\":\"public int com.example.demo.service.impl.DemoServiceImpl.save()\",\"model\":\"demo1\",\"state\":0,\"time\":36,\"txGroup\":{\"groupId\":\"TtQxTwJP\",\"hasOver\":1,\"isCompensate\":0,\"list\":[{\"address\":\"133.133.5.100:8899\",\"isCompensate\":0,\"isGroup\":0,\"kid\":\"wnlEJoSl\",\"methodStr\":\"public int com.example.demo.service.impl.DemoServiceImpl.save()\",\"model\":\"demo2\",\"modelIpAddress\":\"133.133.5.100:8082\",\"channelAddress\":\"/133.133.5.100:64153\",\"notify\":1,\"uniqueKey\":\"bc13881a5d2ab2ace89ae5d34d608447\"}],\"nowTime\":0,\"startTime\":1511356150379,\"state\":1},\"uniqueKey\":\"be6eea31e382f1f0878d07cef319e4d7\"}"}
#请求补偿的返回数据样例数据格式:
#SUCCESS
#请求补偿结果通知的样例数据格式:
#{"resState":true,"groupId":"TtQxTwJP","action":"notify"}
tm.compensate.notifyUrl=http://ip:port/path

#补偿失败,再次尝试间隔(秒),最大尝试次数3次,当超过3次即为补偿失败,失败的数据依旧还会存在TxManager下。
tm.compensate.tryTime=30

#各事务模块自动补偿的时间上限(毫秒)
#指的是模块执行自动超时的最大时间,该最大时间若过段会导致事务机制异常,该时间必须要模块之间通讯的最大超过时间。
#例如,若模块A与模块B,请求超时的最大时间是5秒,则建议改时间至少大于5秒。
tm.compensate.maxWaitTime=5000
#######################################LCN-end#################################################

logging.level.com.codingapi=debug

这边配置文件简单的使用只需要改注册中心(这边我用的是eureka,配置eureka.client.service-url.defaultZone=)和redis ip(spring.redis.host)和端口(spring.redis.port)

启动LCN的tx-manager,访问:localhost:7000,若出现以下图片说明LCN事务管理器启动成功

SpringCloud基于LCN的分布式事务_第1张图片

三、事务的参与者(这边使用了持久层使用了Mybaits)

当A服务调用了B服务,B服务调用了C服务,则A服务是事务的发起者,B和C都是事务的参与者

1、pom.xml文件


      org.springframework.boot
      spring-boot-starter-parent
      1.5.2.RELEASE
       
  


    UTF-8
    1.8
    1.2.0
    5.1.39
    4.1.0



    
        org.springframework.boot
        spring-boot-starter-web
    
    
		org.springframework.cloud
		spring-cloud-starter-eureka
		1.3.5.RELEASE
	
    
        org.mybatis.spring.boot
        mybatis-spring-boot-starter
        ${mybatis-spring-boot.version}
    
    
        mysql
        mysql-connector-java
    
      
            com.codingapi
            transaction-springcloud
            ${lcn.last.version}
            
                
                    org.slf4j
                    *
                
            
        

        
            com.codingapi
            tx-plugins-db
            ${lcn.last.version}
            
                
                    org.slf4j
                    *
                
            
        
	

2、application.yml

tm:
  manager:
    url: http://localhost:7000/tx/manager/

tm.manager.url是LCN的地址

这边就说明LCN需要加的配置,Mybaits和Eureka注册的配置的我就在这细说了,具有了可以看我之前的播客

https://blog.csdn.net/qq_25011427/article/details/83933519

3、在Spring加入TxManagerTxUrlServiceImpl

import com.codingapi.tx.config.service.TxManagerTxUrlService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * 添加从注册中心获取url;注意通过注解放入容器。
 */
@Service
public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService{
    @Value("${tm.manager.url}")
    private String url;
    @Override
    public String getTxUrl() {
        return url;
    }
}

4、参与者业务类使用@TxTransaction注解进行分布式的事务回滚(我这边直接为了简便就直接在Controller层使用@TxTransaction注解, 就没有遵循MVC架构,实际的项目中建议写到业务层中,遵循MVC架构)

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;

import com.codingapi.tx.annotation.ITxTransaction;
import com.codingapi.tx.annotation.TxTransaction;
import com.zhuojing.bean.User;
import com.zhuojing.dao.UserDao;

@RestController
public class UserController implements ITxTransaction{

	@Autowired
	private UserDao userDao;

	@PostMapping("/saveUser")
	@Transactional
	@TxTransaction
	public String saveUser(@RequestBody User user){
		userDao.save(user);
		return "SUCCESS";
	}
}

5、启动类

@SpringBootApplication
@EnableEurekaClient
@EnableAutoConfiguration
public class ApplocationTest {

	public static void main(String[] args) {
		SpringApplication.run(ApplocationTest.class, args);
	}
}

四、事务发起者

事务发起者这边使用了feign进行调用事务参与者,之前使用用来ribbon来调用发现分布式事务没有起作用,具体原因也没有找到,知道的大牛欢迎帮忙解答下。

1、pom.xml


      org.springframework.boot
      spring-boot-starter-parent
      1.5.2.RELEASE
       
  


    UTF-8
    1.8
    1.2.0
    5.1.39
    4.1.0



    
        org.springframework.boot
        spring-boot-starter-web
    
    
		org.springframework.cloud
		spring-cloud-starter-eureka
		1.3.5.RELEASE
	
	
		org.springframework.cloud
		spring-cloud-starter-feign
		1.3.5.RELEASE
	
	        
            com.codingapi
            transaction-springcloud
            ${lcn.last.version}
            
                
                    org.slf4j
                    *
                
            
        

        
            com.codingapi
            tx-plugins-db
            ${lcn.last.version}
            
                
                    org.slf4j
                    *
                
            
        
        

2、application.yml文件

tm: 
  manager:
    url: http://localhost:7000/tx/manager/
logging:
  level:
    com.codingapi: debug

3、在Spring容器中加入TxManagerTxUrlServiceImpl和TxManagerHttpRequestServiceImpl

import com.codingapi.tx.config.service.TxManagerTxUrlService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

/**
 * 添加从注册中心获取url;注意通过注解放入容器。
 */
@Service
public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService{
    @Value("${tm.manager.url}")
    private String url;
    @Override
    public String getTxUrl() {
        return url;
    }
}
import com.codingapi.tx.netty.service.TxManagerHttpRequestService;
import com.lorne.core.framework.utils.http.HttpUtils;
import org.springframework.stereotype.Service;

/**
 * 常见TxManagerHttpRequestService重写get、post方法;
 */

@Service
public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{

    @Override
    public String httpGet(String url) {
        System.out.println("httpGet-start");
        String res = HttpUtils.get(url);
        System.out.println("httpGet-end");
        return res;
    }

    @Override
    public String httpPost(String url, String params) {
        System.out.println("httpPost-start");
        String res = HttpUtils.post(url,params);
        System.out.println("httpPost-end");
        return res;
    }
}

4、feign接口

@FeignClient(name="provider-user")
public interface UserFeignClient {

	@RequestMapping(value="saveUser", method = RequestMethod.GET)
	public String saveUser(@RequestBody User user);
}

5、事务发起者使用@TxTransaction(isStart = true)注解

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.codingapi.tx.annotation.TxTransaction;
import com.zhuojing.bean.User;
import com.zhuojing.feign.UserFeignClient;

@RestController
public class UserController {

	
	@Autowired
	private UserFeignClient userFeignClient;

	@GetMapping("/txSaveUser")
	@TxTransaction(isStart = true)
	public String txSaveUser(){
		User user = new User();
		user.setUsername("aa");
		user.setAge(11);
		user.setBalance(11);
		userFeignClient.saveUser(user);
		int i = 1/0;
		return "aaa";
	}
}

当事务发起者出现异常时,会同时事务管理器让事务的参与者回滚,若事务参与者出现异常也会进行所有参与者和发起者的事务回滚

5、启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ApplicationCustomerFeignTest {

	public static void main(String[] args) {
		SpringApplication.run(ApplicationCustomerFeignTest.class, args);
	}
}

 

你可能感兴趣的:(分布式,SpringCloud)