二阶段提交
二阶段提交协议(Two-phase Commit,即 2PC)是常用的分布式事务解决方案,即将事务的提交过程分为两个阶段来进行处理。
两个阶段分别为:
- 准备阶段 prepare
- 提交阶段 commit
参与的角色:
- 事务协调者(事务管理器):事务的发起者
- 事务参与者(资源管理器):事务的执行者
准备阶段(投票阶段)
这是两阶段的第一段,这一阶段只是准备阶段,由事务的协调者发起询问参与者是否可以提交事务,但是这一阶段并未提交事务
- 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复;
- 各参与者执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务);
- 如参与者执行成功,给协调者反馈同意,否则反馈中止。
提交阶段
这一段阶段属于 2PC 的第二阶段(提交执行阶段),协调者发起正式提交事务的请求,当所有参与者都回复同意时,则意味着完成事务
- 协调者节点向所有参与者节点发出正式提交 (commit) 的请求;
- 参与者节点正式完成操作,并释放在整个事务期间内占用的资源;
- 参与者节点向协调者节点发送 ack 完成消息;
- 协调者节点收到所有参与者节点反馈的 ack 完成消息后,完成事务。
但是,如果任意一个参与者节点在第一阶段返回的消息为终止,或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时,那么这个事务将会被回滚。
协调者节点向所有参与者节点发出回滚操作 (rollback) 的请求;
- 参与者节点利用阶段 1 写入的 undo 信息执行回滚,并释放在整个事务期间内占用的资源;
- 参与者节点向协调者节点发送 ack 回滚完成消息;
- 协调者节点受到所有参与者节点反馈的 ack 回滚完成消息后,取消事务。
不管最后结果如何,第二阶段都会结束当前事务
2PC 的缺点
二阶段提交看起来确实能够提供原子性的操作,但是不幸的是,二阶段提交还是有几个缺点的:
- 性能问题:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态;
- 可靠性问题:参与者发生故障。协调者需要给每个参与者额外指定超时机制,超时后整个事务失败。协调者发生故障。参与者会一直阻塞下去。需要额外的备机进行容错;
- 数据一致性问题:二阶段无法解决的问题:协调者在发出 commit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交;
- 实现复杂:牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
2PC 的优点
尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能 100% 保证强一致)
三阶段提交(3PC)
三阶段提交协议,是二阶段提交协议的改进版本,三阶段提交有两个改动点。
- 在协调者和参与者中都引入超时机制;
- 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。
也就是说,除了引入超时机制之外,3PC 把 2PC 的准备阶段再次一分为二。这样三阶段提交就有 CanCommit、PreCommit、DoCommit 三个阶段。
阶段一:CanCommit 阶段
3PC 的 CanCommit 阶段其实和 2PC 的准备阶段很像。协调者向参与者发送 commit 请求,参与者如果可以提交就返回 Yes 响应,否则返回 No 响应。
事务询问:协调者向所有参与者发出包含事务内容的 canCommit 请求,询问是否可以提交事务,并等待所有参与者答复;
- 响应反馈:参与者收到 canCommit 请求后,如果认为可以执行事务操作,则反馈 yes 并进入预备状态,否则反馈 no。
假如有任何一个参与者向协调者发送了 No 响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
- 发送中断请求 :协调者向所有参与者发送 abort 请求;
中断事务 :参与者收到来自协调者的 abort 请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断
阶段二:PreCommit 阶段
协调者根据参与者的反应情况来决定是否可以进行事务的 PreCommit 操作。根据响应情况,有以下两种可能。
假如所有参与者均反馈 yes,协调者预执行事务。
- 发送预提交请求 :协调者向参与者发送 PreCommit 请求,并进入准备阶段;
- 事务预提交 :参与者接收到 PreCommit 请求后,会执行事务操作,并将 undo 和 redo 信息记录到事务日志中(但不提交事务);
响应反馈 :如果参与者成功的执行了事务操作,则返回 ACK 响应,同时开始等待最终指令。
阶段三:doCommit 阶段
该阶段进行真正的事务提交,也可以分为以下两种情况。
进入阶段 3 后,无论协调者出现问题,或者协调者与参与者网络出现问题,都会导致参与者无法接收到协调者发出的 do Commit 请求或 abort 请求。此时,参与者都会在等待超时之后,继续执行事务提交。
执行提交
- 发送提交请求:协调接收到参与者发送的 ACK 响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送 doCommit 请求;
- 事务提交:参与者接收到 doCommit 请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源;
- 响应反馈:事务提交完之后,向协调者发送 ack 响应;
- 完成事务:协调者接收到所有参与者的 ack 响应之后,完成事务。
中断事务:任何一个参与者反馈 no,或者等待超时后协调者尚无法收到所有参与者的反馈,即中断事务:
发送中断请求:如果协调者处于工作状态,向所有参与者发出 abort 请求;
- 事务回滚:参与者接收到 abort 请求之后,利用其在阶段二记录的 undo 信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源;
- 反馈结果:参与者完成事务回滚之后,向协调者反馈 ACK 消息;
- 中断事务:协调者接收到参与者反馈的 ACK 消息之后,执行事务的中断。
优点
相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段 3 中协调者出现问题时,参与者会继续提交事务。
缺点
数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 doCommit 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致
TC数据库连接信息
## 采用db的存储形式
store.mode=db
## druid数据源
store.db.datasource=druid
## mysql数据库
store.db.dbType=mysql
## mysql驱动
store.db.driverClassName=com.mysql.jdbc.Driver
## TC的数据库url
store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useUnicode=true
## 用户名
store.db.user=root
## 密码
store.db.password=Nov2014
CREATE TABLE `storage` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`num` bigint(11) NULL DEFAULT NULL COMMENT '数量',
`create_time` datetime(0) NULL DEFAULT NULL,
`price` bigint(10) NULL DEFAULT NULL COMMENT '单价,单位分',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
INSERT INTO `storage` VALUES (1, '分布式事务', 1000, '2021-12-25 22:32:40', 100);
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;
spring:
application:
## 指定服务名称,在nacos中的名字
name: seata-storage
## 客户端seata的相关配置
seata:
## 是否开启seata,默认true
enabled: true
application-id: ${spring.application.name}
## seata事务组的名称,一定要和config.tx(nacos)中配置的相同
tx-service-group: ${spring.application.name}-tx-group
## 配置中心的配置
config:
## 使用类型nacos
type: nacos
## nacos作为配置中心的相关配置,需要和server在同一个注册中心下
nacos:
## 命名空间,需要server端(registry和config)、nacos配置client端(registry和config)保持一致
namespace: 7a7581ef-433d-46f3-93f9-5fdc18239c65
## 地址
server-addr: localhost:8848
## 组, 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
group: SEATA_GROUP
## 用户名和密码
username: nacos
password: nacos
registry:
type: nacos
nacos:
## 这里的名字一定要和seata服务端中的名称相同,默认是seata-server
application: seata-server
## 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
group: SEATA_GROUP
namespace: 7a7581ef-433d-46f3-93f9-5fdc18239c65
username: nacos
password: nacos
server-addr: localhost:8848
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
- 对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入;
- 高性能:减少分布式事务解决方案所带来的性能消耗。
官方文档:https://seata.io/zh-cn/index.html
Seata 的几种术语:
- TC(Transaction Coordinator):事务协调者。管理全局的分支事务的状态,用于全局性事务的提交和回滚;
- TM(Transaction Manager):事务管理者。用于开启、提交或回滚事务;
- RM(Resource Manager):资源管理器。用于分支事务上的资源管理,向 TC 注册分支事务,上报分支事务的状态,接收 TC 的命令来提交或者回滚分支事务。
AT 模式
Seata 目前支持多种事务模式,分别有 AT、TCC、SAGA 和 XA 。文章篇幅有限,今天只讲常用的 AT 模式。
AT 模式的特点就是对业务无入侵式,整体机制分二阶段提交(2PC)
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:提交异步化,非常快速地完成;回滚通过一阶段的回滚日志进行反向补偿。
在 AT 模式下,用户只需关注自己的业务 SQL。用户的业务 SQL 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。