分布式事务:seata
Seata介绍
Seata的设计目标是对业务无侵入,因此从业务无侵入的2PC方案着手,在传统2PC的基础上演进。
它把一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。
此外,通常分支事务本身就是一个关系数据库的本地事务。
Seata主要由三个重要组件组成:
-
TC:Transaction Coordinator 事务协调器,管理全局的分支事务的状态,用于全局性事务的提交和回滚。
-
TM:Transaction Manager 事务管理器,用于开启、提交或者回滚全局事务。
-
RM:Resource Manager 资源管理器,用于分支事务上的资源管理,向TC注册分支事务,上报分支事务的状态,接受TC的命令来提交或者回滚分支事务。

Seata的执行流程如下:
-
A服务的TM向TC申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID
-
A服务的RM向TC注册分支事务,并及其纳入XID对应全局事务的管辖
-
A服务执行分支事务,向数据库做操作
-
A服务开始远程调用B服务,此时XID会在微服务的调用链上传播
-
B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖
-
B服务执行分支事务,向数据库做操作
-
全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚
-
TC协调其管辖之下的所有分支事务, 决定是否回滚
Seata实现2PC与传统2PC的差别:
-
架构层次方面,传统2PC方案的 RM 实际上是在数据库层,RM本质上就是数据库自身,通过XA协议实现,而 Seata的RM是以jar包的形式作为中间件层部署在应用程序这一侧的。
-
两阶段提交方面,传统2PC无论第二阶段的决议是commit还是rollback,事务性资源的锁都要保持到Phase2完成才释放。而Seata的做法是在Phase1 就将本地事务提交,这样就可以省去Phase2持锁的时间,整体提高效率
写隔离
-
一阶段 本地事务 提交前,需要确保先拿到 全局锁 。
-
拿不到 全局锁 ,不能提交本地事务,拿到全局锁,提交本地事务并插入undo_log记录。
-
拿 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并根据undo_log记录回滚本地事务,释放本地锁。
示例:
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。
tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
tx1 完成阶段 全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
如果 tx1 的 完成阶段 全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。
读隔离
- 在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)。
在 全局事务 提交之前,本地事务 会先提交,这时候查询数据,对于本地库是 Read Committed,对于全局来说是 Read Uncommitted。
- 如果要求全局的读已提交,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。
服务端
一.准备工作
下载Seata服务端包:https://github.com/seata/seata/releases;本例使用seata-1.4.0。
下载nacos服务端包:https://github.com/alibaba/nacos/releases;本例使用nacos-1.4.0。
(Window环境需要python环境,下载安装配置Python环境:https://www.python.org)。
二.修改配置
Seata解压,找到对应的conf文件夹

本例使用db模式,因此可以删除file.conf文件;
打开文件README-zh.md,可以找到 server所需SQL和部署脚本地址 (https://github.com/seata/seata/tree/develop/script/server);
将 config.txt 和 nacos-config.py 拷贝到conf 文件夹下;
新建数据库 seata , 将 SQL脚本导入执行,会生成三张表:global_table、branch_table、lock_table;
修改 registry.conf 配置文件:本例使用nacos,所以只保留 nacos 配置即可,需注意 namespace。
registry { # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa type = "nacos" loadBalance = "RandomLoadBalance" loadBalanceVirtualNodes = 10 nacos { application = "seata-server" serverAddr = "127.0.0.1:8848" group = "DEFAULT_GROUP" namespace = "1344cd38-7919-4482-9c6f-11d73ab865b2" cluster = "default" username = "" password = "" } } config { # file、nacos 、apollo、zk、consul、etcd3 type = "nacos" nacos { serverAddr = "127.0.0.1:8848" namespace = "1344cd38-7919-4482-9c6f-11d73ab865b2" group = "DEFAULT_GROUP" username = "" password = "" } }
修改 config.txt 配置文件:
...忽略 service.vgroupMapping.my_test_tx_group=default #seata客户端事务组名称 service.default.grouplist=127.0.0.1:8099 ...忽略 store.mode=db #修改成db模式 store.db.datasource=druid store.db.dbType=mysql store.db.driverClassName=com.mysql.jdbc.Driver store.db.url=jdbc:mysql://localhost:3306/seata?useUnicode=true&rewriteBatchedStatements=true store.db.user=root store.db.password=root ...忽略
修改 nacos-config.py 配置文件:
#!/usr/bin/env python3 # -*- coding: UTF-8 -*- import http.client import sys if len(sys.argv) <= 2: print ('python nacos-config.py nacosAddr') exit() headers = { 'content-type': "application/x-www-form-urlencoded" }
# 将“../config.txt”需要改为“./config.txt”,因为 nacos-config.py和 config.txt在同目录 hasError = False for line in open('./config.txt'): pair = line.split('=') if len(pair) < 2: continue print (line), url_prefix = sys.argv[1] conn = http.client.HTTPConnection(url_prefix) if len(sys.argv) == 3: namespace=sys.argv[2] url_postfix = '/nacos/v1/cs/configs?dataId={0}&group=SEATA_GROUP&content={1}&tenant={2}'.format(str(pair[0]),str(line[line.index('=')+1:]).strip(),namespace) else: url_postfix = '/nacos/v1/cs/configs?dataId={}&group=SEATA_GROUP&content={}'.format(str(pair[0]),str(line[line.index('=')+1:])).strip() conn.request("POST", url_postfix, headers=headers) res = conn.getresponse() data = res.read() if data.decode("utf-8") != "true": hasError = True if hasError: print ("init nacos config fail.") else: print ("init nacos config finished, please start seata-server.")
将seata配置推送到nacos:
切换到 Seata 的conf目录,执行命令: python nacos-config.py nacos服务地址 命名空间
例:python nacos-config.py 127.0.0.1:8848 1344cd38-7919-4482-9c6f-11d73ab865b2
三.启动seata服务
切换到 Seata 的 bin 目录,执行命令:sh seata-server.sh

完成。
客户端
一.引入包
compile 'io.seata:seata-spring-boot-starter:1.4.0'
二.yml配置
seata: enabled: true data-source-proxy-mode: AT application-id: account-seata-example tx-service-group: my_test_tx_group # 事务群组(可以每个应用独立取名,也可以使用相同的名字) enable-auto-data-source-proxy: true use-jdk-proxy: false client: rm: report-success-enable: true table-meta-check-enable: false # 自动刷新缓存中的表结构(默认false) report-retry-count: 5 # 一阶段结果上报TC重试次数(默认5) async-commit-buffer-limit: 10000 # 异步提交缓存队列长度(默认10000) lock: retry-interval: 10 # 校验或占用全局锁重试间隔(默认10ms) retry-times: 30 # 校验或占用全局锁重试次数(默认30) retry-policy-branch-rollback-on-conflict: true # 分支事务与其它全局回滚事务冲突时锁策略(优先释放本地锁让回滚成功) tm: commit-retry-count: 3 # 一阶段全局提交结果上报TC重试次数(默认1次,建议大于1) rollback-retry-count: 3 # 一阶段全局回滚结果上报TC重试次数(默认1次,建议大于1) degrade-check: false degrade-check-period: 2000 degrade-check-allow-times: 10 undo: data-validation: true # 二阶段回滚镜像校验(默认true开启) log-serialization: jackson # undo序列化方式(默认jackson) log-table: undo_log # 自定义undo表名(默认undo_log) log: exceptionRate: 100 # 日志异常输出概率(默认100) service: vgroupMapping: my_test_tx_group: default # TC 集群(必须与seata-server保持一致) enable-degrade: false # 降级开关 disable-global-transaction: false # 禁用全局事务(默认false) grouplist: default: 127.0.0.1:8091 transport: shutdown: wait: 3 thread-factory: boss-thread-prefix: NettyBoss worker-thread-prefix: NettyServerNIOWorker server-executor-thread-prefix: NettyServerBizHandler share-boss-worker: false client-selector-thread-prefix: NettyClientSelector client-selector-thread-size: 1 client-worker-thread-prefix: NettyClientWorkerThread type: TCP server: NIO heartbeat: true serialization: seata compressor: none enable-client-batch-send-request: true # 客户端事务消息请求是否批量合并发送(默认true) registry: type: nacos nacos: server-addr: localhost:8848 namespace: group: DEFAULT_GROUP cluster: default config: # file: # 使用 nacos 后就不能打开,否则报错 # name: file.conf # type: nacos # 不能打开,否则报错,有可能是新版已经没有该配置 nacos: namespace: server-addr: localhost:8848 group: DEFAULT_GROUP
三.业务层使用
@GlobalTransactional(rollbackFor = Exception.class)

浙公网安备 33010602011771号