目录
- 1. 传统事务
- 1.1. 事务特征
- 1.2. 事务隔离级别
- 1.2.1. 表格展示
- 1.2.2. oracle和mysql可支持的事务隔离级别
- 2. 分布式事务
- 2.1. CAP指标
- 2.2. BASE理论
- 2.3. 7种常见的分布式事务方案
- 2.3.1. 2PC
- 2.3.2. 3PC
- 2.3.3. TCC
- 2.3.3.1. TCC的注意事项:
- 2.3.3.2. TCC方案的优缺点:
- 2.3.4. SAGA(略,详细请看文章)
- 2.3.5. 本地事务表(略,详细请看文章)
- 2.3.6. MQ事务消息(略,详细请看文章)
- 2.3.7. 最大努力通知(略,详细请看文章)
- 3. 分布式事务实现框架
- 3.1. 分布式事务Seata
- 3.1.1. Seata事务管理中有三个重要的角色:
- 3.1.2. Seata提供了四种不同的分布式事务解决方案:
- 3.1.2.1. XA模式
- 3.1.2.2. AT模式
- 3.1.2.3. TCC模式
- 3.1.2.3.1. 实现
- 3.1.2.3.2. 版本v1.5.1之前存在一些问题:
- 3.1.2.3.2.1. 空回滚、幂等、悬挂
- 3.1.2.4. SAGA模式
- 3.1.2.5. 总结
- 3.1.2.6. 注意
- 3.1.3. 项目例子
分布式事务 - 个人笔记
1. 传统事务
1.1. 事务特征
事务拥有以下四个特性,习惯上被称为 ACID 特性:
原子性(Atomicity)
:- 定义: 事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 意思:事务是最小的工作单元,不可再分。
一致性(Consistency)
:- 定义:事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态是指数据库中的数据应满足完整性约束。除此之外,一致性还有另外一层语义,就是事务的中间状态不能被观察到(这层语义也有说应该属于原子性)。
- 意思:事务必须保证多条DML语句同时成功或者同时失败。
隔离性(Isolation)
:- 定义:多个事务并发执行时,一个事务的执行不应影响其他事务的执行,如同只有这一个操作在被数据库所执行一样。
- 意思:事务A与事务B之间具有隔离。
持久性(Durability)
:- 定义:已被提交的事务对数据库的修改应该永久保存在数据库中。在事务结束时,此操作将不可逆转。
- 意思:持久性说的是最终数据必须持久化到硬盘文件中,事务才算成功的结束。
1.2. 事务隔离级别
事务隔离性隔离级别,理论上隔离级别包括4个:
-
RU(READ-UNCOMMITTED 表示读未提交)
:- 可以读取到事务未提交的数据,隔离性差,会出现脏读(当前内存读),不可重复读,幻读问题;
-
RC(READ-COMMITTED 表示读已提交)
:- 可以读取到事务已提交的数据,隔离性一般,不会出现脏读问题,但是会出现不可重复读,幻读问题;
-
RR(REPEATABLE-READ 表示可重复读)
:- 可以防止脏读(当前内存读),防止不可重复读问题,防止会出现的幻读问题,但是并发能力较差;
- 会使用next lock锁进制,来防止幻读问题,但是引入锁进制后,锁的代价会比较高,比较耗费CPU资源,占用系统性能;
-
SR(SERIALIZABLE 可串行化)
:- 隔离性比较高,可以实现串行化读取数据,但是事务的并发度就没有了;
- 这是事务的最高级别,在每条读的数据上,加上锁,使之不可能相互冲突。
1.2.1. 表格展示
事务隔离级别 | 脏读 | 幻读 | 不可重复读 | 隔离性 | 并发度能力 |
---|---|---|---|---|---|
读未提交 | × | × | × | 极差 | 极好 |
读已提交 | √ | × | × | 差 | 好 |
可重复读 | √ | √ | √ | 好 | 差 |
可串行化 | √ | √ | √ | 极高 | 极差 |
× 表示没有解决
√ 表示已解决
例如:
读已提交,解决了脏读问题,但幻读和不可重复读,没有解决。
1.2.2. oracle和mysql可支持的事务隔离级别
Oracle数据库默认的隔离级别是
读已提交
oracle 支持的事务隔离级别有
- READ COMMITTED (读已提交)
- SERIALIZABLE (可串行化)
- READ ONLY (只读)
MySQL数据库默认的隔离级是
可重复读
MySQL 支持的事务隔离级别有
- READ UNCOMMITTED(未提交读)
- READ COMMITTED(提交读)
- REPEATABLE READ(可重复读)
- SERIALIZABLE(可串行化)
2. 分布式事务
2.1. CAP指标
首先,分布式事务是基于分布式系统的,所以先看如下分布式系统存在的一些问题。
1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标
Consistency
(一致性)Availability
(可用性)Partition tolerance
(分区容错性)
EricBrewer 说,分布式系统无法同时满足这三个指标。
这个结论就叫做 CAP 定理
满足两个指标,我们分别叫:
CP
:满足“分区容错性”和“一致性”AP
:满足“可用性”和“分区容错性”CA
:满足“一致性”和“可用性”
具体解释
- 一致性:指的是在分布式系统中,所有节点访问同一数据项时,看到的是相同的数据。一致性要求在任意时刻,所有节点中的数据是一样的。
- 可用性:指的是在分布式系统中,客户端的请求能够在合理的时间内得到响应。
- 分区容错性:指的是分布式系统能够容忍网络分区的情况,即系统中的某些节点之间由于网络故障而无法通信。
分布式事务
P
是必须的,一定会存在网络方面的问题。
对于一个分布式系统来说,P 是一个基本要求,CAP 三者中,只能根据系统要求在 C 和 A 两者之间做权衡,并且要想尽办法提升 P
2.2. BASE理论
BASE理论是对CAP的一种解决思路,包含三个思想:
- Basically Available (基本可用): 分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
- soft state(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态
- Eventually Consistent (最终一致性): 虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
2.3. 7种常见的分布式事务方案
2.3.1. 2PC
为了解决分布式一致性问题,人们提出了很多解决方案,其中比较重要的就是2PC和3PC。2PC其实就是相当于在后厨引入一个协调者,他负责统筹所有参与者。
二阶段提交的算法思路是在分布式系统中引入了协调者,参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
那么整个操作被分为两个阶段:第一阶段:准备阶段
(投票阶段)和第二阶段:提交阶段
(执行阶段)
但是,同时,2PC也存在一些缺点,如同步阻塞问题、单点故障问题、无法100%保证数据一致性等问题。所以人们在2PC的基础上提出了3PC算法。
缺点:2PC的缺点也很致命:同步阻塞,单点问题,数据不一致,太过保守
- 1、
同步阻塞问题
。执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态,各个参与者在等待协调者发出提交或中断请求时,会一直阻塞,而协调者的发出时间要依赖于所有参与者的响应时间,如果协调者宕机了(单点),那么他就一直阻塞在这,而且无法达成一致(3PC引入了超时提交解决)。 - 2、
单点故障
。由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题) - 3、
数据不一致
。出现分区,或者网络故障。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。 - 4、
太过保守
:2pc没有设计相应的容错机制,例如:当任意一个参与者节点宕机,那么协调者超时没收到响应,就会导致整个事务回滚失败。 - 5、
二阶段无法解决的问题
:协调者(在第二阶段)发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
由于二阶段提交存在着诸如同步阻塞、单点问题、脑裂等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。
2.3.2. 3PC
3PC,三阶段提交协议,是二阶段提交协议的改进版本,三阶段提交有几个改动点:
- (1)在参与者中都引入
超时机制
,解决的单点故障问题
,并减少阻塞
。 - (2)在2PC基础上,3PC把2PC的第一阶段,拆分成两个阶段,增加了
CanCommit阶段
。减少不能执行的事务,进入被锁阶段。
tips: 2pc只有协调者有超时机制,超时后,回滚。
所以3PC会分为3个阶段,CanCommit 准备阶段
、PreCommit 预提交阶段
、DoCommit 提交阶段
,
注意:3PC无法解决:
数据不一致
以及太过保守问题
。
数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 doCommit 指令时,此时如果协调者请求中断事务,而协调者因为网络问题无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
2.3.3. TCC
TCC(Try Confirm Cancel)是应用层的两阶段提交,所以对代码的侵入性强,其核心思想是:针对每个操作,都要实现对应的确认和补偿操作,也就是业务逻辑的每个分支都需要实现 try、confirm、cancel 三个操作,第一阶段由业务代码编排来调用Try接口进行资源预留,当所有参与者的 Try 接口都成功了,事务协调者提交事务,并调用参与者的 confirm 接口真正提交业务操作,否则调用每个参与者的 cancel 接口回滚事务,并且由于 confirm 或者 cancel 有可能会重试,因此对应的部分需要支持幂等。
2.3.3.1. TCC的注意事项:
- (1)
允许空回滚
:
问题:空回滚出现的原因是 Try 超时或者丢包,导致 TCC 分布式事务二阶段的 回滚,触发 Cancel 操作,此时事务参与者未收到Try,但是却收到了Cancel 请求。(简略:cansel比try更快来到,cansel需要处理try为空的情况
)
解决办法:在cansel阶段,查询是否有冻结记录,如果没有,则创建一个不需要补偿的记录,例如:冻结金额为0
- (2)
防悬挂控制
:
问题:悬挂指的是二阶段的 Cancel 比 一阶段的Try 操作先执行,出现该问题的原因是 Try 由于网络拥堵而超时,导致事务管理器生成回滚,触发 Cancel 接口,但之后拥堵在网络的 Try 操作又被资源管理器收到了,但是 Cancel 比 Try 先到。但按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,所以此时应该拒绝执行空回滚之后到来的 Try 操作,否则会产生数据不一致。因此我们可以在 Cancel 空回滚返回成功之前,先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口执行前先检查这条事务xid或业务主键是否已经标记为回滚成功,如果是则不执行 Try 的业务操作。(简略:cansel比try更快来到,try需要处理,cansel已经创建了空try问题
)
解决办法:在try阶段,查询是否有冻结记录,如果有,则跳过直接返回结果。
- (3)
幂等控制
:
问题:由于网络原因或者重试操作都有可能导致 Try - Confirm - Cancel 3个操作的重复执行,所以使用 TCC 时需要注意这三个操作的幂等控制,通常我们可以使用事务 xid 或业务主键判重来控制。(简略:Try - Confirm - Cancel 3个操作的重复执行的问题
)
解决办法:在冻结记录表,增加状态字段,记录 Try - Confirm - Cancel 3个操作状态,执行Try - Confirm - Cancel操作时,先查看当前事务在哪个阶段。
具体解决办法:
- 因为try阶段,防悬挂控制已经解决了try阶段的重复try问题。
- comfirm阶段,我们已经把冻结记录删了,所以重复删除,不会影响功能。
- cansel阶段,我们要判断是否重复取消执行,如果是,则直接返回结果。(真正要写代码的)
2.3.3.2. TCC方案的优缺点:
(1)TCC 事务机制相比于上面介绍的 XA 事务机制,有以下优点:
性能提升
:具体业务来实现,控制资源锁的粒度变小,不会锁定整个资源。
数据最终一致性
:基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
可靠性
:解决了 XA 协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。
(2)缺点:TCC 的 Try、Confirm 和 Cancel 操作功能要按具体业务来实现,业务耦合度较高,提高了开发成本。
2.3.4. SAGA(略,详细请看文章)
2.3.5. 本地事务表(略,详细请看文章)
2.3.6. MQ事务消息(略,详细请看文章)
2.3.7. 最大努力通知(略,详细请看文章)
参考文章:七种常见分布式事务详解(2PC、3PC、TCC、Saga、本地事务表、MQ事务消息、最大努力通知)
3. 分布式事务实现框架
3.1. 分布式事务Seata
seata官网:https://seata.apache.org/zh-cn/docs/user/quickstart/
3.1.1. Seata事务管理中有三个重要的角色:
TC
(Transaction Coordinator)-事务协调者: 维护全局和分支事务的状态协调全局事务提交或回滚TM
(Transaction Manager)-事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务RM
(Resource Manager)-资源管理器: 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状
态,并驱动分支事务提交或回滚。
字面意思就是:
TC
事务协调者,即独立运行的seata-server,用于接收事务注册,提交和回滚TM
事务发起者。用来告诉TC全局事务的开始,提交,回滚RM
事务资源,每一个RM都会作为一个分支事务注册在TC。
3.1.2. Seata提供了四种不同的分布式事务解决方案:
3.1.2.1. XA模式
XA模式是使用数据库本身所拥有的事务
多事务操作的方式,如下:
3.1.2.2. AT模式
AT模式是利用额外的表(undo_log、lock_table)存储原始的数据,若业务回滚,则恢复原始的数据。
多事务操作的方式,如下:
AT是利用全局锁的方式,最终使用的是行锁。
-- auto-generated definition
create table lock_table
(
row_key varchar(128) not null
primary key,
xid varchar(128) null,
transaction_id bigint null,
branch_id bigint not null,
resource_id varchar(256) null,
table_name varchar(32) null,
pk varchar(36) null,
status tinyint default 0 not null comment '0:locked ,1:rollbacking',
gmt_create datetime null,
gmt_modified datetime null
)
charset = utf8mb4;
create index idx_branch_id
on lock_table (branch_id);
create index idx_status
on lock_table (status);
create index idx_xid
on lock_table (xid);
-- auto-generated definition
create table undo_log
(
branch_id bigint not null comment 'branch transaction id',
xid varchar(128) 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 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',
constraint ux_undo_log
unique (xid, branch_id)
)
comment 'AT transaction mode undo table' charset = utf8mb4;
create index ix_log_created
on undo_log (log_created);
3.1.2.3. TCC模式
缺点:
TCC 是一种侵入式的分布式事务解决方案,需要业务系统自行实现 Try,Confirm,Cancel 三个操作,对业务系统有着非常大的入侵性,设计相对复杂。
优势:
TCC 完全不依赖底层数据库,能够实现跨数据库、跨应用资源管理,可以提供给业务方更细粒度的控制。
3.1.2.3.1. 实现
需要创建冻结数据表
-- auto-generated definition
create table account_freeze_tbl
(
xid varchar(255) primary key,
user_id varchar(255) null,
freeze_money int default 0 null,
status int default 0 null
)
charset = utf8mb3;
-- auto-generated definition
create table order_freeze_tbl
(
xid varchar(255) primary key,
order_id varchar(255) null,
freeze_count int default 0 null,
freeze_money int default 0 null,
status int default 0 null
)
charset = utf8mb3;
-- auto-generated definition
create table storage_freeze_tbl
(
xid varchar(255) primary key,
storage_id varchar(255) null,
freeze_count int default 0 null,
status int default 0 null
)
charset = utf8mb3;
seata v1.5.1 及之后,解决空回滚、幂等、悬挂,需要添加 useTCCFence = true,并且新增表 tcc_fence_Log。
@LocalTCC
public interface TccAccountService {
@TwoPhaseBusinessAction(name = "account", commitMethod = "commit", rollbackMethod = "rollback",useTCCFence = true)
String account(@BusinessActionContextParameter("userId") String userId,
@BusinessActionContextParameter("money") int money,
@BusinessActionContextParameter("type") int type) ;
boolean commit(BusinessActionContext actionContext);
boolean rollback(BusinessActionContext actionContext);
}
CREATE TABLE IF NOT EXISTS tcc_fence_Log
(
xid VARCHAR(128) NOT NULL ,
branch_id Bigint NOT NULL ,
action_name VARCHAR(64) NOT NULL ,
status TINYINT NOT NULL ,
gmt_create DATETIME NOT NULL ,
gmt_modified DATETIME NOT NULL ,
PRIMARY KEY (xid,branch_id)
)
ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
TCC分支事务状态:包含四种:已尝试、已提交、已回滚、空悬挂
3.1.2.3.2. 版本v1.5.1之前存在一些问题:
3.1.2.3.2.1. 空回滚、幂等、悬挂
参考前面的TCC。
3.1.2.4. SAGA模式
seata saga:https://seata.apache.org/zh-cn/docs/dev/mode/saga-mode
Saga模式是SEATA提供的长事务解决方案,在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务(执行处理的时候出错了,给一个修复的机会)都由业务开发实现。
参考前面的SAGA
3.1.2.5. 总结
XA模式
- 强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入TCC模式
- 最终一致的分阶段事务模式,有业务侵入AT模式
- 最终一致的分阶段事务模式,无业务侵入,也是Seata的默认模式SAGA模式
- 长事务模式,有业务侵入
tips:
- 无业务侵入 - 不用写代码
- 有业务侵入 - 要写代码
3.1.2.6. 注意
TCC 写代码注意点:
- try阶段,你可以通过RootContext.getXID() 获取xid,但在rollback阶段,你只能铜鼓businessActionContext.getXid() 获取xid
- seata 1.5.1 之后,已经完善了,空回滚、悬挂、幂等 问题,你不需要在配了
- 使用seata事务,本地事务需要开启的,都要开启 @GlobalTransactional
3.1.3. 项目例子
seata例子,只有XA、AT、TCC模式