赞
踩
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。本文主要介绍seata中AT模式的原理以及使用方式。
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
seata是对两阶段提交协议的演变:
一阶段:业务数据和回滚日志(undo_log)记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
运行流程示意图如下(假设我们的业务系统有四个服务,现在模拟一次购物行为,business:聚合业务层,account:扣费,storage:减库存,order:存订单):
环境说明
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 |
- 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

- 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';
- #分布式事物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

maven依赖,RM的依赖和TM相同(为了匹配seata-server的版本,我这里自己定义了seata-spring-boot-starter的版本也为1.4.0): - <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");
- }
- }

- ########每个微服务需要开启本地事务
- # account
- public boolean decreaseUserBalance(xxxxx) {
- return 扣款结果;
- }
- # storage
- public boolean decreaseProductStock(xxxxx) {
- return 减库存结果;
- }
- # order
- public boolean saveOrder(xxxxx) {
- return 保存订单结果;
- }
实战模拟
模拟正常请求,查看对应数据库的数据是否正常;
模拟出现异常时(比如扣款成功,减库存成功,保存订单失败),查看对应数据库的数据是否正常;
debug查看分布式事务中间过程,undo_log的数据是什么样子的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。