Environment to prepare

The environment version
JDK 11
Seata 1.4.2
Dubbo 2.7.2
Zookeeper
MybatisPlus

SQL script

Seata library for database scripts used by the SEATA service

/* Navicat Premium Data Transfer Source Server : l-mysql Source Server Type : MySQL Source Server Version : 50733 Source Host: 10.211.55.4:3306 Source Schema: SEata Target Server Type: MySQL Target Server Version: 50733 File Encoding : 65001 Date: 07/05/2021 14:49:55 */

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `lock_key` varchar(128) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL.PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL.PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` mediumtext,
  `branch_id` mediumtext,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL.PRIMARY KEY (`row_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL.PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;

Copy the code

Test library for SQL scripts used by business services

/* Navicat Premium Data Transfer Source Server : l-mysql Source Server Type : MySQL Source Server Version : 50733 Source Host: 10.211.55.4:3306 Source Schema: test Target Server Type: MySQL Target Server Version: 50733 File Encoding : 65001 Date: 07/05/2021 14:51:42 */

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for branch_table
-- ----------------------------
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `lock_key` varchar(128) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint(4) DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL.PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for global_table
-- ----------------------------
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint(20) DEFAULT NULL,
  `status` tinyint(4) NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int(11) DEFAULT NULL,
  `begin_time` bigint(20) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL.PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`,`status`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for lock_table
-- ----------------------------
DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(96) DEFAULT NULL,
  `transaction_id` mediumtext,
  `branch_id` mediumtext,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL.PRIMARY KEY (`row_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for order_account
