分布式事务 Seata
# 1、简介
事务基本要求:ACID,原子性、一致性、隔离性、持久性。
本章解决问题:
基于微服务的分布式事务。在分布式系统下,一个业务跨越多个服务或数据源,每个服务都是一个分支事务,要保证所有分支事务最终状态一致,这样的事务就是分布式事务。
CAP定理
1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有 3 个指标:
- Consistency:一致性
- Available:可用性
- Partition tolerance:分区容错性
于是 CAP 定理的内容为:
- 分布式系统节点通过网络连接,一定会出现分区问题(P)
- 当分区出现时,系统的一致性(C)和可用性(A)就无法同时满足
我们之前搭建的 Elasticsearch 集群属于 CP,不属于 AP。
BASE理论
解决CAP存在的问题
- Basically Available(基本可用):在分布式系统出现故障时,允许损失部分可用性,但保证核心可用。
- Soft State(软状态):在规定时间内允许出现不一致状态。
- Eventually Consistent(最终一致性):数据最终会达到一致性。
解决的方式:
- 【AP模式】:各子事务分别提交,允许出现结果的不一致,然后采用措施恢复数据,最终达到一致性。
- 【CP模式】:各子事务执行后互相等待,同时提交、同时回滚,最终达到一致性。但在事务等待的过程,本次服务处于弱可用,同时因为各子事务必须彼此感知各自事务状态才能保证一致性,因此需要一个“事务协调者”负责协调,由此也诞生出了全局事务、分支事务的概念。
# 2、Seata简介
分布式事务解决方案,http://seata.io/zh-cn/
每个微服务都需配置 Seata,略微繁琐
- 简介:
- Seata 是 2019 年 1 月份蚂蚁金服与阿里巴巴共同开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
- Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata 事务中有 3 个重要角色
- TC(Transaction Coordinator)事务协调者:维护全局事务和分支事务的状态,协调全局事务的提交与回滚。
- TM(Transaction Manager)事务管理器:定义全局事务的范围,开启、提交或回滚全局事务。
- RM(Resource Manager)资源管理器:管理分支事务,向 TC 注册分支事务并报告状态,提供分支事务的提交与回滚功能。
Seata 就是 TC(作为TC,搭建成功后我们不需要访问它,这是给 TM 和 RM 访问的),企业中需要搭建集群。
Seata提供了 4 种不同的分布式事务处理方案:
- XA模式:强一致性分布阶段事务模式,牺牲一定可用性,无业务侵入。
- AT模式(默认):最终一致的分阶段事务模式,无业务侵入。
- TCC模式:最终一致的分阶段事务模式,有业务侵入。
- SAGA模式:长事务模式,有业务侵入。
# 3、安装
1. 下载 seata-server 包并解压:https://github.com/seata/seata/releases
2. 修改conf目录下的application.yml
文件
1.4.2版本为
registry.conf
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
# 若无以下配置则注释
# extend:
# logstash-appender:
# destination: 127.0.0.1:4560
# kafka-appender:
# bootstrap-servers: 127.0.0.1:9092
# topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
# 配置中心
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: "" # 命名空间为空,默认 public
group: SEATA_GROUP
username: nacos
password: nacos
data-id: seataServer.properties
# 注册中心
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
username: nacos
password: nacos
cluster: SH # SH表示上海
# 已经配置了 nacos 作为配置中心,所以这里 store 与 server 不配置
# store:
# support: file 、 db 、 redis
# mode: file
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
3. 新建数据库 seata
,在此基础上新增两张表branch_table
与global_table
作事务管理。
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`resource_group_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`branch_type` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`status` tinyint(4) NULL DEFAULT NULL,
`client_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime(6) NULL DEFAULT NULL,
`gmt_modified` datetime(6) NULL DEFAULT NULL,
PRIMARY KEY (`branch_id`) USING BTREE,
INDEX `idx_xid`(`xid`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
`xid` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`transaction_id` bigint(20) NULL DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_service_group` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`transaction_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`timeout` int(11) NULL DEFAULT NULL,
`begin_time` bigint(20) NULL DEFAULT NULL,
`application_data` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`gmt_create` datetime NULL DEFAULT NULL,
`gmt_modified` datetime NULL DEFAULT NULL,
PRIMARY KEY (`xid`) USING BTREE,
INDEX `idx_gmt_modified_status`(`gmt_modified`, `status`) USING BTREE,
INDEX `idx_transaction_id`(`transaction_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
SET FOREIGN_KEY_CHECKS = 1;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
4. Nacos 新建配置文件
为 Seata 集群作准备
Nacos创建seataServer.properties
配置文件,修改 MySQL 数据库信息,其余配置默认。
# 数据存储方式,db代表数据库
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=数据库密码
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
# 事务、日志等配置
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
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
# 关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
5. 启动:
Linux 选择.sh
,Windows 选择.bat
。
另外注意这里可能会报nohup: /Library/Internet: No such file or directory
错误,原因是JDK路径查找失败;
./bin/seata-server.sh
6. 查看启动日志: /seata/logs/start.out
判断是否启动成功。
7. 微服务中引入依赖并配置连接
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<!-- 排除旧版本 Seata ,引入 1.6.1 版本 -->
<exclusions>
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
nacos服务名称必须包括 4 部分
,而且每个微服务都必须配置这些信息,微服务将根据这些信息去注册 Seata。
namespace
group
serviceName
cluster
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-server # TC 在 Nacos 中的名称
tx-service-group: seata-demo
service:
vgroup-mapping:
seata-demo: SH
2
3
4
5
6
7
8
9
10
11
12
- 重启微服务,查看 Seata 日志
# 4、XA规范
分阶段事务模式,几乎所有的主流数据库都对其提供了支持。
示意图:
优点:
- 事务具有强一致性,满足ACID
- 常用数据库均支持,实现简单、无代码侵入
缺点:
- 事务之间耦合度很高
- 事务之间相互等待,性能较差
- 事务的实现依赖于关系型数据库
实现步骤
- 各微服务附加配置后重启
- 业务 Service 加 全局事务注解
@GlobalTransaction
seata: data-source-proxy-mode: XA # 使用 XA 模式
1
2@Override @GlobalTransactional public void create(Order order) { // 创建订单 // 扣用户余额 // 扣库存 }
1
2
3
4
5
6
7【补充说明】
data-source-proxy-mode
配置的作用:设置数据源代理模式,Seata 通过劫持数据源
data-source
来实现分布式事务的管理,配置后所有事务都将由 Seata 托管。
# 5、AT模式
默认
同样是分阶段事务模式,弥补了 XA 模式中资源锁定周期过长的缺陷,但同时也牺牲了一些安全性。
示意图:允许先成功,后续使用
undo log
进行回滚。【脏读问题】:
这里阐述比较复杂,总之 AT 模式就是牺牲一定的安全性换来效率
由于各事务在一定程度上存在“独立性”,所以 AT 模式存在“脏读”现象。
AT 模式新增**【全局锁】**用来防止数据脏读,当数据遇到同时 update 请求时,全局锁会限制另一方的提交,直到原来的一方释放全局锁,此时 AT 模式相当于退化为 XA 模式。
但是全局锁只能作用于 Seata 事务,也就是说对非 Seata 管理的事务无效,在这种情况下依旧会产生“脏读”现象(无法解决)。幸运的是,Seata 能察觉这种现象的产生并抛出异常,我们可以捕获这种异常并编写代码发送邮件告知服务管理者。
当数据没有发生“脏读”问题时,AT模式效率较高,原因如下:
事务分布式提交,突破“木桶效应”限制。
- Seata 的“全局锁”粒度较细,只锁字段中的具体数据,对相同字段的其他数据无影响。
- MySQL 属于“粗粒度”锁,会锁住整张表,极大的降低效率。
// 例如在下面字段中,当 name 被某事务支配时,money字段并不受影响 {"id":1,"name":"张三","money":100}
1
2
【 XA模式 与 AT模式 总结】
AT模式牺牲的只是一些比较小的安全性(sava 与 update 属于“小概率”操作),换来的是极大的效率提升,在业务sava 与 update 次数较少且安全性要求不高的数据库,应优先使用AT模式。
实现步骤
- 数据库新建 2 张表,存储在不一样地方
lock_table
:导入到与 TC(即 Seata 服务端)相关联的数据库undo_log
:导入到与微服务相关的数据库(也就是在每个相关的微服务数据库中都需要导入undo_log
表)
- 修改相关微服务的
application.yml
配置文件,声明为使用 AT 模式(其实默认模式)。
DROP TABLE IF EXISTS `lock_table`; CREATE TABLE `lock_table` ( `row_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, `xid` varchar(96) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `transaction_id` bigint(20) NULL DEFAULT NULL, `branch_id` bigint(20) NOT NULL, `resource_id` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `table_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `pk` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `gmt_create` datetime NULL DEFAULT NULL, `gmt_modified` datetime NULL DEFAULT NULL, PRIMARY KEY (`row_key`) USING BTREE, INDEX `idx_branch_id`(`branch_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;
1
2
3
4
5
6
7
8
9
10
11
12
13
14DROP TABLE IF EXISTS `undo_log`; CREATE TABLE `undo_log` ( `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id', `xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id', `context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci 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 INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;
1
2
3
4
5
6
7
8
9
10
11seata: data-source-proxy-mode: AT 12
1
2
3- 数据库新建 2 张表,存储在不一样地方
# 6、TCC模式
TCC模式效率很高,但过于复杂
具体案例见:链接 (opens new window)
简介
需编写代码分别实现 3 个阶段
- Try:资源检查和预留
- Confirm:业务执行和提交
- Cancel:预留资源释放
示意图:
优点:
- 分布式提交事务,效率高
- 相比 AT 模式,无需生成快照(即 undo_log)、无需使用全局锁,性能最强
- 依赖补偿操作,不依赖数据库事务,可用于非事务型数据库
缺点:
- 代码侵入性很强,需同时编写 try、confirm、cancel 接口,特别繁琐与麻烦
- 事务最终一致而不是强一致
- 需要考虑Confirm与Cancel失败的情况,即做好幂等处理
- 另外需要注意空回滚的情况
【名词解释】
- 空回滚:当某分支事务的 try 阶段阻塞时,可能导致全局事务超时而触发其他服务的 cancel 操作。在未执行 try 操作时先执行了 cancel 操作,这时 cancel 不能做回滚,就是空回滚。
- 幂等处理:对于已经空回滚的业务,如果以后继续执行 try,就永远不可能 confirm 或 cancel ,这就是业务悬挂(应当阻止执行空回滚后的 try 操作,避免悬挂)。
举例
# 7、SAGA模式
TCC模式的“简化版”,牺牲了一定的安全性,存在数据“脏读”风险
Saga模式在实际中很少被运用
简介:
Saga模式是 Seata 提供的长事务解决方案,具体分为两个阶段:
- 一阶段:直接提交本地事务
- 二阶段:成功则什么也不做,失败则通过编写补偿业务回滚
优点:
- 类似 TCC,但不用编写 TCC 中 3 个阶段,实现简单
- 事务参与者可以基于事件驱动实现异步调用,吞吐量高
- 无锁,一阶段直接提交事务,性能好
缺点:
- 没有锁与事务隔离性,存在数据“脏写”情况
- 软状态持续的时间不确定,时效性较差