产生背景的原因
当架构从单体演变为多服务时,整个系统的可靠性变得难以控制。在单个服务中,从请求到响应结果,请求的整个周期都在服务器上,当地事务可以确保一组数据操作的一致性。
在微服务中,从请求到响应,多个服务器和数据库可能会跨越,当地事务不能保证事务的一致性。
事故不安全案件在微服务中,从请求到响应,可以跨越多个服务器和数据库。如下图所示,假设有一个金融系统,并将其分为多个微服务。每个微服务都有自己的数据库。我们现在需要使用以下微服务来启动贷款操作:
本贷款操作可抽象概括为以下步骤:(理论简化版)
- 用户发起贷款,调用贷款服务的贷款界面;
- 贷款同时,在信贷服务中 减少信用额度;
- 同时,在资金服务中借款 增加账户余额;
- 同时,在日志服务中借款 增加流水记录;
每一项服务都是单独部署的,理想情况下,上述操作都能顺利实施。
若某项服务在执行过程中出现异常:
假设一个常见的场景,资金服务无法正常处理要求,那么这个链接就变成了以下几个方面:
- 用户发起贷款,调用贷款服务的贷款界面;
- 贷款同时,在信贷服务中 减少信用额度;
- 同时,在资金服务中借款 增加账户余额;
- 同时,在日志服务中借款 增加流水记录;
本地Transaction在多项服务中已经无法应对这种情况,现在系列操作导致了上述情况,用户信用额度减少,流水也被记录下来,但用户没有收到钱;
Seata简介Seata是一种开源分布式事务解决方案,致力于提供高性能、简单易用的分布式事务服务。Seata将为用户提供ATA、TCC、SAGA和XA事务模型为用户创建一站式分布式解决方案。
https://seata.io/zh-cn/docs/overview/what-is-seata.html
目前使用的流行情况如下:AT > TCC > Saga
处理流程- Transaction ID XID:全局唯一的事务ID
- 3组件概念
- Transaction Coordinator(TC) 事务协调员:维护全局和分支事务状态,推动全局事务提交或回滚。
- Transaction Manager(TM) 事务管理器:定义全局事务范围:开始全局事务,提交或回滚全局事务。
- Resource Manager(RM) 资源管理器:管理分支事务处理的资源,与TC交谈,注册分支事务,报告分支事务状态,推动分支事务提交或回滚。
- TM开始分布式事务(TM向TC注册全局事务记录);
- RM向TC报告资源准备状态;
- TM结束分布式事务(TM通知TC提交/回滚分布式事务);
- TC汇总事务信息,决定分布式事务是提交还是回滚;
- TC通知所有RM提交/回滚资源,事务二阶段结束。
自动化分支事务。无入侵分布式事务解决方案。在AT的触摸下,用户只需关注自己的“业务SQL”,用户的“业务SQL”是第一阶段,Seata 提交和回滚操作将自动生成事务的第二阶段。
整体机制协议在两个阶段提交的演变:
- 第一阶段:在同一本地事务中提交业务数据和回滚日志记录,释放本地锁和连接资源,Seata服务端只保留一个全局锁。
- 二阶段:
- 提交异步化(异步删除undo log),完成非常快。
- 回滚通过一阶段的回滚日志进行反向补偿。
与2PC最大的区别在于事务已在第一阶段提交,然后通过反向补偿进行回滚。
一阶段DML操作时,将回滚日志生成并插入UNDO_LOG表。单机模式、全局事务会话信息内存阅读和写作,并持续本地文件 root.data,性能较高。
- db 模式:适合集群通过模式,全局事务会话信息通过 db 共享,相对性能差。
我们采用db模式的方法+nacos+seata+ 服务集成;
windows安装在这里;
下载地址https://seata.io/zh-cn/blog/download.html
创建seata-server解压文件 Nacos创建命名空间 修改配置文件这里我们使用nacos集成seata-server作为配置中心,直接删除file.conf文件;
修改redistry.confnacos注册配置中心的设置如下:
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa # 使用注册中心的类型是nacos type = "nacos" # nacos相关配置 nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "SEATA_GROUP" namespace = “0b26125a-4557-4ec5-8afb-853144e126f” 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" aclToken = "" } 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 { # file、nacos 、apollo、zk、consul、etcd3 # nacoso设置配置中心的类型 type = "nacos" # nacos相关配置 nacos { serverAddr = "127.0.0.1:8848" namespace = “0b26125a-4557-4ec5-8afb-853144e126f” group = "SEATA_GROUP" username = "nacos" password = "nacos" } consul { serverAddr = "127.0.0.1:8500" aclToken = "" } apollo { appId = "seata-server" ## apolloConfigService will cover apolloMeta apolloMeta = "http://192.168.1.204:8801" apolloConfigService = "http://192.168.1.204:8080" namespace = "application" apolloAccesskeySecret = "" cluster = "seata" } zk { serverAddr = "127.0.0.1:2181" sessionTimeout = 6000 connectTimeout = 2000 username = "" password = "" nodePath = "/seata/seata.properties" } etcd3 { serverAddr = "http://localhost:2379" } file { name = "file.conf" }}
将配置文件导入nacossnacos
没有配置文件的新版本seata需要手动下载
seata配置文件
- config.txt下载完成后,放在bin同级目录下;
- nacos将nacos下的nacos-config.sh放在/conf目录下;
- 修改config.txt配置文件信息;
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html#Transport configuration, for client and servertransport.type=TCPtransport.server=NIOtransport.heartbeat=truetransport.enableTmClientBatchSendRequest=falsetransport.enableRmClientBatchSendRequest=truetransport.enableTcServerBatchSendResponse=falsetransport.rpcRmRequestTimeout=30000transport.rpcTmRequestTimeout=30000transport.rpcTcRequestTimeout=30000transport.threadFactory.bossThreadPrefix=NettyBosstransport.threadFactory.workerThreadPrefix=NettyServerNIOWorkertransport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandlertransport.threadFactory.shareBossWorker=falsetransport.threadFactory.clientSelectorThreadPrefix=NettyClientSelectortransport.threadFactory.clientSelectorThreadSize=porttransport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThreadtransport.threadFactory.bossThreadSize=porttransport.threadFactory.workerThreadSize=defaulttransport.shutdown.wait=3transport.serialization=seatatransport.compressor=none#Transaction routing rules configuration, only for the clientservice.vgroupMapping.default_tx_group=default#If you use a registry, you can ignore itservice.default.grouplist=127.0.0.1:8091service.enableDegrade=falseservice.disableGlobalTransaction=false#Transaction rule configuration, only for the clientclient.rm.asyncCommitBufferLimit=10000client.rm.lock.retryInterval=10client.rm.lock.retryTimes=30client.rm.lock.retryPolicyBranchRollbackOnConflict=trueclient.rm.reportRetryCount=5client.rm.tableMetaCheckEnable=trueclient.rm.tableMetaCheckerInterval=60000client.rm.sqlParserType=druidclient.rm.reportSuccessEnable=falseclient.rm.sagaBranchRegisterEnable=falseclient.rm.sagaJsonParser=fastjsonclient.rm.tccActionInterceptorOrder=-21448264.tm.commitRetryCount=5client.tm.rollbackRetryCount=5client.tm.defaultGlobalTransactionTimeout=60000client.tm.degradeCheck=falseclient.tm.degradeCheckAllowTimes=10client.tm.degradeCheckPeriod=2000client.tm.interceptorOrder=-21448264.undo.dataValidation=trueclient.undo.logSerialization=jacksonclient.undo.onlyCareUpdateColumns=trueserver.undo.logSaveDays=7server.undo.logDeletePeriod=860000client.undo.logTable=undo_logclient.undo.compress.enable=trueclient.undo.compress.type=zipclient.undo.compress.threshold=64k#For TCC transaction modetcc.fence.logTableName=tcc_fence_logtcc.fence.cleanPeriod=1h#Log rule configuration, for client and serverlog.exceptionRate=100#Transaction storage configuration, only for the server. The file, DB, and redis configuration values are optional.# 存储模式改为dbstore.mode=dbstore.lock.mode=filestore.session.mode=file#Used for password encryptionstore.publicKey=#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.store.file.dir=file_store/datastore.file.maxBranchSessionSize=16384store.file.maxGlobalSessionSize=512store.file.fileWriteBufferCacheSize=16384store.file.flushDiskMode=asyncstore.file.sessionReloadReadSize=100#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.store.db.datasource=druidstore.db.dbType=mysqlstore.db.driverClassName=com.mysql.jdbc.Driver# 配置数据库信息store.db.url=jdbc:mysql://zhangbh:3306/seata?useUnicode=true&rewriteBatchedStatements=true# Store数据库账号.db.user=root# 数据库密码store.db.password=rootstore.db.minConn=5store.db.maxConn=30store.db.globalTable=global_tablestore.db.branchTable=branch_tablestore.db.distributedLockTable=distributed_lockstore.db.queryLimit=100store.db.lockTable=lock_tablestore.db.maxWait=5000#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.store.redis.mode=singlestore.redis.single.host=127.0.0.1store.redis.single.port=6379store.redis.sentinel.masterName=store.redis.sentinel.sentinelHosts=store.redis.maxConn=10store.redis.minConn=1store.redis.maxTotal=100store.redis.database=0store.redis.password=store.redis.queryLimit=100#Transaction rule configuration, only for the serverserver.recovery.committingRetryPeriod=1000server.recovery.asynCommittingRetryPeriod=1000server.recovery.rollbackingRetryPeriod=1000server.recovery.timeoutRetryPeriod=1000server.maxCommitRetryTimeout=-1server.maxRollbackRetryTimeout=-1server.rollbackRetryTimeoutUnlockEnable=falseserver.distributedLockExpireTime=10000server.xaerNotaRetryTimeout=60000server.session.branchAsyncQueueSize=5000server.session.enableBranchAsyncRemove=false#Metrics configuration, only for the servermetrics.enabled=falsemetrics.registryType=compactmetrics.exporterList=prometheusmetrics.exporterPrometheusPort=9898
4.将配置文件导入nacos执行脚本
sh nacos-config.sh -h localhost -p 8848 -g SEATA_GROUP -t 0b26125a-4557-4ec5-8afb-8534 -u nacos -w nacos
执行成功后如图所示:
创建数据库1.存储全局事务信息,创建数据库seata;
https://github.com/seata/seata/tree/develop/script/server/db-- -------------------------------- The script used when storeMode is 'db' ---------------------------------- the table to store GlobalSession dataCREATE 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_status_gmt_modified` (`status` , `gmt_modified`), KEY `idx_transaction_id` (`transaction_id`)) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store BranchSession dataCREATE 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 = InnoDBDEFAULT CHARSET = utf8mb4;-- the table to store lock dataCREATE TABLE IF NOT EXISTS `lock_table`( `row_key` VARCHAR(128) NOT NULL, `xid` VARCHAR(128), `transaction_id` BIGINT, `branch_id` BIGINT NOT NULL, `resource_id` VARCHAR(256), `table_name` VARCHAR(32), `pk` VARCHAR(36), `status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking', `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`row_key`), KEY `idx_status` (`status`), KEY `idx_branch_id` (`branch_id`), KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;CREATE TABLE IF NOT EXISTS `distributed_lock`( `lock_key` CHAR(20) NOT NULL, `lock_value` VARCHAR(20) NOT NULL, `expire` BIGINT, primary key (`lock_key`)) ENGINE = InnoDBDEFAULT CHARSET = utf8mb4;INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
2.在每个业务库中创建undo_log表,用于存储本地事务回滚信息
CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
启动服务
/bin/seata-server.bat
集成Client使用-项目配置pom依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-seata</artifactId> <version>2021.1</version> <exclusions> <exclusion> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <!--seata starter 采用1.4.2版本--> <dependency> <groupId>io.seata</groupId> <artifactId>seata-spring-boot-starter</artifactId> <version>${seata.version}</version> </dependency>
yml配置
# Seata 配置项,对应 SeataProperties 类seata: enabled: true # Seata 应用编号,默认为 ${spring.application.name} application-id: ${spring.application.name} # Seata 事务组编号,用于 TC 集群名 tx-service-group: ${spring.application.name}-group # 数据源代理是否自动打开? enable-auto-data-source-proxy: true # 数据源代理模式,使用AT模式 data-source-proxy-mode: AT # 事务组,配置项值为TC集群名,需要与服务端保持一致 service: # 虚拟组和分组的映射 vgroup-mapping: account-service-group: default # 全局事务开关 disable-global-transaction: true # nacos配置中心集成 config: type: nacos nacos: server-addr: localhost group: SEATA_GROUP namespace: 0b26125a-4557-4ec5-8afb-8534 username: nacos password: nacos # nacos注册中心整合 registry: type: nacos nacos: server-addr: localhost group: SEATA_GROUP namespace: 0b26125a-4557-4ec5-8afb-8534 username: nacos password: nacos
nacos配置
在相应的yml配置中添加serviceee.vgroup-mapping.account-service-group,值为default
因为service最终通过事务组的映射配置获得.default.grouplist配置项,获得真实的TC服务地址。
使用业务代码@Globaltransactional即可,如图所示: