Lcn分佈式事務流程實現(啓動事務協調者)
如果覺得還可以 記得關注一下公衆號哦!一起交流學習!
一、lcn流程圖實現
二、Lcn介紹
1. tx-lcn
1.正如官網所說的:LCN並不生產事務,LCN只是本地事務的協調工!
Lcn本身不會產生事務,也不會涉及到某些業務代碼!他對事務的操作本身就依賴一個事務協調者服務
如上圖所說的一樣 他分爲4個步驟
- 服務發起者 在事務協調者內創建事務組,並將本事務加入事務組
- 事務參與者加入事務組,直到有結束標記出現
- 事務協調者向所有的事務參與者發送詢問,是否能夠提交!全部提交則事務組提交!有一個回滾標記則事務組回滾!
- 事務組執行操作之後,釋放所有鎖資源!
三、代碼案例
1 創建數據庫 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;
2.引入事務協調者,並啓動
github:https://github.com/codingapi/tx-lcn.git
將標記的項目加入IDEA,並修改配置文件
spring.application.name=tx-manager
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
spring.datasource.username=root
spring.datasource.password=root
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
#tx-lcn.logger.enabled=true
# TxManager Host Ip
#tx-lcn.manager.host=127.0.0.1
# TxClient連接請求端口
#tx-lcn.manager.port=8070
# 心跳檢測時間(ms)
#tx-lcn.manager.heart-time=15000
# 分佈式事務執行總時間
#tx-lcn.manager.dtx-time=30000
#參數延遲刪除時間單位ms
#tx-lcn.message.netty.attr-delay-time=10000
#tx-lcn.manager.concurrent-level=128
# 開啓日誌
#tx-lcn.logger.enabled=true
#logging.level.com.codingapi=debug
#redis 主機
#spring.redis.host=127.0.0.1
#redis 端口
#spring.redis.port=6379
#redis 密碼
#spring.redis.password=
以上的配置詳情介紹
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
# 開啓日誌,默認爲false
tx-lcn.logger.enabled=true
tx-lcn.logger.enabled=false
tx-lcn.logger.driver-class-name=${spring.datasource.driver-class-name}
tx-lcn.logger.jdbc-url=${spring.datasource.url}
tx-lcn.logger.username=${spring.datasource.username}
tx-lcn.logger.password=${spring.datasource.password}
# redis 的設置信息. 線上請用Redis Cluster
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
3 啓動事務協調者
Lcn案例編寫
一、上個文章裏面已經啓動了事務協調者 現在我們來開發一個demo來測試一下協調者是否生效了吧!
一、編寫數據庫
分別在兩個不同的庫中創建兩張表
CREATE DATABASE /*!32312 IF NOT EXISTS*/`server1` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin */;
USE `server1`;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for server1
-- ----------------------------
DROP TABLE IF EXISTS `server1`;
CREATE TABLE `server1` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`server2` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin */;
USE `server2`;
-- ----------------------------
-- Table structure for server2
-- ----------------------------
DROP TABLE IF EXISTS `server2`;
CREATE TABLE `server2` (
`id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
二、父項目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.lcn</groupId>
<artifactId>lcn-test</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>server1</module>
<module>public-resources</module>
<module>server2</module>
<module>eureka</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--spring boot的核心啓動器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
三、Eureka編寫
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">
<parent>
<artifactId>lcn-test</artifactId>
<groupId>com.lcn</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.lcn</groupId>
<artifactId>eureka</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
配置文件 application.yml
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
server:
peer-eureka-nodes-update-interval-ms: 60000
enable-self-preservation: false
eviction-interval-timer-in-ms: 5000
client:
service-url:
defaultZone: http://localhost:8761/eureka/
register-with-eureka: false
fetch-registry: true
啓動類
package com.eureka.server;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author huangfu
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServer {
public static void main(String[] args) {
new SpringApplicationBuilder(EurekaServer.class).run(args);
}
}
四、創建公共資源 module(public-resources)
創建serve1實體類
package com.pojo;
import lombok.Data;
/**
* @author huangfu
*/
@Data
public class ServerOne {
private String id;
private String name;
public ServerOne() {
}
public ServerOne(String id, String name) {
this.id = id;
this.name = name;
}
}
創建server2
package com.pojo;
import lombok.Data;
/**
* @author huangfu
*/
@Data
public class ServerTwo {
private String id;
private String name;
public ServerTwo() {
}
public ServerTwo(String id, String name) {
this.id = id;
this.name = name;
}
}
項目結構
五、Server1開發
配置文件 appplication.yml
server:
port: 8080
spring:
application:
name: server1
main:
allow-bean-definition-overriding: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/lcn-server1?useUnicode=true&characterEncoding=utf8
username: root
password: root
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
feign:
hystrix:
enabled : true
# 在feign中開啓hystrix功能,默認情況下feign不開啓hystrix功能
mybatis:
configuration:
map-underscore-to-camel-case: true
tx.properties
注意:必須添加,用來指定事務協調者的訪問位置
# 默認之配置爲TM的本機默認端口
tx-lcn.client.manager-address=127.0.0.1:8070
啓動類 注意:啓動類必須添加
@EnableDistributedTransaction
註解 啓動分佈式事務
package com.order;
import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author huangfu
*/
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
@MapperScan("com.order.mapper")
@EnableDistributedTransaction
public class ServerOneApplication {
public static void main(String[] args) {
SpringApplication.run(ServerOneApplicatin.class);
}
}
Mapper類 開發
package com.order.mapper;
import com.pojo.ServerOne;
import org.apache.ibatis.annotations.Insert;
/**
* @author huangfu
*/
public interface ServerOneMapper {
/**
* 插入數據
* @param serverOne
*/
@Insert("insert into server1 values(#{id},#{name})")
void insertData(ServerOne serverOne);
}
service類開發
package com.order.server;
import com.pojo.ServerOne;
/**
* @author huangfu
*/
public interface ServerOneService {
/**
* 插入數據
* @param serverOne
*/
void insertData(ServerOne serverOne,String id);
}
注意:涉及到分佈式事務的一定要添加
@LcnTransaction
註解
package com.order.server.impl;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.order.client.ServerTwoClient;
import com.order.mapper.ServerOneMapper;
import com.order.server.ServerOneService;
import com.pojo.ServerOne;
import com.pojo.ServerTwo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author huangfu
*/
@Service
@SuppressWarnings("all")
public class ServerOneServiceImpl implements ServerOneService {
@Autowired
private ServerOneMapper serverOneMapper;
@Autowired
private ServerTwoClient serverTwoClient;
@Override
@LcnTransaction
@Transactional(rollbackFor = Exception.class)
public void insertData(ServerOne serverOne,String id) {
serverOneMapper.insertData(serverOne);
ServerTwo serverTwo = new ServerTwo(serverOne.getId(),serverOne.getName());
serverTwoClient.addData2(serverTwo);
if("1".equals(id)){
throw new RuntimeException("自定義異常");
}
System.out.println("---------------服務一執行完成---------------");
}
}
feign遠程調用Server2服務
package com.order.client;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.pojo.ServerTwo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @author huangfu
*/
@FeignClient(value = "server2")
public interface ServerTwoClient {
@RequestMapping(value = "/addData2",method = RequestMethod.POST)
public void addData2(@RequestBody ServerTwo serverTwo);
}
package com.order.client.impl;
import com.order.client.ServerTwoClient;
import com.pojo.ServerTwo;
import org.springframework.stereotype.Component;
/**
* @author huangfu
*/
@Component
public class ServerTwoClientImpl implements ServerTwoClient {
@Override
public void addData2(ServerTwo serverTwo) {
System.out.println("------斷路器-------------");
}
}
開發Controller
package com.order.controller;
import com.order.server.ServerOneService;
import com.pojo.ServerOne;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
/**
* @author huangfu
*/
@RestController
public class ServerOneController {
private final ServerOneService serverOneService;
@Autowired
public ServerOneController(ServerOneService serverOneService) {
this.serverOneService = serverOneService;
}
@RequestMapping("addDataOne")
public String addDataOne(String id){
ServerOne serverOne = new ServerOne();
serverOne.setId(UUID.randomUUID().toString());
serverOne.setName("張三");
serverOneService.insertData(serverOne,id);
return "success";
}
}
項目結構
六、Server2開發
配置文件 appplication.yml
server:
port: 8080
spring:
application:
name: server2
main:
allow-bean-definition-overriding: true
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/lcn-server2?useUnicode=true&characterEncoding=utf8
username: root
password: root
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
instance:
prefer-ip-address: true
feign:
hystrix:
enabled : true
# 在feign中開啓hystrix功能,默認情況下feign不開啓hystrix功能
mybatis:
configuration:
map-underscore-to-camel-case: true
tx.properties
注意:必須添加,用來指定事務協調者的訪問位置
# 默認之配置爲TM的本機默認端口
tx-lcn.client.manager-address=127.0.0.1:8070
啓動類 注意:啓動類必須添加
@EnableDistributedTransaction
註解 啓動分佈式事務
package com.server2;
import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author huangfu
*/
@EnableEurekaClient
@SpringBootApplication
@EnableFeignClients
@MapperScan("com.server2.mapper")
@EnableDistributedTransaction
public class ServerTwoApplication {
public static void main(String[] args) {
SpringApplication.run(ServerTwoApplication.class);
}
}
mapper
package com.server2.mapper;
import com.pojo.ServerOne;
import com.pojo.ServerTwo;
import org.apache.ibatis.annotations.Insert;
/**
* @author huangfu
*/
public interface ServerTwoMapper {
/**
* 插入數據
* @param serverTwo
*/
@Insert("insert into server2 values(#{id},#{name})")
void insertData(ServerTwo serverTwo);
}
service類開發
package com.server2.server;
import com.pojo.ServerOne;
import com.pojo.ServerTwo;
/**
* @author huangfu
*/
public interface ServerTwoService {
/**
* 插入數據
* @param serverTwo
*/
void insertData(ServerTwo serverTwo);
}
注意:涉及到分佈式事務的一定要添加
@LcnTransaction
註解
package com.server2.server.impl;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.pojo.ServerOne;
import com.pojo.ServerTwo;
import com.server2.mapper.ServerTwoMapper;
import com.server2.server.ServerTwoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author huangfu
*/
@Service
@SuppressWarnings("all")
public class ServerTwoServiceImpl implements ServerTwoService {
@Autowired
private ServerTwoMapper serverTwoMapper;
@Override
@LcnTransaction
@Transactional(rollbackFor = Exception.class)
public void insertData(ServerTwo serverTwo) {
serverTwoMapper.insertData(serverTwo);
System.out.println("---------------服務二執行完成---------------");
}
}
Controller開發
package com.server2.controller;
import com.pojo.ServerTwo;
import com.server2.server.ServerTwoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author huangfu
*/
@RestController
public class ServerTwoController {
@Autowired
private ServerTwoService serverTwoService;
@PostMapping("addData2")
public void addData(@RequestBody ServerTwo serverTwo){
serverTwoService.insertData(serverTwo);
}
}
server2項目結構
七、瀏覽器訪問
http://localhost:8080/addDataOne?id=2 當id=1時出現異常,測試各數據庫是否回滾