-- ----------------------------
DROP TABLE IF EXISTS `order_account`;
CREATE TABLE `order_account` (
  `order_account_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `account_id` bigint(20) DEFAULT NULL,
  `order_id` bigint(20) DEFAULT NULL,
  `is_deleted` int(1) DEFAULT NULL.PRIMARY KEY (`order_account_id`)
) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for order_item
-- ----------------------------
DROP TABLE IF EXISTS `order_item`;
CREATE TABLE `order_item` (
  `order_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `type` int(1) DEFAULT NULL.PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `undo_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `branch_id` bigint(20) NOT NULL,
  `xid` varchar(100) NOT NULL,
  `context` varchar(128) NOT NULL,
  `rollback_info` longblob NOT NULL,
  `log_status` int(11) NOT NULL,
  `log_created` datetime NOT NULL,
  `log_modified` datetime NOT NULL,
  `ext` varchar(100) DEFAULT NULL.PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

The SEATA service is installed and started

Download seata

Modify the configuration item of registry.conf

Registry {# file, nacos, Eureka, redis, zk, consul, etCD3, sofa type = "zk" nacos {application = "seta-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = "" cluster = "default" username = "" password = ""} eureka { serviceUrl = "http://localhost:8761/eureka" application = "default" weight = "1" } redis { serverAddr = "localhost:6379" Db = 0 password = "" cluster = "default" timeout = 0} zk {cluster = "default" serverAddr = "10.211.55.4:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" } consul { cluster = "default" serverAddr = "127.0.0.1:8500" aclToken = ""} etCD3 {cluster = "default" serverAddr = "http://localhost:2379"} sofa {serverAddr = "127.0.0.1:9603" application = "default" region = "DEFAULT_ZONE" datacenter = "DefaultDataCenter" cluster = "default" Group = "SEATA_GROUP" addressWaitTime = "3000"} file {name = "file.conf"}} config {# file, nacos , Apollo, zk, etCD3 type = "zk" nacos {serverAddr = "127.0.0.1:8848" namespace = "" group = "SEATA_GROUP" username = "" password = "" dataId = "consul =" seataserver. properties"} consul {serverAddr = "consul = "" AppId = "seata - server" # # apolloConfigService will cover apolloMeta apolloMeta = "http://192.168.1.204:8801" ApolloConfigService = "http://192.168.1.204:8080" namespace = "application" apolloAccesskeySecret = "cluster" = "seata" } zk {serverAddr = "10.211.55.4:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" nodePath  = "/seata/seata.properties" } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" } }Copy the code

Modified items:

registry.type=”zk”

Registry. Zk: serverAddr = “10.211.55.4:2181”

config.type=”zk”

The config. Zk: serverAddr = “10.211.55.4:2181”

Start the SEATA service:

nohup sh bin/seata-server.sh -p 8091 -n 2 &
Copy the code

Synchronize the Seata configuration to zK

Zk-config.properties This file is located in the resources directory of the project. The configuration is as follows. You need to write a script to initialize these configuration items in zK.

transport.type=TCP transport.server=NIO transport.heartbeat=true transport.enableClientBatchSendRequest=false transport.threadFactory.bossThreadPrefix=NettyBoss transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler transport.threadFactory.shareBossWorker=false transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector transport.threadFactory.clientSelectorThreadSize=1 transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread transport.threadFactory.bossThreadSize=1 transport.threadFactory.workerThreadSize=default transport.shutdown.wait=3 service.vgroupMapping.my_test_tx_group=default service.vgroupMapping.account-web-group=default service.vgroupMapping.account-service-app-group=default service.vgroupMapping.order-service-app-group=default Service. The default. Grouplist = 10.211.55.4:8091 service. EnableDegrade = false service. DisableGlobalTransaction = false client.rm.asyncCommitBufferLimit=10000 client.rm.lock.retryInterval=10 client.rm.lock.retryTimes=30 client.rm.lock.retryPolicyBranchRollbackOnConflict=true client.rm.reportRetryCount=5 client.rm.tableMetaCheckEnable=false client.rm.sqlParserType=druid client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 client.tm.degradeCheck=false client.tm.degradeCheckAllowTimes=10 client.tm.degradeCheckPeriod=2000 store.mode=file store.file.dir=file_store/data store.file.maxBranchSessionSize=16384 store.file.maxGlobalSessionSize=512 store.file.fileWriteBufferCacheSize=16384 store.file.flushDiskMode=async store.file.sessionReloadReadSize=100 store.db.datasource=druid store.db.dbType=mysql Store. Db. DriverClassName = com) mysql). The JDBC Driver store. The url = JDBC: mysql: / / 10.211.55.4:3306 / seata? UseUnicode = true store.db.user=volc store.db.password=uURUx%7i store.db.minConn=5 store.db.maxConn=30 store.db.globalTable=global_table store.db.branchTable=branch_table store.db.queryLimit=100 store.db.lockTable=lock_table store.db.maxWait=5000 Host =10.211.55.4 store.redis.port=6379 store.redis.maxconn =10 store.redis.minconn =1 store.redis.database=0 store.redis.password=null store.redis.queryLimit=100 server.recovery.committingRetryPeriod=1000 server.recovery.asynCommittingRetryPeriod=1000 server.recovery.rollbackingRetryPeriod=1000 server.recovery.timeoutRetryPeriod=1000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false client.undo.dataValidation=true client.undo.logSerialization=jackson client.undo.onlyCareUpdateColumns=true server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 client.undo.logTable=undo_log client.log.exceptionRate=100 transport.serialization=seata transport.compressor=none metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898Copy the code

Modify the configuration:

Service. The default. Grouplist = 10.211.55.4:8091 store. The url = JDBC: mysql: / / 10.211.55.4:3306 / seata? UseUnicode = true Store. The db. The user = username store. The password = password service. VgroupMapping. Account - web - group = # default account service groups: The account - service - app - group, group name: default service. VgroupMapping. The account - service - app - group = default # order service groups: The order - service - app - group, the group name: default service. VgroupMapping. Order - service - app - group = defaultCopy the code

Initialize the SEATA configuration script

import io.seata.config.zk.DefaultZkSerializer;
import lombok.extern.slf4j.Slf4j;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.serialize.ZkSerializer;
import org.apache.zookeeper.CreateMode;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Set;

@Slf4j
public class ZkDataInit {

    private static volatile ZkClient zkClient;

    public static void main(String[] args) {

        if (zkClient == null) {
            ZkSerializer zkSerializer = new DefaultZkSerializer();
            zkClient = new ZkClient("10.211.55.4:2181".6000.2000, zkSerializer);
        }
        if(! zkClient.exists("/seata")) {
            zkClient.createPersistent("/seata".true);
        }
        // Obtain the value corresponding to the key
        Properties properties = new Properties();
        // Use ClassLoader to load the properties configuration file to generate the corresponding input stream
        // Load the input stream using the Properties object
        try {
            File file = ResourceUtils.getFile("classpath:zk-config.properties");
            InputStream in = new FileInputStream(file);
            properties.load(in);
            Set<Object> keys = properties.keySet();// Return a collection of attribute keys
            for (Object key : keys) {
                boolean b = putConfig(key.toString(), properties.get(key).toString());
                log.info(key.toString() + "=" + properties.get(key) + "result="+ b); }}catch(IOException e) { e.printStackTrace(); }}/ * * *@param dataId
     * @param content
     * @return* /
    public static boolean putConfig(final String dataId, final String content) {
        Boolean flag = false;
        String path = "/seata/" + dataId;
        if(! zkClient.exists(path)) { zkClient.create(path, content, CreateMode.PERSISTENT); flag =true;
        } else {
            zkClient.writeData(path, content);
            flag = true;
        }
        returnflag; }}Copy the code

~ Connect to zK and check the initialization.

Business services integration Seata

Depend on the package

<dependency>
  <groupId>io.seata</groupId>
  <artifactId>seata-all</artifactId>
  <version>1.4.2</version>
</dependency>
Copy the code

Add the configuration file registry. Conf under Resources

Registry {# file, nacos, Eureka, redis, zk, consul, etCD3, sofa type = "zk" zk {cluster = "default" serverAddr = "10.211.55.4:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = ""}} config {# file, nacos , Apollo, zk, etCD3 type = "zk" zk {serverAddr = "10.211.55.5:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" nodePath = "/seata/seata.properties" } }Copy the code

The configuration class

package com.enterprise.order.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.spring.annotation.GlobalTransactionScanner;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource druidDataSource(a) {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

  
  	/** * Agent database */
    @Primary
    @Bean("dataSource")
    public DataSourceProxy dataSource(DataSource druidDataSource) {
        return new DataSourceProxy(druidDataSource);
    }

    /** * Configure the global transaction scanner with two parameters, one is the application name and one is the transaction group **@return* /
    @Bean
    public GlobalTransactionScanner globalTransactionScanner(a) {
        return new GlobalTransactionScanner("order-service-app"."order-service-app-group"); }}Copy the code

Mybatisplus scans for mapper interface configuration classes

package com.enterprise.order.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.enterprise.order.dal.*.mapper*")
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor(a) {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        returnpaginationInterceptor; }}Copy the code

Start the class

@EnableDubbo
@MapperScan("com.enterprise.order.dal.mapper")
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class OrderServiceApp {
    public static void main(String[] args) { SpringApplication.run(OrderServiceApp.class, args); }}Copy the code

Business code adds distributed transactions

@Override
@GlobalTransactional
public CommonRes<OrderRes> createOrder(OrderReq orderReq) {
    log.info("Global transaction ID:" + RootContext.getXID());
    // Notify the account center to start the order
    accountClient.notifyAccountOrder();
    // Create the order data
    OrderRes order = orderService.createOrder(orderReq);
    return CommonRes.success(order);
}
Copy the code

Configure the account service as the Order service, start the Order service, and observe the logs printed by the SEATA service

10:58:09. 740 INFO - [rverHandlerThread_1_2_500] I.S.C.R.P rocessor. Server RegRmProcessor: The RM register success, message: RegisterRMRequest {resourceIds = 'JDBC: mysql: / / 10.211.55.4:3306 / test', applicationId='order-service-app', transactionServiceGroup='order-service-app-group'},channel:[id: 0x28dca215, L:/10.211.55.4:8091 -r :/10.211.55.2:52633],client version:1.4.2Copy the code

Error:

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [io.seata.spring.annotation.GlobalTransactionScanner]: Factory method 'globalTransactionScanner' threw exception; nested exception is io.seata.common.exception.NotSupportYetException: config type can not be null
Copy the code

The globalTransactionScanner object could not be injected because the Registry. Conf file is not configured in the Resources directory

The interface test

[DubboServerHandler – 10.211.55.2 16:34:46. 680:20881 – thread – 3] INFO com. Enterprise. The order. The RPC. OrderRpcImpl – global transaction id: 10.211.55.4:8091:18145212904984580

Roll back log

[DubboServerHandler - 10.211.55.2 16:34:46. 680:20881 - thread - 3] INFO com. Enterprise. The order. The RPC. OrderRpcImpl - global transaction id: [DubboServerHandler 10.211.55.4:16:34:47 8091-18145212904984580. 043-10.211.55.2:20881 - thread - 3] the DEBUG i.s.r.d.undo.AbstractUndoLogManager - Flushing UNDO LOG: {@ "class" : "IO. Seata. Rm. The datasource. Undo. BranchUndoLog", "xid" : "10.211.55.4:8091-18145212904984580", "branchId" : 181452129049 84582,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"INSERT","table Name":"order_item","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords$EmptyTableRecords","tableName ":"order_item","rows":["java.util.ArrayList",[]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords ","tableName":"order_item","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["j ava.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"order_id","keyType":"PRIMARY_KEY","type ":-5,"value":["java.lang.Long",101]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"type","keyType":"NULL", "Type" : 4, "value" : 1}}}}]]]]]]} 16:34:47, 046 [DubboServerHandler - 10.211.55.2:20881 - thread - 3] the DEBUG c.e.order.dal.mapper.OrderMapper.insert - <== Updates: 1 16:34:47. [DubboServerHandler - 10.211.55.2:20881, 048 - thread - 3] the DEBUG org. Mybatis. Spring. SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3c17d29a]Copy the code

Conclusion:

  1. Seata database and business data scripts.
  2. Download seata, modify the registry. Conf file, and start the Seata service.
  3. Synchronize seATA configuration to ZK (brush script).
  4. For project consolidation, add the registry. Conf configuration to the Resources directory.
  5. Project integration, SEATA proxy business data source DataSourceProxy.
  6. Project integration, injection configuration GlobalTransactionScanner GlobalTransactionScanner.
  7. Business agents use global transaction annotations.