微服务分布式事务组件 Seata(一)
前言
事务简介
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。 在关系数据库中,一个事务由一组SQL语句组成。 事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(atomicity):事务是一个不可分割的工作单位, 事务中包括的诸操作要么都做,要么都不做。
- 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一 个一 致性状态, 事务的中间状态不能被观察到的。
- 隔离性(isolation) : 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离性又分为四个级别:读未提交(read uncommitted)、读已提交(read committed,解决脏读)、可重复读(repeatable read,解决虚读)、串行化(serializable,解决幻读)。
- 持久性(durability) :持久性也称永久性(permanence) ,指一个事务一旦提交, 它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。任何事务机制在实现时,都应该考虑事务的ACID特性,包括:本地事务、分布式事务,及时不能都很好的满足,也要考虑支持到什么程度。
本地事务
@Transactional
,大多场景下,我们的应用都只需要操作单一的数据库,这种情况下的事务称之为本地事务(Local Transaction),本地事务的 ACID 特性是数据库直接提供支持。本地事务应用架构如下
在 JDBC 编程中,我们通过java.sql.Connection
对象来开启、关闭或者提交事务
Connection conn = ... // 获取数据库连接
conn.setAutoCommit(false);// 关闭自动提交
try{
// ... 执行增删改查 sql
conn.commit(); // 提交事务
} catch(Exception e) {
conn.rollback(); // 事务回滚
} finally {
conn.close(); //关闭连接
}
分布式事务
下面两种情况如果是按照之前的本地事务,是无法保证事务的。
一、什么是 Seata
Seata是一款开源的分布式事务解决方案, 致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。AT 模式是阿里首推的模式,阿里云上有商用版本的 GTS (Global Transaction Service全局事务服务)
常见的分布式事务解决方案
- seata 阿里分布式事务框架
- 消息队列
- SAGA
- XA
他们有一个共同点,都是“两阶段(2PC)”。“两阶段”是指完成整个分 布式事务,划分成两个步骤完成。
实际上,这四种常见的分布式事务解决方案,分别对应着分布式事务的四种模式: AT、 TCC、 Saga、 XA;
2PC两阶段提交(Two-Phase Commit)协议:
顾名思义,分为两个阶段:Prepare 和 Commit
1.1、Prepare:提交事务请求
基本流程如下
- 询问协调者向所有参与者发送事务请求,询问是否可执行事务操作,然后等待各个参与者的响应。
- 执行各个参与者接收到协调者事务请求后,执行事务操作(例如更新一个关 系型数据库表中的记录),并将 Undo 和 Redo 信息记录事务日志中。
- 响应如果参与者成功执行了事务并写入Undo和Redo信息,则向协调者返回YES响应,否则返回NO响应。当然,参与者也可能宕机,从而不会返回响应。
1.2、正常提交事务
- commit请求协调者向所有参与者发送Commit请求。
- 事务提交参与者收到 Commit 请求后,执行事务提交,提交完成后释放事务执行期占用的所有资源。
- 反馈结果参与者执行事务提交后向协调者发送Ack响应。
- 完成事务接收到所有参与者的Ack响应后,完成事务提交。
1.3、中断事务
在执行Prepare步骤过程中,如果某些参与者执行事务失败、宕机或与协调者之间的网络中断,那么协调者就无法收到所有参与者的 YES 响应,或者某个参与者返回了 No 响应,此时,协调者就会进入回退流程,对事务进行回退。流程如下图红色部分(将Commit请求替换为红色的Rollback 请求)
- rollback请求协调者向所有参与者发送Rollback请求。
- 事务回滚参与者收到Rollback后,使用Prepare阶段的Undo日志执行事务回滚,完成后释放事务执行期占用的所有资源。
- 反馈结果参与者执行事务回滚后向协调者发送 Ack 响应。
- 中断事务接收到所有参与者的Ack响应后,完成事务中断。
二、2PC 的问题
- 同步阻塞参与者在等待协调者的指令时,其实是在等待其他参与者的响应,在此过程中,参与者是无法进行其他操作的,也就是阻塞了其运行。倘若参 与者与协调者之间网络异常导致参与者一直收不到协调者信息,那么会导致参与者一直阻塞下去。
- 单点在2PC中,一切请求都来自协调者,所以协调者的地位是至关重要的,如果协调者宕机,那么就会使参与者一直阻塞并一直占用事务资源。如果协调者也是分布式,使用选主方式提供服务,那么在一个协调者挂掉后, 可以选取另一个协调者继续后续的服务,可以解决单点问题。但是,新协调者无法知道上一个事务的全部状态信息(例如已等待Prepare响应的时长等),所以也无法顺利处理上一个事务。
- 数据不一致Commit事务过程中Commit请求/Rollback请求可能因为协调者宕机或协调者与参与者网络问题丢失,那么就导致了部分参与者没有收到Commit/Rollback请求,而其他参与者则正常收到执行了CommitRollback操作,没有收到请求的参与者则继续阻塞。这时,参与者之间的数据就不再一 致了。当参与者执行CommitRollback后会向协调者发送Ack,然而协调者不论是否收到所有的参与者的Ack,该事务也不会再有其他补救措施了,协调者能做的也就是等待超时后像事务发起者返回一个“我不确定该事务是否成功”。
- 环境可靠性依赖协调者Prepare请求发出后,等待响应,然而如果有参与者宕机或与协调者之间的网络中断,都会导致协调者无法收到所有参与者的响应,那么在 2PC 中,协调者会等待一定时间, 然后超时后,会触发事务中断,在这个过程中,协调者和所有其他参与者都是出于阻塞的。这种机制对网络问题常见的现实环境来说太苛刻了。
三、AT 模式(auto transaction)
AT模式是一种无侵入的分布式事务解决方案。
阿里seata框架,实现了该模式。
在AT模式下,用户只需关注自己的“业务SQL”,用户的“业务SQL”作为一阶段, Seata 框架会自动生成务的二阶段提交和回滚操作。
- 一阶段
在一阶段,Seata 会拦截“业务SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务SQL”更新业务数据,在业务数据更新之后,再将其保存成”after image”,最后生成行锁。以上操作全部在一个数据库事务内完成, 这样保证了一阶段操作的原子性。
- 二阶段提交
二阶段如果是提交的话,因为“业务SQL”在一阶段已经提交至数据库,所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
- 二阶段回滚
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和“after image”,如果两份数据完全一致就说明没有脏写, 可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
四、TCC 模式
TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confim 方法,二阶段回滚执行 Cancel 方法。
缺点:
侵入性强,并且得自己实现相关事务控制逻辑
优点:
在整个过程中基本没有锁,性能更强
五、基于消息队列实现分布式事务
六、Seata 详细说明
在Seata的架构中,一共有三个角色:
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TC为单独部署的Server服务端,TM和RM为嵌入到应用中的Client客户端。
在 Seata 中,一个分布式事务的生命周期如下
1.TM 请求 TC 开启一个全局事务。TC 会生成一个 XID 作为该全局事务的编号。XID 会在微服务的调用链路中传播,保证将多个微服务的子事务关联在一起。
2.RM 请求 TC 将本地事务注册为全局事务的分支事务,通过全局事务的XID进行关联。
3.TM 请求 TC 告诉 XID 对应的全局事务是进行提交还是回滚。
4.TC 驱动 RM 们将 XID 对应的自己的本地事务进行提交还是回滚。
本作品采用《CC 协议》,转载必须注明作者和本文链接