下载seata服务端
https://github.com/seata/seata/releases
修改registry.conf
这里使用nacos做注册中心和配置中心, 也就不需要服务端的file.conf了
但是使用nacos时, nacos的密码不能有特殊符号, 否则seata可能连接不上(1.5.0已修复)
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(); }