seata分布式事务
注意: nacos版本与seata版本一定要匹配,否则会有很多莫名其妙的错误
docker-compose up -d
docker-compose down
本文选择的版本 nacos2.0.3、seata选择的版本 在docker-compose.yaml已经写明
1、在home目录下新建 seata目录
编写 docker-compose.yaml
version: "3.1"
services:
seata-server:
image: seataio/seata-server:1.4.2
hostname: seata-server
ports:
- "8091:8091"
environment:
- SEATA_PORT=8091
- SEATA_CONFIG_NAME=file:/root/seata-config/registry
- SEATA_IP=192.168.1.8
volumes:
# 需要把file.conf和registry.conf都放到./seata-server/config文件夹中
- "/home//seata/config:/root/seata-config"
2、在seata目录新建 config目录
新建2个配置
file.conf、registry.conf
file.conf配置如下:
store {
## store mode: file、db、redis
mode = "db"
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
driverClassName = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://192.168.1.8:3306/seata?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"
user = "root"
password = "wangjing"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
}
registry.conf
registry {
type = "nacos"
nacos {
# seata服务注册在nacos上的别名,客户端通过该别名调用服务
application = "seata-server"
# 请根据实际生产环境配置nacos服务的ip和端口
serverAddr = "192.168.1.8:8848"
# nacos上指定的namespace
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "file"
file{
name = "file:/home/seata/config/file.conf"
}
}
3、数据库脚本
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,
`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(6) DEFAULT NULL,
`gmt_modified` datetime(6) DEFAULT NULL,
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `distributed_lock` (
`lock_key` char(20) NOT NULL,
`lock_value` varchar(20) NOT NULL,
`expire` bigint(20) DEFAULT NULL,
PRIMARY KEY (`lock_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
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_status_gmt_modified` (`status`,`gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `lock_table` (
`row_key` varchar(128) NOT NULL,
`xid` varchar(128) DEFAULT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`branch_id` bigint(20) NOT NULL,
`resource_id` varchar(256) DEFAULT NULL,
`table_name` varchar(32) DEFAULT NULL,
`pk` varchar(36) DEFAULT NULL,
`status` tinyint(4) 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` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:每个参与分布式事务的数据库 都需要加入 undo_log表
CREATE TABLE `undo_log` (
`branch_id` bigint(20) 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 DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';
服务端的seata-server到此结束,此时在nacos中,应该就能查看到seata-server已经注册到nacos里面了
4、接下来 就是在项目中如何集成
首先肯定需要引入相关包。 需要进行分布式事务控制的项目 都需要引用
<!-- Seata -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>${seata.version}</version> <!-- 该版本在主项目中定义的为1.4.2 -->
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2021.0.1.0</version> <!-- 注意服务端用的1.4.2 然后2021.0.1.0是能匹配到1.4.2版本的 -->
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
5、接下来 就是yml的配置了
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: sas_group #事务组名称,需要进行同一组事务控制,就需要一样的配置,如果A项目用了 a_group B项目需要加入到事务中,也用a_group
service:
vgroup-mapping:
sas_group: default
enable-degrade: false # 是否启用降级
disable-global-transaction: false # 是否禁用全局事务
config:
type: file # 配置中心为file模式
registry:
type: nacos
nacos:
server-addr: 192.168.1.8:8848
application: seata-server
username: nacos
password: nacos
namespace:
group: DEFAULT_GROUP # 此处需要看你的nacos注册在那个分组,此处我踩了坑,默认的分组也需要写,我没写。
enable-auto-data-source-proxy: true
use-jdk-proxy: false
6、启动类 注解
一个是远程调用,一个是事务控制,已添加的忽视
@EnableDiscoveryClient
@EnableTransactionManagement
7、代码块使用
@GlobalTransactional 即为开启分布式事务。
在此可以打印出 事务id ,在调用oaApiClient的时候 也同样打印,如果发现xid为null,表示开启事务是不成功的。如果xid有值,表示开启分布式事务id是成功了。但是在oaApiClient里面xid与开启的xid不一致 表示oaApiClient里面的事务是没有加入到分布式事务中,因此就需要看一下配置了
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public void test1(String type) throws BizException{
System.out.println("事务id---------------------->" + RootContext.getXID());
JSONObject resModel = oaApiClient.saveMessage();
log.info(resModel.toString());
if("0".equals(type)){
// 远程调用成功 也回滚
throw new BizException(ResponseErrorEnum.OPERATION_FAILED);
}
LedgerResult ledgerResult = new LedgerResult();
ledgerResult.setDetails("测试");
ledgerResultService.save(ledgerResult);
// 等于1则 两边执行完了 再回滚
if("1".equals(type)){
throw new BizException(ResponseErrorEnum.OPERATION_FAILED);
}
}
oaApiClient.saveMessage的代码片段,注意 也要开启自身事务@Transactional
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveMessage() throws Exception {
System.out.println("事务id---------------------->" + RootContext.getXID());
MessageInfo m = new MessageInfo();
m.setTitle("分布式测试1");
m.setContext("分布式测试1");
m.setSendUid(1L);
m.setType(15);
messageInfoService.saveMessageInfoTest(m);
return true;
}
到此seata的部署,并且与spring cloud的集成已经完成。
至于理论知识,可以看官网。以及不同类型事务模式