Seata 部署&使用AT+TCC模式

237 阅读10分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

启动Server

Server端存储模式:

  • file:单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高
  • db:高可用模式,全局事务会话信息通过db共享,相应性能差些
  • redis:性能较高,存在事务信息丢失风险,请提前配置合适当前场景的redis持久化配置。

下载包

seata-server-1.5.2.tar.gz

建表

-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
    `xid`                       VARCHAR(128) NOT NULL,
    `transaction_id`            BIGINT,
    `status`                    TINYINT      NOT NULL,
    `application_id`            VARCHAR(32),
    `transaction_service_group` VARCHAR(32),
    `transaction_name`          VARCHAR(128),
    `timeout`                   INT,
    `begin_time`                BIGINT,
    `application_data`          VARCHAR(2000),
    `gmt_create`                DATETIME,
    `gmt_modified`              DATETIME,
    PRIMARY KEY (`xid`),
    KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
    KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
    `branch_id`         BIGINT       NOT NULL,
    `xid`               VARCHAR(128) NOT NULL,
    `transaction_id`    BIGINT,
    `resource_group_id` VARCHAR(32),
    `resource_id`       VARCHAR(256),
    `branch_type`       VARCHAR(8),
    `status`            TINYINT,
    `client_id`         VARCHAR(64),
    `application_data`  VARCHAR(2000),
    `gmt_create`        DATETIME(6),
    `gmt_modified`      DATETIME(6),
    PRIMARY KEY (`branch_id`),
    KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
    `row_key`        VARCHAR(128) NOT NULL,
    `xid`            VARCHAR(128),
    `transaction_id` BIGINT,
    `branch_id`      BIGINT       NOT NULL,
    `resource_id`    VARCHAR(256),
    `table_name`     VARCHAR(32),
    `pk`             VARCHAR(36),
    `status`         TINYINT      NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
    `gmt_create`     DATETIME,
    `gmt_modified`   DATETIME,
    PRIMARY KEY (`row_key`),
    KEY `idx_status` (`status`),
    KEY `idx_branch_id` (`branch_id`),
    KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

CREATE TABLE IF NOT EXISTS `distributed_lock`
(
    `lock_key`       CHAR(20) NOT NULL,
    `lock_value`     VARCHAR(20) NOT NULL,
    `expire`         BIGINT,
    primary key (`lock_key`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4;

INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);

修改store.mode

seata-->conf-->application.yml,修改store.mode=db

修改数据库连接

模版在seata-->conf-->application.example.yml中:

store:
  # support: file 、 db 、 redis
  mode: db
  db:
    datasource: druid
    db-type: mysql
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/seata?rewriteBatchedStatements=true
    user: root
    password: root
    min-conn: 5
    max-conn: 100
    global-table: global_table
    branch-table: branch_table
    lock-table: lock_table
    distributed-lock-table: distributed_lock
    query-limit: 100
    max-wait: 5000

启动

进入seata bin目录:

cd /xxx/seata/bin/

执行命令

./seata-server.sh -h 127.0.0.1 -p 8091 -m db

需要注意

mysql版本和mysql-connector-java 驱动jar包版本的匹配。笔者mysql版本是8.0.29,启动后如下:

image.png 搜下seata-->lib下的mysql-connector-java会发现在mysql文件夹下seata为你准备:

image.png 明显不匹配,自己下载mysql-connector-java-8.0.29.jar放在lib目录下即可。

image.png

业务系统集成-AT

添加依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>2.2.2.RELEASE</version>
    <exclusions>
        <exclusion>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>1.5.2</version>
</dependency>

undo_log表新建、配置参数(仅AT模式)

  • ddl
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id`     BIGINT       NOT NULL COMMENT 'branch transaction id',
`xid`           VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context`       VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB     NOT NULL COMMENT 'rollback info',
`log_status`    INT(11)      NOT NULL COMMENT '0:normal status,1:defense status',
`log_created`   DATETIME(6)  NOT NULL COMMENT 'create datetime',
`log_modified`  DATETIME(6)  NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
  • 参数
    • registry.type:注册中心类型,默认file,支持file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
    • config.type:配置中心类型,默认file,支持file、nacos 、apollo、zk、consul、etcd3、custom
    • service.vgroupMapping.my_test_tx_group:事务群组,my_test_tx_group为分组,配置项值为TC集群名

    事务分组是seata的资源逻辑,类似于服务实例。在file.conf中的my_test_tx_group就是一个事务分组。事务分组的配置项值就是TC集群名称,去相应注册中心拉取相应服务名的服务列表,获取真实的TC服务列表。

    这样设计的目的是事务分组可以作为资源的逻辑隔离单位,当发生故障时可以快速failover

    • service.default.grouplist:TC服务列表,仅注册中心为file时使用

    可以配置多个,配置多个意味着集群。不推荐使用,不是真正的注册中心,不具服务的健康检查机制当tc不可用时无法自动剔除列表,推荐使用nacos 、eureka、redis、zk、consul、etcd3、sofa。registry.type=file或config.type=file 设计的初衷是让用户再不依赖第三方注册中心或配置中心的前提下,通过直连的方式,快速验证seata服务

    • service.disableGlobalTransaction:全局事务开关,默认false。false为开启,true为关闭

以下是demo配置:

seata:
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 127.0.0.1:8091

数据源代理

我们使用了seata-starter,默认的data-source-proxy-mode为AT,默认开启enable-auto-data-source-proxy。

初始化GlobalTransactionScanner

自动:引入了seata-spring-boot-starter、spring-cloud-starter-alibaba-seata会自动注入。

实现xid跨服务传递

自动:springCloud用户可以引入spring-cloud-starter-alibaba-seata,内部已经实现xid传递

使用

不管是发起方还是被调用方都需要添加依赖、undo_log表新建、配置参数。

发起方

package com.study.seata.controller;

import com.study.seata.service.SeataService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SeataController {
    @Autowired
    private SeataService seataService;


    @RequestMapping("/testSeata")
    @GlobalTransactional
    public void testSeata() {
        seataService.testSeata();
        int i = 1 / 0;
    }
}
package com.study.seata.service.impl;

import com.study.common.entity.UserDo;
import com.study.common.service.UserService;
import com.study.ordinary.api.TestRemote;
import com.study.seata.service.SeataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service("seataService")
public class SeataServiceImpl implements SeataService {
    @Autowired
    private UserService userService;
    @Resource
    private TestRemote testRemote;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testSeata() {
        UserDo userUpdate = new UserDo();
        userUpdate.setId(1L);
        userUpdate.setUserName("waer");
        userService.updateById(userUpdate);

        UserDo userUpdate1 = new UserDo();
        userUpdate1.setId(2L);
        userUpdate1.setUserName("waer");
        userService.updateById(userUpdate1);

        testRemote.testSeata();
    }
}

简单例子。需要注意的是笔者这里还是使用了本地事务,因为需要保证本地事务,不能因为有全局事务就丢弃了本地事务,此外因为AT模式全局事务不具备隔离性事务过程中的数据会被其他客户端读到,为了避免脏读需要加for update,但是性能不高,所以还是把本地事务加上。

调用方

package com.study.ordinary.api;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "${spring.application.name}",url = "http://localhost:${server.port}")
public interface TestRemote {
    @RequestMapping("/test")
    void testSeata();
}
package com.study.ordinary.controller;

import com.study.ordinary.api.TestRemote;
import com.study.common.entity.TestDo;
import com.study.common.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController implements TestRemote {
    @Autowired
    private TestService testService;

    @Override
    public void testSeata() {
        TestDo testUpdate = new TestDo();
        testUpdate.setId(6L);
        testUpdate.setName("wff");
        testService.updateById(testUpdate);
    }
}

简单的例子,对test表进行插入。

观察下与seata相关的表的内容

seata-服务端

global_table

CREATE TABLE `global_table` (
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `status` tinyint NOT NULL,
  `application_id` varchar(32) DEFAULT NULL,
  `transaction_service_group` varchar(32) DEFAULT NULL,
  `transaction_name` varchar(128) DEFAULT NULL,
  `timeout` int DEFAULT NULL,
  `begin_time` bigint DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xid`),
  KEY `idx_status_gmt_modified` (`status`,`gmt_modified`),
  KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

因为官方没有注释,只能根据例子猜测下。

  • 例子:
xid	transaction_id	status	application_id	transaction_service_group	transaction_name	timeout	begin_time	application_data	gmt_create	gmt_modified
192.168.2.147:8091:6917850988250068680	6917850988250068680	1	java-2022-seata	my_test_tx_group	testSeata()	60000	1667119746694		2022-10-30 08:49:06	2022-10-30 08:49:06
  1. xid:全局事务的唯一标识,用于传播事务。seata服务ip+端口+transaction_id。
  2. transaction_id:事务id。
  3. status:这里的1表示全局事务开始,具体见官方文档
  4. application_id:应用id。
  5. transaction_service_group:事务服务分组。
  6. transaction_name:事务名。这里是被@GlobalTransactional注释的方法。
  7. timeout:事务超时时间,可以使@GlobalTransactional.timeoutMills指定。

branch_table

CREATE TABLE `branch_table` (
  `branch_id` bigint NOT NULL,
  `xid` varchar(128) NOT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `resource_group_id` varchar(32) DEFAULT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `branch_type` varchar(8) DEFAULT NULL,
  `status` tinyint DEFAULT NULL,
  `client_id` varchar(64) DEFAULT NULL,
  `application_data` varchar(2000) DEFAULT NULL,
  `gmt_create` datetime(6) DEFAULT NULL,
  `gmt_modified` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
  • 例子
branch_id	xid	transaction_id	resource_group_id	resource_id	branch_type	status	client_id	application_data	gmt_create	gmt_modified
6917850988250068682	192.168.2.147:8091:6917850988250068680	6917850988250068680		jdbc:mysql://localhost:3306/my_new_test	AT	0	java-2022-ordianry:127.0.0.1:62247	{"autoCommit":false}	2022-10-30 08:49:08.727008	2022-10-30 08:49:08.727008
6917850988250068684	192.168.2.147:8091:6917850988250068680	6917850988250068680		jdbc:mysql://localhost:3306/my_test	AT	0	java-2022-seata:127.0.0.1:62411	{"autoCommit":false}	2022-10-30 08:49:08.808038	2022-10-30 08:49:08.808038
  1. branch_id:分支id
  2. xid:全局事务的唯一标识,用于传播事务。seata服务ip+端口+transaction_id。
  3. transaction_id
  4. resource_group_id:资源id
  5. resource_id:mysql url
  6. branch_type:分支事务模式。
  7. status:分支状态。这里为0表示未知。
  8. client_id:客户端id,name+ip+端口。
  9. application_data:应用数据,比如使用了tcc
{
    "actionContext": {
        "action-start-time": 1667651422889,
        "useTCCFence": true,
        "sys::prepare": "testSeata",
        "sys::rollback": "rollback",
        "sys::commit": "commit",
        "host-name": "198.18.0.1",
        "params": {
            "test": "test"
        },
        "actionName": "seataService"
    }
}

lock_table

CREATE TABLE `lock_table` (
  `row_key` varchar(128) NOT NULL,
  `xid` varchar(128) DEFAULT NULL,
  `transaction_id` bigint DEFAULT NULL,
  `branch_id` bigint NOT NULL,
  `resource_id` varchar(256) DEFAULT NULL,
  `table_name` varchar(32) DEFAULT NULL,
  `pk` varchar(36) DEFAULT NULL,
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`row_key`),
  KEY `idx_status` (`status`),
  KEY `idx_branch_id` (`branch_id`),
  KEY `idx_xid_and_branch_id` (`xid`,`branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
  • 例子:
row_key	xid	transaction_id	branch_id	resource_id	table_name	pk	status	gmt_create	gmt_modified
jdbc:mysql://localhost:3306/my_new_test^^^test^^^6	192.168.2.147:8091:6917850988250068680	6917850988250068680	6917850988250068682	jdbc:mysql://localhost:3306/my_new_test	test	6	0	2022-10-30 08:49:08	2022-10-30 08:49:08
jdbc:mysql://localhost:3306/my_test^^^user^^^1	192.168.2.147:8091:6917850988250068680	6917850988250068680	6917850988250068684	jdbc:mysql://localhost:3306/my_test	user	1	0	2022-10-30 08:49:08	2022-10-30 08:49:08
jdbc:mysql://localhost:3306/my_test^^^user^^^2	192.168.2.147:8091:6917850988250068680	6917850988250068680	6917850988250068684	jdbc:mysql://localhost:3306/my_test	user	2	0	2022-10-30 08:49:08	2022-10-30 08:49:08
  1. row_key:行key。这里为mysql url+表名+主键
  2. xid:全局事务的唯一标识,用于传播事务。seata服务ip+端口+transaction_id。

业务-客户端

CREATE TABLE `undo_log` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'increment id',
  `branch_id` bigint NOT NULL COMMENT 'branch transaction id',
  `xid` varchar(100) NOT NULL COMMENT 'global transaction id',
  `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
  `rollback_info` longblob NOT NULL COMMENT 'rollback info',
  `log_status` int NOT NULL COMMENT '0:normal status,1:defense status',
  `log_created` datetime NOT NULL COMMENT 'create datetime',
  `log_modified` datetime NOT NULL COMMENT 'modify datetime',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8mb3 COMMENT='AT transaction mode undo table';

随便看一个就行。

  • 例子:
id	branch_id	xid	context	rollback_info	log_status	log_created	log_modified
26	6917850988250068682	192.168.2.147:8091:6917850988250068680	serializer=jackson&compressorType=NONE	{"@class":"io.seata.rm.datasource.undo.BranchUndoLog","xid":"192.168.2.147:8091:6917850988250068680","branchId":6917850988250068682,"sqlUndoLogs":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.undo.SQLUndoLog","sqlType":"UPDATE","tableName":"test","beforeImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"test","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":-5,"value":["java.math.BigInteger",6]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"name","keyType":"NULL","type":1,"value":"111"}]]}]]},"afterImage":{"@class":"io.seata.rm.datasource.sql.struct.TableRecords","tableName":"test","rows":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Row","fields":["java.util.ArrayList",[{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"id","keyType":"PRIMARY_KEY","type":-5,"value":["java.math.BigInteger",6]},{"@class":"io.seata.rm.datasource.sql.struct.Field","name":"name","keyType":"NULL","type":1,"value":"wff"}]]}]]}}]]}	0	2022-10-30 08:49:09	2022-10-30 08:49:09
  1. branch_id:分支id。
  2. context:内容,包含了一些技术信息。这里为serializer=jackson&compressorType=NONE
  3. rollback_info:回滚信息
{
	"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
	"xid": "192.168.2.147:8091:6917850988250068680",
	"branchId": 6917850988250068682,
	"sqlUndoLogs": ["java.util.ArrayList", [{
		"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
		"sqlType": "UPDATE",
		"tableName": "test",
		"beforeImage": {
			"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
			"tableName": "test",
			"rows": ["java.util.ArrayList", [{
				"@class": "io.seata.rm.datasource.sql.struct.Row",
				"fields": ["java.util.ArrayList", [{
					"@class": "io.seata.rm.datasource.sql.struct.Field",
					"name": "id",
					"keyType": "PRIMARY_KEY",
					"type": -5,
					"value": ["java.math.BigInteger", 6]
				}, {
					"@class": "io.seata.rm.datasource.sql.struct.Field",
					"name": "name",
					"keyType": "NULL",
					"type": 1,
					"value": "111"
				}]]
			}]]
		},
		"afterImage": {
			"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
			"tableName": "test",
			"rows": ["java.util.ArrayList", [{
				"@class": "io.seata.rm.datasource.sql.struct.Row",
				"fields": ["java.util.ArrayList", [{
					"@class": "io.seata.rm.datasource.sql.struct.Field",
					"name": "id",
					"keyType": "PRIMARY_KEY",
					"type": -5,
					"value": ["java.math.BigInteger", 6]
				}, {
					"@class": "io.seata.rm.datasource.sql.struct.Field",
					"name": "name",
					"keyType": "NULL",
					"type": 1,
					"value": "wff"
				}]]
			}]]
		}
	}]]
}

里面有xid、branchId、sqlType(这里为UPDATE)、tableName(这里为test)、beforeImage(记录了提交前的数据状态,只记录会变化的值)、afterImage(记录了提交后的数据状态,只记录会变化的值),利用这些值生成回滚sql。

业务系统集成-TCC

AT分布式事务缺点在《Seata之AT初识》说过,一个比较明显的是基于本地ACID事务的关系型数据库,比如我们常用的插入数据库之后更新缓存,就不能适用,我们自己处理的话就是在catch中写回退逻辑,但是不够优雅,就可以利用tcc。

tcc给笔者的感觉就像编程范式。

package com.study.seata.controller;

import com.study.seata.service.SeataService;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class SeataController {
    @Autowired
    private SeataService seataService;


    @RequestMapping("/testSeata")
    @GlobalTransactional
    public void testSeata() {
        seataService.testSeata(new HashMap<String, String>() {{
            put("test", "test");
        }});
    }
}
package com.study.seata.service;

import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;

import java.util.Map;

@LocalTCC
public interface SeataService {
    @TwoPhaseBusinessAction(name = "seataService", commitMethod = "commit", rollbackMethod = "rollback")
    void testSeata(@BusinessActionContextParameter(paramName = "params") Map<String, String> params);

    void commit(BusinessActionContext context);

    void rollback(BusinessActionContext context);
}
package com.study.seata.service.impl;

import com.alibaba.fastjson.JSON;
import com.study.common.entity.UserDo;
import com.study.common.service.UserService;
import com.study.common.api.TestRemote;
import com.study.seata.service.SeataService;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Map;

@Service("seataService")
public class SeataServiceImpl implements SeataService {
    @Autowired
    private UserService userService;
    @Resource
    private TestRemote testRemote;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testSeata(Map<String, String> params) {
        UserDo userUpdate = new UserDo();
        userUpdate.setId(1L);
        userUpdate.setUserName("waer");
        userService.updateById(userUpdate);

        UserDo userUpdate1 = new UserDo();
        userUpdate1.setId(2L);
        userUpdate1.setUserName("waer");
        userService.updateById(userUpdate1);

        testRemote.test();

        stringRedisTemplate.opsForValue().set("test", "1");
        int i = 1 / 0;
    }

    @Override
    public void commit(BusinessActionContext context) {
        System.out.println("ddd:" + JSON.toJSONString(context));
    }

    @Override
    public void rollback(BusinessActionContext context) {
        stringRedisTemplate.delete("test");
    }
}

这里其实就是AT+TCC,利用tcc做AT干不了的事情。

来源

部署指南