LCN實現分佈式事務

LCN介紹

官方宣稱:LCN並不生產事務,LCN只是本地事務的協調工。 TX-LCN定位於一款事務協調性框架,框架其本身並不操作事務,而是基於對事務的協調從而達到事務一致性的效果。

LCN解決方案

在一個分佈式系統下存在多個模塊協調來完成一次業務。那麼就存在一次業務事務下可能橫跨多種數據源節點的可能。TX-LCN將可以解決這樣的問題

例如存在服務模塊A 、B、 C。A模塊是mysql作為數據源的服務,B模塊是基於redis作為數據源的服務,C模塊是基於mongo作為數據源的服務。若需要解決他們的事務一致性就需要針對不同的節點採用不同的方案,並且統一協調完成分佈式事務的處理。

LCN實現分佈式事務

若採用TX-LCN分佈式事務框架,則可以將A模塊採用LCN模式、B/C採用TCC模式就能完美解決。

事務控制原理

TX-LCN由兩大模塊組成, TxClient、TxManager,TxClient作為模塊的依賴框架,提供TX-LCN的標準支持,TxManager作為分佈式事務的控制放。事務發起方或者參與反都由TxClient端來控制。

LCN實現分佈式事務

核心步驟

創建事務組

是指在事務發起方開始執行業務代碼之前先調用TxManager創建事務組對象,然後拿到事務標示GroupId的過程。

加入事務組

添加事務組是指參與方在執行完業務方法以後,將該模塊的事務信息通知給TxManager的操作。

通知事務組

是指在發起方執行完業務代碼以後,將發起方執行結果狀態通知給TxManager,TxManager將根據事務最終狀態和事務組的信息來通知相應的參與模塊提交或回滾事務,並返回結果給事務發起方。

實戰之-TxManager

LCN官方文檔中的快速開始,說實話,還真快速不了,首先咱們先看下官方文檔怎麼說的。

建中間件數據庫

名稱為: tx-manager,並創建表。

CREATE TABLE `t_tx_exception` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,


`mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`transaction_state` tinyint(4) NULL DEFAULT NULL,
`registrar` tinyint(4) NULL DEFAULT NULL,
`remark` varchar(4096) NULL DEFAULT NULL,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解決 1已解決',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

TM服務搭建

LCN實現分佈式事務

將官方源碼倉庫中的txlcn-tm直接打包,然後運行即可,TM中間件就算搭起來了,如果直接把txlcn-tm拎出來,打包是打不成功的,因為txlcn-tm 依賴了其他的txlcn的其他服務,打包的話需要把其他包也一起打進去,我現在是直接本地導入IDE運行的,是可以的。

LCN實現分佈式事務

TM服務中 application.properties配置

spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8&usessl=false
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update

#Redis配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=

tx-lcn.manager.admin-key=lcn


#spring.application.name=TransactionManager
#server.port=7970
#
## JDBC 數據庫配置
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8
#spring.datasource.username=root
#spring.datasource.password=123456
#
## 數據庫方言
#spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#
## 第一次運行可以設置為: create, 為TM創建持久化數據庫表
#spring.jpa.hibernate.ddl-auto=validate
#
## TM監聽IP. 默認為 127.0.0.1
#tx-lcn.manager.host=127.0.0.1
#
## TM監聽Socket端口. 默認為 ${server.port} - 100
#tx-lcn.manager.port=8070
#
## 心跳檢測時間(ms). 默認為 300000
#tx-lcn.manager.heart-time=300000
#
## 分佈式事務執行總時間(ms). 默認為36000
#tx-lcn.manager.dtx-time=8000
#
## 參數延遲刪除時間單位ms 默認為dtx-time值
#tx-lcn.message.netty.attr-delay-time=${tx-lcn.manager.dtx-time}
#
## 事務處理併發等級. 默認為機器邏輯核心數5倍
#tx-lcn.manager.concurrent-level=160
#
## TM後臺登陸密碼,默認值為codingapi
#tx-lcn.manager.admin-key=codingapi
#
## 分佈式事務鎖超時時間 默認為-1,當-1時會用tx-lcn.manager.dtx-time的時間


#tx-lcn.manager.dtx-lock-time=${tx-lcn.manager.dtx-time}
#
## 雪花算法的sequence位長度,默認為12位.
#tx-lcn.manager.seq-len=12
#
## 異常回調開關。開啟時請制定ex-url
#tx-lcn.manager.ex-url-enabled=false
#
## 事務異常通知(任何http協議地址。未指定協議時,為TM提供內置功能接口)。默認是郵件通知
#tx-lcn.manager.ex-url=/provider/email-to/***@**.com

#注意(NOTE)
#(1) TxManager所有配置均有默認配置,請按需覆蓋默認配置。
#(2) 特別注意 TxManager進程會監聽兩個端口號,一個為TxManager端口,另一個是事務消息端口。TxClient默認連接事務消息端口是8070, 所以,為保證TX-LCN基於默認配置運行良好,請設置TxManager端口號為8069 或者指定事務消息端口為8070
#(3) 分佈式事務執行總時間 a 與 TxClient通訊最大等待時間 b、TxManager通訊最大等待時間 c、微服務間通訊時間 d、微服務調用鏈長度 e 幾個時間存在著依賴關係。 a >= 2c + (b + c + d) * (e - 1), 特別地,b、c、d 一致時,a >= (3e-1)b。你也可以在此理論上適當在減小a的值,發生異常時能更快得到自動補償,即 a >= (3e-1)b - Δ(原因)。 最後,調用鏈小於等於3時,將基於默認配置運行良好
#(4) 若用tx-lcn.manager.ex-url=/provider/email-to/[email protected] 這個配置,配置管理員郵箱信息(如QQ郵箱):
#spring.mail.host=smtp.qq.com
#spring.mail.port=587
#spring.mail.username=xxxxx@**.com

啟動成功後界面

LCN實現分佈式事務

TxManager進程會監聽兩個端口號,一個為TxManager 7970 端口,另一個是事務消息端口 8070。

實戰之-TxClient

場景簡介

AB兩個服務,A(producer)服務調用B(consumer)服務完畢後,拋出異常,B服務能回滾,確保冪等。

AB環境:MybatisPlus,Erueka,Feign,這些具體就不細講了

服務結構

LCN實現分佈式事務

producer 服務

Pom.xml


<project>xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<parent>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-parent/<artifactid>
<version>2.1.2.RELEASE/<version>
<relativepath>
/<parent>

<groupid>com.glj/<groupid>


<artifactid>producer/<artifactid>
<version>0.0.1-SNAPSHOT/<version>
<name>producer/<name>
<modelversion>4.0.0/<modelversion>
<description>Demo project for Spring Boot /<description>

<properties>
<project.build.sourceencoding>UTF-8
<project.reporting.outputencoding>UTF-8 /<project.reporting.outputencoding>
<java.version>1.8/<java.version>
<codingapi.txlcn.version>5.0.2.RELEASE /<codingapi.txlcn.version>
/<project.build.sourceencoding>/<properties>

<dependencies>

<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-web /<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-test /<artifactid>
<scope>test/<scope>
/<dependency>
<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-freemarker /<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-netflix-eureka-client /<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-openfeign/<artifactid>
/<dependency>

<dependency>
<groupid>com.google.guava/<groupid>
<artifactid>guava/<artifactid>
<version>27.0-jre/<version>
/<dependency>

<dependency>
<groupid>mysql/<groupid>
<artifactid>mysql-connector-java/<artifactid>
<scope>runtime/<scope>
/<dependency>

<dependency>
<groupid>org.projectlombok/<groupid>
<artifactid>lombok/<artifactid>
<optional>true/<optional>
/<dependency>


<dependency>
<groupid>com.baomidou/<groupid>
<artifactid>mybatis-plus-boot-starter/<artifactid>
<version>3.0.6/<version>
/<dependency>

<dependency>
<groupid>io.springfox/<groupid>
<artifactid>springfox-swagger2/<artifactid>
<version>2.8.0/<version>
<exclusions>
<exclusion>
<artifactid>org.mapstruct/<artifactid>
<groupid>mapstruct/<groupid>
/<exclusion>
/<exclusions>
/<dependency>
<dependency>
<groupid>io.springfox/<groupid>
<artifactid>springfox-swagger-ui/<artifactid>
<version>2.8.0/<version>
/<dependency>

<dependency>
<groupid>com.codingapi.txlcn/<groupid>
<artifactid>txlcn-tc/<artifactid>
<version>${codingapi.txlcn.version}/<version>
/<dependency>

<dependency>
<groupid>com.codingapi.txlcn/<groupid>
<artifactid>txlcn-txmsg-netty/<artifactid>
<version>${codingapi.txlcn.version}/<version>
/<dependency>


<build>
<plugins>
<plugin>


<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-maven-plugin/<artifactid>
/<plugin>
/<plugins>
/<build>

<dependencymanagement>
<dependencies>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-dependencies/<artifactid>
<version>Greenwich.RELEASE/<version>
<type>pom/<type>
<scope>import/<scope>
/<dependency>
/<dependencies>
/<dependencymanagement>

application.yml

spring:
application:
name: spring-cloud-producer

datasource:
url: jdbc:mysql://127.0.0.1:3306/spring_cloud_app? serverTimezone=GMT%2B8&characterEncoding=utf-8
password: root
username: root
driver-class-name: com.mysql.cj.jdbc.Driver

redis:
host: 127.0.0.1
port: 6379

server:
port: 8081

eureka:
client:
service-url:
defaultZone: http://glj:[email protected]:2100/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ipAddress}: ${server.port}

#swagger
swagger2:
enable: true

tx-lcn:
client:
manager-address: 127.0.0.1:8070

服務入口

package com.glj.producer;

import com.codingapi.txlcn.tc.config. EnableDistributed Transaction;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure. SpringBootApplication;
import org.springframework.cloud.client.discovery. EnableDiscoveryClient;
import org.springframework.cloud.openfeign. EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableDistributedTransaction
public class ProducerApplication {

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

}

.

package com.glj.producer.business.service.impl;

import com.baomidou.mybatisplus.extension.service. impl.ServiceImpl;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.glj.producer.business.entity.UserPo;
import com.glj.producer.business.mapper.UserMapper;
import com.glj.producer.business.service.IUserService;
import com.glj.producer.client.ConsumerUserClinet;
import com.glj.producer.dto.ProducerRequst;
import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory. annotation. Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation. Transactional;

/**
*


* 服務實現類


*


*
* @author gaoleijie
* @since 2019-05-21
*/
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl <usermapper> implements IUserService {
@Autowired
private ConsumerUserClinet consumerUserClinet;
@Autowired
private IUserService userService;

/**
* 使用分佈式事務
* @param requst
* @return
*/
@Override
@LcnTransaction
@Transactional
public String aTob(ProducerRequst requst) {
log.info("開始調用B服務");
Boolean res = consumerUserClinet.saveUser(requst .getNickName(),requst.getId());
log.info("B服務return result{}",res);

UserPo userPo = userService.getById(requst.getId());
userPo.setUserName(requst.getNickName());
userService.saveOrUpdate(userPo);

Preconditions.checkArgument (StringUtils.isNotBlank (requst.getExFlag()),"exFlag is null");

if(res) {
return "scuess";
} else {
return "false";
}
}

/**
* 未使用分佈式事務
* @param requst
* @return
*/
@Override
@Transactional
public String aTob1(ProducerRequst requst) {
log.info("開始調用B服務");


Boolean res = consumerUserClinet.saveUser1 (requst.getNickName(),requst.getId());
log.info("B服務return result{}",res);

UserPo userPo = userService.getById(requst.getId());
userPo.setUserName(requst.getNickName());
userService.saveOrUpdate(userPo);

Preconditions.checkArgument (StringUtils.isNotBlank (requst.getExFlag()),"exFlag is null");

if(res) {
return "scuess";
} else {
return "false";
}
}
}

-

package com.glj.producer.business.controller;


import com.glj.producer.business.service.IUserService;
import com.glj.producer.dto.ProducerRequst;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
*


* 前端控制器
*


*
* @author gaoleijie
* @since 2019-05-21
*/
@RestController
@RequestMapping("/user")
public class UserController {

@Autowired
private IUserService userService;

@ApiOperation(value = "A 服務調用B服務(使用分佈式事務)")
@PostMapping("/aTob")


public String aTob(@RequestBody ProducerRequst requst){
return userService.aTob(requst);
}

@ApiOperation(value = "A 服務調用B服務(未使用分佈式事務)")
@PostMapping("/aTob1")
public String aTob1(@RequestBody ProducerRequst requst){
return userService.aTob1(requst);
}
}

其實就是這麼簡單,核心代碼就三處 一處是服務入口加註解@EnableDistributedTransaction 一處是service方法上加註解 @LcnTransaction 另一處是application.yml 指定事務消息端口

consumer 服務

Pom.xml


<project>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>
<parent>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-parent/<artifactid>
<version>2.1.2.RELEASE/<version>
<relativepath>
/<parent>
<groupid>com.glj/<groupid>
<artifactid>consumer/<artifactid>
<version>0.0.1-SNAPSHOT/<version>
<name>consumer/<name>
<description>Demo project for Spring Boot/<description>

<properties>
<project.build.sourceencoding>UTF-8
<project.reporting.outputencoding>UTF-8
<java.version>1.8/<java.version>
<codingapi.txlcn.version>5.0.2.RELEASE
<springcloud.version>Greenwich.RELEASE springcloud.version>
<swagger-version>2.8.0/<swagger-version>
/<springcloud.version>/<codingapi.txlcn.version>/<project.reporting.outputencoding>/<project.build.sourceencoding>/<properties>

<dependencies>


<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-freemarker /<artifactid>
/<dependency>/<dependencies>

<dependency>
<groupid>com.baomidou/<groupid>
<artifactid>mybatis-plus-boot-starter /<artifactid>
<version>3.0.6/<version>
<exclusions>
<exclusion>
<groupid>org.apache.tomcat/<groupid>
<artifactid>tomcat-jdbc/<artifactid>
/<exclusion>
/<exclusions>
/<dependency>

<dependency>
<groupid>mysql/<groupid>
<artifactid>mysql-connector-java/<artifactid>
<scope>runtime/<scope>
/<dependency>
<dependency>
<groupid>org.projectlombok/<groupid>
<artifactid>lombok/<artifactid>
<optional>true/<optional>
/<dependency>

<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-test/<artifactid>
<scope>test/<scope>
/<dependency>
<dependency>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-starter-web/<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-netflix-eureka-client /<artifactid>
/<dependency>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-netflix-hystrix /<artifactid>
/<dependency>


<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-starter-openfeign /<artifactid>


/<dependency>


<dependency>
<groupid>io.springfox/<groupid>
<artifactid>springfox-swagger2/<artifactid>
<version>2.8.0/<version>
<exclusions>
<exclusion>
<artifactid>org.mapstruct/<artifactid>
<groupid>mapstruct/<groupid>
/<exclusion>
/<exclusions>
/<dependency>
<dependency>
<groupid>io.springfox/<groupid>
<artifactid>springfox-swagger-ui/<artifactid>
<version>${swagger-version}/<version>
/<dependency>

<dependency>
<groupid>com.codingapi.txlcn/<groupid>
<artifactid>txlcn-tc/<artifactid>
<version>${codingapi.txlcn.version}/<version>
/<dependency>

<dependency>
<groupid>com.codingapi.txlcn/<groupid>
<artifactid>txlcn-txmsg-netty/<artifactid>
<version>${codingapi.txlcn.version}/<version>
/<dependency>


<dependency>
<groupid>com.google.guava/<groupid>
<artifactid>guava/<artifactid>
<version>27.0-jre/<version>
/<dependency>


<build>
<plugins>
<plugin>
<groupid>org.springframework.boot/<groupid>
<artifactid>spring-boot-maven-plugin /<artifactid>
/<plugin>
/<plugins>


/<build>

<dependencymanagement>
<dependencies>
<dependency>
<groupid>org.springframework.cloud/<groupid>
<artifactid>spring-cloud-dependencies/<artifactid>
<version>${springcloud.version}/<version>
<type>pom/<type>
<scope>import/<scope>
/<dependency>
/<dependencies>
/<dependencymanagement>

服務入口

package com.glj.consumer;

import com.codingapi.txlcn.tc.config. Enable DistributedTransaction;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure. SpringBootApplication;
import org.springframework.cloud.client.discovery. EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@EnableDistributedTransaction
public class ConsumerApplication {

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

}

package com.glj.consumer.business.service.impl;

import com.baomidou.mybatisplus.extension. service.impl.ServiceImpl;
import com.codingapi.txlcn.tc.annotation. LcnTransaction;
import com.glj.consumer.business.entity. SysUserPo;
import com.glj.consumer.business.mapper. SysUserMapper;
import com.glj.consumer.business.service. ISysUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation. Transactional;

/**
*


* 服務實現類


*


*
* @author gaoleijie
* @since 2019-05-21
*/
@Service
public class SysUserServiceImpl extends ServiceImpl <sysusermapper> implements ISysUserService {
/**
* LCN實現分佈式事務
* @param nickName
* @param Id
* @return
*/
@Override
@Transactional
@LcnTransaction
public Boolean saveUser(String nickName,Long Id){
SysUserPo user = this.getById(Id);
user.setNickname(nickName);
return this.saveOrUpdate(user);
}

/**
* 未實現分佈式事務
* @param nickName
* @param Id
* @return
*/
@Override
@Transactional
public Boolean saveUser1(String nickName,Long Id){
SysUserPo user = this.getById(Id);
user.setNickname(nickName);
return this.saveOrUpdate(user);
}
}

還是那句話,就這麼簡單,跟生產者同樣三處

LCN實現分佈式事務

LCN實現分佈式事務

效果

結果表明使用分佈式以後,異常後數據回滾 沒有使用分佈式,異常後,AB數據不一致

"/<sysusermapper>/<usermapper>


分享到:


相關文章: