seata是干什么的?
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。本文主要介绍seata中AT模式的原理以及使用方式。
seata中关键角色
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
整体机制
seata是对两阶段提交协议的演变:
-
一阶段:业务数据和回滚日志(undo_log)记录在同一个本地事务中提交,释放本地锁和连接资源。
-
二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
运行流程示意图如下(假设我们的业务系统有四个服务,现在模拟一次购物行为,business:聚合业务层,account:扣费,storage:减库存,order:存订单):
运行流程
- 在business模块中使用@GlobalTransactional注解,TM向TC发起全局事物,生成XID(全局锁),分别调用account、storage、order模块;
- 在account模块中进行扣费逻辑处理,这时进行写表操作,undo_log记录回滚日志,通知TC操作结果;
- 在storage模块中进行减库存逻辑处理,这时进行写表操作,undo_log记录回滚日志,通知TC操作结果;
- 在order模块中进行保存订单逻辑处理,这时进行写表操作,undo_log记录回滚日志,通知TC操作结果;
- 正常情况:business调用其他模块全部成功,TM通知TC全部提交,TC通知所有RM提交成功,删除本地undo_log;
- 异常情况:business调用其他模块出现异常,TM通知TC全局rollback,TC通知所有RM进行回滚,根据undo_log进行反向操作,还原数据,最后删除undo_log;
实战操作
-
环境说明
macOS Mojave 10.14.6 spring-cloud-alibaba 2.2.1.RELEASE spring-cloud-starter-alibaba-seata
2.2.1.RELEASE seata-spring-boot-starter 1.4.0 seata 1.4.0 nacos 1.3.1 mysql 5.7.17-log - 搭建部署TC(seata-server)
修改配置store { ## store mode: file、db、redis mode = "db" ## file store property file { ## store location dir dir = "sessionStore" # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions maxBranchSessionSize = 16384 # globe session size , if exceeded throws exceptions maxGlobalSessionSize = 512 # file buffer size , if exceeded allocate new buffer fileWriteBufferCacheSize = 16384 # when recover batch read size sessionReloadReadSize = 100 # async, sync flushDiskMode = async } ## 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://127.0.0.1:3306/seata" user = "root" password = "123456" minConn = 5 maxConn = 100 globalTable = "global_table" branchTable = "branch_table" lockTable = "lock_table" queryLimit = 100 maxWait = 5000 } ## redis store property redis { host = "127.0.0.1" port = "6379" password = "" database = "0" minConn = 1 maxConn = 10 maxTotal = 100 queryLimit = 100 } }
sql初始化,先创建数据库seata,再执行建表sql
-- 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 = utf8; -- 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 = utf8; -- 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 = utf8;
更改config.txt(https://github.com/seata/seata/tree/v1.4.0),并将其上传至nacos配置中心,其中需要修改的是store.mode、store.db.url、store.db.user、store.db.password,注意service.default.grouplist需要改成当前seata-server所在服务器的ip地址,service.vgroupMapping.my_test_tx_group中的my_test_tx_group为事务分组的名称要与微服务中配置tx-service-group的值一致,后面会说到这个配置;
上传配置至nacos(在seata-1.4.0/script/config-center/nacos目录下):./nacos-config.sh -h 10.0.0.251
启动seata-server并指定本机IP:./seata-server.sh -h 10.0.0.251# seata-1.4.0/script/config-center/config.txt transport.type=TCP transport.server=NIO transport.heartbeat=true transport.enableClientBatchSendRequest=false transport.threadFactory.bossThreadPrefix=NettyBoss transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker transport.threadFactory.serverExecutorThreadPrefix=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 service.vgroupMapping.my_test_tx_group=default service.default.grouplist=127.0.0.1:8091 service.enableDegrade=false service.disableGlobalTransaction=false client.rm.asyncCommitBufferLimit=10000 client.rm.lock.retryInterval=10 client.rm.lock.retryTimes=30 client.rm.lock.retryPolicyBranchRollbackOnConflict=true client.rm.reportRetryCount=5 client.rm.tableMetaCheckEnable=false client.rm.sqlParserType=druid client.rm.reportSuccessEnable=false client.rm.sagaBranchRegisterEnable=false client.tm.commitRetryCount=5 client.tm.rollbackRetryCount=5 client.tm.defaultGlobalTransactionTimeout=60000 client.tm.degradeCheck=false client.tm.degradeCheckAllowTimes=10 client.tm.degradeCheckPeriod=2000 store.mode=db store.file.dir=file_store/data store.file.maxBranchSessionSize=16384 store.file.maxGlobalSessionSize=512 store.file.fileWriteBufferCacheSize=16384 store.file.flushDiskMode=async store.file.sessionReloadReadSize=100 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/seata?useUnicode=true store.db.user=root store.db.password=123456 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 store.redis.host=127.0.0.1 store.redis.port=6379 store.redis.maxConn=10 store.redis.minConn=1 store.redis.database=0 store.redis.password=null store.redis.queryLimit=100 server.recovery.committingRetryPeriod=1000 server.recovery.asynCommittingRetryPeriod=1000 server.recovery.rollbackingRetryPeriod=1000 server.recovery.timeoutRetryPeriod=1000 server.maxCommitRetryTimeout=-1 server.maxRollbackRetryTimeout=-1 server.rollbackRetryTimeoutUnlockEnable=false client.undo.dataValidation=true client.undo.logSerialization=jackson client.undo.onlyCareUpdateColumns=true server.undo.logSaveDays=7 server.undo.logDeletePeriod=86400000 client.undo.logTable=undo_log client.log.exceptionRate=100 transport.serialization=seata transport.compressor=none metrics.enabled=false metrics.registryType=compact metrics.exporterList=prometheus metrics.exporterPrometheusPort=9898
- undo_log建表初始化
分别在account、storage、order对应的DB中执行以下sql(由于我这里business服务是聚合服务,没有连接DB,所以不需要执行此sql):CREATE TABLE `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 DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table';
- 搭建部署TM(business)
配置文件(application.yml,这里只有seata相关配置,其他配置大家根据自己的情况配置):
maven依赖,RM的依赖和TM相同(为了匹配seata-server的版本,我这里自己定义了seata-spring-boot-starter的版本也为1.4.0):#分布式事物seata seata: enabled: true application-id: ${spring.application.name} tx-service-group: my_test_tx_group enable-auto-data-source-proxy: true config: type: nacos nacos: server-addr: 10.0.0.251:8848 group: SEATA_GROUP registry: type: nacos nacos: application: seata-server server-addr: 10.0.0.251:8848
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <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.4.0</version> </dependency>
代码入口:
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-seata-example") @Override public void saveOrder(OrderReqVo req) { log.info("开始全局事务,XID = " + RootContext.getXID()); //扣余额 boolean flag = rpcUserBalanceService.decreaseUserBalance(xxxx); if (!flag) { throw new BusinessException("XXXX"); } //减库存 flag = rpcStorageService.decreaseProductStock(xxxx); if (!flag) { throw new BusinessException("XXXX"); } //存订单 flag = rpcOrderService.saveOrder(xxxxx); if (!flag) { throw new BusinessException("XXXX"); } }
- 搭建部署RM(account、storage、order)
配置文件和maven依赖和TM一样,三个微服务模块分别创建对应方法,然后启动服务:########每个微服务需要开启本地事务 # account public boolean decreaseUserBalance(xxxxx) { return 扣款结果; } # storage public boolean decreaseProductStock(xxxxx) { return 减库存结果; } # order public boolean saveOrder(xxxxx) { return 保存订单结果; }
-
实战模拟
模拟正常请求,查看对应数据库的数据是否正常;
模拟出现异常时(比如扣款成功,减库存成功,保存订单失败),查看对应数据库的数据是否正常;
debug查看分布式事务中间过程,undo_log的数据是什么样子的。