这几天在使用微服务进行开发,搭建架构时需要集成Seata分布式事务管理,中间遇到了一些坑,决定写这篇文章以后集成Seata时可以快速解决问题
1.Seata简介
1.1Seata是什么
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。
1.2AT模式
- 提供无侵入自动补偿的事务模式,目前已支持 MySQL、 Oracle 、PostgreSQL和 TiDB的AT模式,H2 开发中
1.3TCC模式
- 支持 TCC 模式并可与 AT 混用,灵活度更高
1.4SAGA模式
- 为长事务提供有效的解决方案
1.5XA模式
- 似乎正在研发中
- 总而言之,大部分情况下我们需要了解的是AT模式
2.Seata的下载与运行
2.1下载
- 直接官网下载:https://seata.io/zh-cn/index.html
- windows下载zip包,linux下载tar.gz即可,我使用的是1.4.0版本的
2.2运行
- 将下载好的压缩包解压
- 解压后直接执行bin下面的seata-server.sh(windows双击seata-server.bat)即可运行seata,默认端口是8091
3.Nacos集成
3.1添加Seata配置到Nacos中
- 基本配置配置如下,具体配置请查阅官方文档
#这里的demo-system-group表示当前的组,用户可以自己定义组名如lat-system-group也可,但是在后面配置文件中必须对应,见5.注意点详解
service.vgroupMapping.demo-system-group=default
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/ry-seata?useUnicode=true
store.db.user=root
store.db.password=password
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
#下面这条配置修改了写日志时序列化数据的方式,如果不修改序列化方式可能出现意外的错误,见5.注意点
client.undo.logSerialization=kryo
- 注意这里的配置每行都对应一个Data Id,配置完成后Nacos配置文件应如下图(部分配置)
3.2将Seata注册到Nacos中
- 打开解压后的压缩包在config文件夹下,编辑修改registry.conf文件,本人如下(注册中心和配置中心都选择nacos)
registry {
# file 注册信息在指定文件下
# nacos 注册到nacos注册中心
# eureka注册到eureka注册中心
# redis 注册到redis作为注册中心
# zk 注册到zk注册中心
# consul注册到consul注册中心
# etcd3 注册到etcd3注册中心
# sofa 注册到sofa注册中心
type = "nacos"
loadBalance = "RandomLoadBalance"
loadBalanceVirtualNodes = 10
nacos {
application = "seata-server"
serverAddr = "192.168.100.201:8849"
group = "SEATA_GROUP"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
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 = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
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 {
# 同上,选择一个配置中心
type = "nacos"
nacos {
serverAddr = "192.168.100.201:8849"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
appId = "seata-server"
apolloMeta = "http://192.168.1.204:8801"
namespace = "application"
apolloAccesskeySecret = ""
}
zk {
serverAddr = "127.0.0.1:2181"
sessionTimeout = 6000
connectTimeout = 2000
username = ""
password = ""
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}
3.3添加Seata数据库
- 微服务大多是分库的,这里给Seata一个独立的数据库方便Seata持久化,建表语句如下
-- -------------------------------- 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_gmt_modified_status` (`gmt_modified`, `status`),
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(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- 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(20) 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(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';
- 必须注意的是undo_log表,这张表需要在每个需要用到全局事务的业务库中都有一份,每个事务执行过程中都会向当前数据源的这张表写入数据
3.4重启Seata,Seata服务被注册到Nacos服务列表中
4.使用Seata做事务管理
4.1添加依赖
- 这里直接集成SpringCloud-Alibaba的启动器就可以,本人用的2021.1版本,注意是所有需要全局事务管理的微服务都需要添加该依赖,建议在父工程中进行统一版本管理
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
4.2修改业务配置文件
- 在配置文件中进行多数据源动态配置的时候需要开启Seata配置开关(必须),另外需要配置Seata参数,本人样例配置如下
# spring配置
spring:
datasource:
druid:
stat-view-servlet:
enabled: true
loginUsername: admin
loginPassword: 123456
dynamic:
druid:
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,slf4j
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
datasource:
# 主库数据源
master:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.100.201:3306/demo-cloud?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: ***
password: ***
# 从库数据源
# slave:
# username:
# password:
# url:
# driver-class-name:
seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭
# seata配置
seata:
# 默认关闭,如需启用spring.datasource.dynami.seata需要同时开启
enabled: true
# Seata 应用编号,默认为 ${spring.application.name}
application-id: ${spring.application.name}
# Seata 事务组编号,用于 TC 集群名,需要注意与前面Nacos配置一致,否则无法拉取到Seata服务,见5.注意点
tx-service-group: demo-system-group
# 关闭自动代理
enable-auto-data-source-proxy: false
# 服务配置项
service:
# 虚拟组和分组的映射
vgroup-mapping:
ruoyi-system-group: default
config:
type: nacos
nacos:
serverAddr: 192.168.100.201:8849
group: SEATA_GROUP
namespace:
registry:
type: nacos
nacos:
application: seata-server
server-addr: 192.168.100.201:8849
namespace:
4.3开启事务注解
- 每个事务的开始使用@GlobalTransactional注解开启全局事务
- 每个分支事务使用@Transaction注解开启当前服务事务
- 每个多数据源的服务必须指定当前当前数据源
5.注意点
5.1 业务服务启动报错,连接不上服务 服务报错 no available service ‘null‘ found, please make sure registry config correct
- 错误关键字
no available service ‘null‘ found, please make sure registry config correct
- 原因:配置seata分组与业务中指定分组不一致,找不到指定分组的seata服务
- 解决方法:Nacos配置中的service.vgroupMapping.demo-system-group,其最后一段(demo-system-group)与业务服务配置的seata.service.vgroup.demo-system-group的最后一段(demo-system-group)必须对应
5.2 nacos集成分布式事务插件Seata的序列化问题
- 错误关键字
json encode exception, Type id handling not implemented for type java.lang.Object (by serializer of type com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer)
- 实际上是Seata本身存在bug
- 解决方法:在Nacos配置列表中添加client.undo.logSerialization=kryo
5.3获取Seata服务失败
- 错误关键字
no available service 'default' found, please make sure registry config correct
- 其他服务拉取Seata事务控制服务失败
- 检查Seata是否启动成功,配置文件是否配置正确,如5.1的问题
5.4Seata后台运行
- cd到Seata的bin文件夹下
- nohup ./seata-server.sh 2>1 &