1、LCN源碼下載
下載地址:https://github.com/codingapi/tx-lcn/releases
2、LCN轉微服務
將下載源碼中的該項目以微服務的形式集成入駐到你的微服務項目中
(1)替換配置文件
原配置文件
application.properties
#######################################txmanager-start#################################################
#服務端口
server.port=8899
#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://127.0.0.1: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=127.0.0.1
#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
新配置文件
大家參考我的進行修改,注意redis配置因爲我這邊的微服務公共配置文件裏有,所以這裏就去掉了,大家要根據自己項目的實際情況去調整。
bootstrap.yml
spring:
application:
name: tx-manager
profiles:
active: dev
cloud:
config:
fail-fast: true
discovery:
service-id: ylapp-config-server
enabled: true
profile: ${spring.profiles.active}
label: ${spring.profiles.active}
#######################################LCN-start#################################################
#業務模塊與TxManager之間通訊的最大等待時間(單位:秒)
#通訊時間是指:發起方與響應方之間完成一次的通訊時間。
#該字段代表的是Tx-Client模塊與TxManager模塊之間的最大通訊時間,超過該時間未響應本次請求失敗。
tm:
transaction:
netty:
delaytime: 5
hearttime: 15 #業務模塊與TxManager之間通訊的心跳時間(單位:秒)
redis:
savemaxtime: 30 #存儲到redis下的數據最大保存時間(單位:秒) ,,,#該字段僅代表的事務模塊數據的最大保存時間,補償數據會永久保存。
socket:
port: 9998 #socket server Socket對外服務端口,,,#TxManager的LCN協議的端口
maxconnection: 100 #最大socket連接數,,,#TxManager最大允許的建立連接數量
compensate:
auto: false #事務自動補償 (true:開啓,false:關閉)
notifyUrl: http://ip:port/path #事務補償記錄回調地址(rest api 地址,post json格式)
tryTime: 30 #再次嘗試間隔(秒),最大嘗試次數3次,當超過3次即爲補償失敗,失敗的數據依舊還會存在TxManager下。
maxWaitTime: 5000 #各事務模塊自動補償的時間上限(毫秒)
#######################################LCN-end#################################################
---
spring:
profiles: dev
eureka:
instance:
prefer-ip-address: true
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 20
client:
serviceUrl:
defaultZone: http://ylapp:[email protected]:1025/eureka,http://ylapp:[email protected]:1025/eureka
registry-fetch-interval-seconds: 10
log:
path: ./logs
---
spring:
profiles: test
eureka:
instance:
prefer-ip-address: true
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 20
client:
serviceUrl:
defaultZone: http://ylapp:[email protected]:1025/eureka,http://ylapp:[email protected]:1025/eureka
registry-fetch-interval-seconds: 10
log:
path: /var/www/html/logs
(2)替換pom
同樣我這邊給出我的示例,大家參考,其實就是把你項目中其他微服務的pom拷貝過來做適當調整
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.***</groupId>
<artifactId>***-txmanager-service</artifactId>
<version>1.3.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>***-txmanager-service</name>
<description>***-txmanager-service</description>
<parent>
<groupId>com.github.***</groupId>
<artifactId>***-public</artifactId>
<version>1.3.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>com.github.***</groupId>
<artifactId>***-common</artifactId>
<version>1.3.0-SNAPSHOT</version>
</dependency>
<!--MySQL-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--七牛-->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu.version}</version>
</dependency>
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
<!--zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>com.github.1991wangliang</groupId>
<artifactId>lorne_core</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.12.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<finalName>${project.name}</finalName>
</configuration>
</plugin>
</plugins>
</build>
</project>
(3)修改tx-client-4.1.0.jar
這裏我直接貼上我修改後的,目的是爲了支持多環境配置,怎樣修改jar包裏的class文件我就不詳細教了,大家自己百度,有很多種方法。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.codingapi.tx.config;
import com.codingapi.tx.config.service.TxManagerTxUrlService;
import com.lorne.core.framework.utils.config.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component
public class ConfigReader {
private Logger logger = LoggerFactory.getLogger(ConfigReader.class);
private TxManagerTxUrlService txManagerTxUrlService;
@Autowired
private ApplicationContext spring;
public ConfigReader() {
}
public String getTxUrl() {
try {
this.txManagerTxUrlService = (TxManagerTxUrlService)this.spring.getBean(TxManagerTxUrlService.class);
} catch (Exception var4) {
this.logger.debug("load default txManagerTxUrlService ");
}
String activeProfile = this.spring.getEnvironment().getActiveProfiles()[0];
final String fileName = "tx-dev.properties";
if ("test".equals(activeProfile)) {
fileName = "tx-test.properties";
} else if ("ltest".equals(activeProfile)) {
fileName = "tx-ltest.properties";
} else if ("pre".equals(activeProfile)) {
fileName = "tx-pre.properties";
} else if ("prd".equals(activeProfile)) {
fileName = "tx-prd.properties";
}
if (this.txManagerTxUrlService == null) {
this.txManagerTxUrlService = new TxManagerTxUrlService() {
private final String configName = fileName;
private final String configKey = "url";
public String getTxUrl() {
return ConfigUtils.getString(this.configName, "url");
}
};
this.logger.debug("load default txManagerTxUrlService");
} else {
this.logger.debug("load txManagerTxUrlService");
}
return this.txManagerTxUrlService.getTxUrl();
}
}
(4)新增配置文件tx-dev.properties
因爲凡是需要使用分佈式事物的服務都需要該配置,所以我這邊選擇放在common服務下
url=http://192.168.50.250:7000/tx/manager/
(5)添加maven依賴
最外層父級pom.xml 添加
<properties>
<!-- LCN分佈式事務版本 -->
<lcn.last.version>4.1.0</lcn.last.version>
</properties>
需要使用LCN的服務加
<dependencies>
<!-- lcn 依賴 start -->
<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>
<!-- lcn 依賴 end -->
</dependencies>
3、使用示例
這邊都只貼出service層代碼
事物發起方, A服務
/**
* @Author chenqi
* @Description 測試 lcn
* @Date 11:45 2019/8/7
* @Param []
* @return java.lang.Boolean
**/
@TxTransaction(isStart = true)
@Transactional(rollbackFor = Exception.class)
@Override
public Boolean txManagerTest(){
//A服務插入數據
String time = DateUtils.findCurrentDateTime();
PartnerUser partnerUser = new PartnerUser();
partnerUser.setUserName("12111111111");
partnerUser.setPassword("12111111111");
partnerUser.setState(1);
partnerUser.setCreateTime(time);
partnerUser.setUpdateTime(time);
partnerUser.setFlagDel(CommonConstant.STATUS_NORMAL);
insert(partnerUser);
//調用B服務保存數據,使用springCloud-feign
appUserService.addTest();
//拋異常
throw new PartnerServiceException("測試回滾");
}
事物參與方, B服務
/**
* @Author chenqi
* @Description 新增用戶測試 lcn
* @Date 11:46 2019/8/7
* @Param []
* @return void
**/
@TxTransaction
@Transactional(rollbackFor = Exception.class)
@Override
public void addTest(){
AppUser appUser = new AppUser();
appUser.setUserName("12111111111");
appUser.setPhone("12111111111");
appUser.setPassword("12111111111");
appUser.setName("test");
appUser.setCreateTime(new Date());
insert(appUser);
}
原理說明:
在上述示例中,A服務首先自身插入數據,然後調用B服務插入數據,最後在A服務拋出異常,
如果沒有集成lcn分佈式事物協調服務,雖然A服務的數據插入操作會回滾,但是B服務的數據是會正常插入到數據庫的,從而造成了事物不一致的嚴重事故,但是集成了lcn之後,A服務在執行方法時會創建一個事務組,看如下日誌:
08-07 13:56:29.773 INFO [com.codingapi.tx.framework.utils.SocketManager] - send-msg->
{"a":"cg","k":"GRDnKxRm","p":{"g":"GuzW5DVW"}}
08-07 13:56:29.842 INFO [c.c.tx.datasource.relational.LCNStartConnection] -
transaction is start-connection.
08-07 13:56:29.847 INFO [c.c.tx.datasource.relational.LCNStartConnection] -
lcn start connection init ok .
08-07 13:56:30.759 INFO [c.c.t.s.feign.TransactionRestTemplateInterceptor] -
LCN-SpringCloud TxGroup info -> groupId:GuzW5DVW
可以看到,會生成一個事務組id GuzW5DVW,當A服務調用B服務時,我們再看B服務的日誌輸出:
maxOutTime : 5000
transaction is wait for TxManager notify, groupId : GuzW5DVW
08-07 13:56:32.113 INFO [com.codingapi.tx.framework.utils.SocketManager] - send-msg->
{"a":"atg","k":"XI9Yk40D","p":{"s":0,"t":"SZc1UpSQ","ms":"public void
com.****.****.appuser.service.impl.AppUserServiceImpl.addTest()","g":"GuzW5DVW"}}
可以看到,B服務執行完了操作,但是事物狀態是等待通知的狀態,最後A服務拋出異常:
08-07 13:56:31.826 INFO [com.codingapi.tx.framework.utils.SocketManager] - send-msg->
{"a":"glb","k":"WU2p0cXS","p":{"g":"GuzW5DVW","k":"4aebbb3be0863f0b203fd82ec7076cab"}}
08-07 13:56:31.839 INFO [com.codingapi.tx.framework.utils.SocketManager] - send-msg->
{"a":"plb","k":"Pgd1A5ot","p":{"d":"a0e1839774470462c6294d2ef14ff916","g":"GuzW5DVW"
,"k":"4aebbb3be0863f0b203fd82ec7076cab"}}
08-07 13:56:32.161 INFO [c.c.tx.datasource.relational.LCNStartConnection] -
rollback label
08-07 13:56:32.178 INFO [com.codingapi.tx.framework.utils.SocketManager] - send-msg->
{"a":"ctg","k":"dggB9W2l","p":{"s":0,"g":"GuzW5DVW"}}
[com.****.****.common.util.exception.PartnerServiceException: 測試回滾]
08-07 13:56:32.433 INFO [c.p.y.common.bean.handler.GlobalExceptionHandler] -
保存***服務異常信息 ex=測試回滾
com.****.****.common.util.exception.PartnerServiceException: 測試回滾
at com.****.****.partner.service.impl.PartnerUserServiceImpl.txManagerTest(
PartnerUserServiceImpl.java:1828)
at com.****.****.partner.service.impl.PartnerUserServiceImpl$$FastClassBySpringCGLIB
$$388e1d9a.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
A服務拋出該異常後,A服務自身的數據庫插入修改操作都會回滾了,那麼B服務呢?看B服務如下日誌:
08-07 13:56:32.398 INFO [c.c.tx.control.service.impl.ActionTServiceImpl] -
accept notify data ->{"a":"t","c":0,"t":"SZc1UpSQ","k":"KJDBQozR"}
lcn transaction over, res -> groupId:GuzW5DVW and state is rollback
08-07 13:56:32.403 INFO [c.c.tx.control.service.impl.ActionTServiceImpl] -
accept notify response res ->1
可以看到,B服務收到了來自lcn的通知,進行了事物回滾rollback
至此,實現了不同服務相互調用的事物一致性,從而解決了分佈式事物的問題。
升級到三個服務調用
事物發起方 A服務
/**
* @Author chenqi
* @Description 測試 lcn
* @Date 11:45 2019/8/7
* @Param []
* @return java.lang.Boolean
**/
@TxTransaction(isStart = true)
@Transactional(rollbackFor = Exception.class)
@Override
public Boolean txManagerTest(){
//A服務插入數據
String time = DateUtils.findCurrentDateTime();
PartnerUser partnerUser = new PartnerUser();
partnerUser.setUserName("12111111111");
partnerUser.setPassword("12111111111");
partnerUser.setState(1);
partnerUser.setCreateTime(time);
partnerUser.setUpdateTime(time);
partnerUser.setFlagDel(CommonConstant.STATUS_NORMAL);
insert(partnerUser);
//調用B服務保存數據 使用springCloud-feign
appUserService.addTest();
//調用C服務保存數據 使用springCloud-feign
goodsService.addTest();
//拋異常
throw new PartnerServiceException("測試回滾");
}
經測試,B服務和C服務也是會同步回滾的,請看日誌
A服務日誌:
08-07 15:49:06.579 INFO [com.codingapi.tx.framework.utils.SocketManager] -
send-msg->{"a":"cg","k":"7lLz4YGg","p":{"g":"cczxxyiV"}}
08-07 15:49:06.665 INFO [c.c.tx.datasource.relational.LCNStartConnection] -
transaction is start-connection.
08-07 15:49:06.668 INFO [c.c.tx.datasource.relational.LCNStartConnection] -
lcn start connection init ok .
08-07 15:49:06.763 INFO [c.c.t.s.feign.TransactionRestTemplateInterceptor] -
LCN-SpringCloud TxGroup info -> groupId:cczxxyiV
08-07 15:49:06.771 INFO [c.c.r.loadbalancer.LcnZoneAwareLoadBalancerProxy] -
enter chooseServer method, key:null
08-07 15:49:06.797 INFO [com.codingapi.tx.framework.utils.SocketManager] -
send-msg->{"a":"plb","k":"9YlgHHbq","p":{"d":"a0e1839774470462c6294d2ef14ff916"
,"g":"cczxxyiV","k":"e5c4bd28f5d1161bcf7a7eeaccc3569e"}}
08-07 15:49:06.946 INFO [c.c.t.s.feign.TransactionRestTemplateInterceptor] -
LCN-SpringCloud TxGroup info -> groupId:cczxxyiV
08-07 15:49:06.947 INFO [c.c.r.loadbalancer.LcnZoneAwareLoadBalancerProxy] -
enter chooseServer method, key:null
08-07 15:49:06.960 INFO [com.codingapi.tx.framework.utils.SocketManager] -
send-msg->{"a":"plb","k":"bkFzaEM1","p":{"d":"4cd44af89aca600fc757d311a3825189"
,"g":"cczxxyiV","k":"5ced183dfe48f9d1268a36d688501cca"}}
08-07 15:49:08.610 INFO [c.c.tx.datasource.relational.LCNStartConnection] -
rollback label
08-07 15:49:08.621 INFO [com.codingapi.tx.framework.utils.SocketManager] -
send-msg->{"a":"ctg","k":"bgjlWoCA","p":{"s":0,"g":"cczxxyiV"}}
[com.****.****.common.util.exception.PartnerServiceException: 測試回滾]
08-07 15:49:08.720 INFO [c.p.y.common.bean.handler.GlobalExceptionHandler] -
保存***服務異常信息 ex=測試回滾
com.****.****.common.util.exception.PartnerServiceException: 測試回滾
at com.****.****.partner.service.impl.PartnerUserServiceImpl.txManagerTest(
PartnerUserServiceImpl.java:1835)
.....
B服務日誌:
maxOutTime : 5000
transaction is wait for TxManager notify, groupId : cczxxyiV
08-07 15:49:06.914 INFO [com.codingapi.tx.framework.utils.SocketManager] -
send-msg->{"a":"atg","k":"vTbQz6xW","p":{"s":0,"t":"B6FG3S9b"
,"ms":"public void com.****.****.appuser.service.impl.AppUserServiceImpl.addTest()"
,"g":"cczxxyiV"}}
08-07 15:49:08.642 INFO [c.c.tx.control.service.impl.ActionTServiceImpl] -
accept notify data ->{"a":"t","c":0,"t":"B6FG3S9b","k":"mEJRboQ0"}
lcn transaction over, res -> groupId:cczxxyiV and state is rollback
08-07 15:49:08.654 INFO [c.c.tx.control.service.impl.ActionTServiceImpl] -
accept notify response res ->1
C服務日誌:
maxOutTime : 5000
transaction is wait for TxManager notify, groupId : cczxxyiV
08-07 15:49:08.698 INFO [c.c.tx.control.service.impl.ActionTServiceImpl] -
accept notify data ->{"a":"t","c":0,"t":"YTT6A7Ht","k":"8hVTbHGo"}
lcn transaction over, res -> groupId:cczxxyiV and state is rollback
08-07 15:49:08.709 INFO [c.c.tx.control.service.impl.ActionTServiceImpl] -
accept notify response res ->1
至此,SpringCloud集成LCN4.1.0就完成了,使用示例這邊只展示這兩個場景,其他更多的場景大家可以自己去嘗試寫demo測試,有問題歡迎留言討論!
如果該文章有幫助到您,就留言點個贊吧!您的支持與肯定是我持續更新最大的動力。