TX-LCN分佈式事務使用方案

Lcn分佈式事務流程實現(啓動事務協調者)

如果覺得還可以 記得關注一下公衆號哦!一起交流學習!
在這裏插入圖片描述

一、lcn流程圖實現

在這裏插入圖片描述

二、Lcn介紹

1. tx-lcn

1.正如官網所說的:LCN並不生產事務,LCN只是本地事務的協調工!

Lcn本身不會產生事務,也不會涉及到某些業務代碼!他對事務的操作本身就依賴一個事務協調者服務

如上圖所說的一樣 他分爲4個步驟

  1. 服務發起者 在事務協調者內創建事務組,並將本事務加入事務組
  2. 事務參與者加入事務組,直到有結束標記出現
  3. 事務協調者向所有的事務參與者發送詢問,是否能夠提交!全部提交則事務組提交!有一個回滾標記則事務組回滾!
  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時出現異常,測試各數據庫是否回滾

八、整體項目結構

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章