Seata分布式事务环境搭建
2022-03-30 21:19:29 704
下载seata服务端
https://github.com/seata/seata/releases
修改registry.conf
这里使用nacos做注册中心和配置中心, 也就不需要服务端的file.conf了
但是使用nacos时, nacos的密码不能有特殊符号, 否则seata可能连接不上
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = "e794b575-4231-4935-8271-145c5840d392"
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = "e794b575-4231-4935-8271-145c5840d392"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
}
seata服务端需要的几个表: https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql
其他的一些相关的脚本https://github.com/seata/seata/tree/develop/script
nacos建立命名空间
新增配置文件
Data ID: seataServer.properties
Group: SEATA_GROUP
seataServer.properties配置内容
### seata
store.mode=db
store.publicKey=
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
store.db.datasource=druid
## mysql/oracle/postgresql/h2/oceanbase etc.
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
## if using mysql to store the data, recommend add rewriteBatchedStatements=true in jdbc connection param
store.db.url=jdbc:mysql://192.168.101.128:3309/seata?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&&serverTimezone=Asia/Shanghai
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=100
store.db.globalTable = global_table
store.db.branchTable = branch_table
store.db.lockTable =lock_table
store.db.queryLimit = 100
store.db.maxWait = 5000
## transport
# tcp udt unix-domain-socket
transport.type=TCP
#NIO NATIVE
transport.server=NIO
#enable heartbeat
transport.heartbeat=true
transport.serialization=seata
transport.compressor=none
transport.threadFactory.bossThreadPrefix = NettyBoss
transport.threadFactory.workerThreadPrefix = NettyServerNIOWorker
transport.threadFactory.serverExecutorThread-prefix = 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
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
单体服务多库事务
SpringBoot项目引入依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
项目配置文件
seata:
application-id: test #这里填你应用的id
service:
grouplist:
# seata-server地址
default: 127.0.0.1:8091
# 分组事务
vgroup-mapping:
global_tx_group: default
enable-degrade: false
disable-global-transaction: false
# 是否开启spring-boot自动装配
enabled: true
# 是否启用数据源 bean 的自动代理
enable-auto-data-source-proxy: true
tx-service-group: global_tx_group
client:
tm:
# 一阶段全局提交结果上报TC重试次数 默认1次,建议大于1
commit-retry-count: 3
# 一阶段全局回滚结果上报TC重试次数 默认1次,建议大于1
rollback-retry-count: 3
rm:
# 是否上报一阶段成功 true、false,从1.1.0版本开始,默认false.true用于保持分支事务生命周期记录完整,false可提高不少性能
report-success-enable: true
# 自动刷新缓存中的表结构 默认false
table-meta-check-enable: true
# 一阶段结果上报TC重试次数
report-retry-count: 5
# 异步提交缓存队列长度 默认10000。 二阶段提交成功,RM异步清理undo队列
async-commit-buffer-limit: 1000
lock:
# 校验或占用全局锁重试间隔 默认10,单位毫秒
retry-interval: 10
# 分支事务与其它全局回滚事务冲突时锁策略 默认true,优先释放本地锁让回滚成功
retry-policy-branch-rollback-on-conflict: true
# 校验或占用全局锁重试次数
retry-times: 30
undo:
# 自定义undo表名 默认undo_log
log-table: seata_undo_log
# 二阶段回滚镜像校验
data-validation: true
# undo log序列化方式
log-serialization: jackson
transport:
type: TCP
server: NIO
heartbeat: true
# client和server通信编解码方式 seata(ByteBuf)、protobuf、kryo、hession、fst,默认seata
serialization: seata
# client和server通信数据压缩方式 none、gzip,默认none
compressor: none
thread-factory:
boss-thread-prefix: NettyBoss
client-worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
client-selector-thread-size: 1
client-selector-thread-prefix: NettyClientWorkerThread
简单使用, 配合dynamic-datasource-spring-boot-starter
使用
@Autowired
StaffMapper staffMapper;
@Override
@GlobalTransactional(rollbackFor = Exception.class)
public void globalTx() {
userService.updateMaster();
userService.updateIndependent();
//模拟异常回滚
int i = 1 / 0;
}
@DS("master")
@Transactional(rollbackFor = Exception.class)
public void updateMaster() {
User user1 = baseDao.selectById(1);
user1.setAge(999);
baseDao.updateById(user1);
User user2 = baseDao.selectById(2);
user2.setAge(999);
baseDao.updateById(user2);
}
@DS("independent")
@Transactional(rollbackFor = Exception.class)
public void updateIndependent() {
Staff staff1 = staffMapper.selectById(1);
staff1.setAge(999);
staffMapper.updateById(staff1);
Staff staff2 = staffMapper.selectById(2);
staff2.setAge(999);
staffMapper.updateById(staff2);
}
可以观察到seata_undo_log中的undo记录
SELECT CAST(rollback_info AS char) FROM seata_undo_log
{
"@class": "io.seata.rm.datasource.undo.BranchUndoLog",
"xid": "192.168.101.1:8091:6593516322371825665",
"branchId": 6593516322371825668,
"sqlUndoLogs": [
"java.util.ArrayList",
[
{
"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
"sqlType": "UPDATE",
"tableName": "staff",
"beforeImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "staff",
"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": 4,
"value": [
"java.lang.Long",
1
]
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "name",
"keyType": "NULL",
"type": 12,
"value": "1"
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "age",
"keyType": "NULL",
"type": 4,
"value": 2
}
]
]
}
]
]
},
"afterImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "staff",
"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": 4,
"value": [
"java.lang.Long",
1
]
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "name",
"keyType": "NULL",
"type": 12,
"value": "1"
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "age",
"keyType": "NULL",
"type": 4,
"value": 999
}
]
]
}
]
]
}
},
{
"@class": "io.seata.rm.datasource.undo.SQLUndoLog",
"sqlType": "UPDATE",
"tableName": "staff",
"beforeImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "staff",
"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": 4,
"value": [
"java.lang.Long",
2
]
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "name",
"keyType": "NULL",
"type": 12,
"value": "2"
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "age",
"keyType": "NULL",
"type": 4,
"value": 3
}
]
]
}
]
]
},
"afterImage": {
"@class": "io.seata.rm.datasource.sql.struct.TableRecords",
"tableName": "staff",
"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": 4,
"value": [
"java.lang.Long",
2
]
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "name",
"keyType": "NULL",
"type": 12,
"value": "2"
},
{
"@class": "io.seata.rm.datasource.sql.struct.Field",
"name": "age",
"keyType": "NULL",
"type": 4,
"value": 999
}
]
]
}
]
]
}
}
]
]
}
可以看到该表内存储了数据操作前和操作后的记录
微服务项目分布式事务
如果是微服务项目, 需要分布式事务支持, 配置如下
引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
配置和单体服务多库事务是一样的, seata.application-id
可以不填, 默认取当前应用id
每个模块都需要配置, 因为seata需要代理数据源
但实际1.4.2版本使用jackson/fastjson序列化Date字段时会失败(https://github.com/seata/seata/issues/3883), 可以替换序列化方式为kryo
需要额外引入依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-serializer-kryo</artifactId>
<version>1.4.2</version>
</dependency>
简易demo
@Autowired
RoleFeignClient roleFeignClient;
@Autowired
StaffFeignClient staffFeignClient;
@GlobalTransactional(rollbackFor = Exception.class)
public RestResult<Boolean> globalTxTest() {
log.info("xid: {}", RootContext.getXID());
roleFeignClient.updateRole();
staffFeignClient.updateUser();
int i = 1 / 0;
return RestResult.ok();
}
@Override
@Transactional(rollbackFor = Exception.class)
public RestResult<Boolean> updateRole() {
log.info("xid: {}", RootContext.getXID());
RolePO role1 = roleDAO.selectById(1);
role1.setName("1111111111");
roleDAO.updateById(role1);
RolePO role2 = roleDAO.selectById(2);
role2.setName("2222222");
roleDAO.updateById(role2);
return RestResult.ok();
}
@Override
@Transactional(rollbackFor = Exception.class)
public RestResult<Boolean> updateUser() {
log.info("xid: {}", RootContext.getXID());
StaffPO staff1 = baseMapper.selectById(1);
staff1.setAge(999);
baseMapper.updateById(staff1);
StaffPO staff2 = baseMapper.selectById(2);
staff2.setAge(999);
baseMapper.updateById(staff2);
return RestResult.ok();
}