分布式事务简介
事务是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。事务的四个特征(ACID)
- 原子性(Atomicity): 指一组操作要么全部执行,要么全部不执行。在事务执行过程中,如果出错则会滚到事务开始前的状态,所有操作像一个原子,不可拆分。
- 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。
- 隔离性(Isolation):同一时间,只允许一个事务操作同一条数据,并且操作对其他事务不可见,不同事务之间也不会有干扰,
- 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
传统的单体应用借助关系数据库的ACID特性能很好的保证数据一致性,但是随着系统的发展,由单体应用向分布式、微服务的演变,传统的事务无法协调多个服务、多个数据库之间的事务,是之达到统一提交或统一回滚状态。
分布式事务是指事务的参与者、资源服务器以及事务管理器分别位于不同的分布式系统的不同结点之上,一次分布式事务涉及到多个系统、数据库之间的协调,保证分散在各结点的事务操作要么全部成功,要么全部失败。
分布式事务理论
CAP理论
CAP理论对分布式系统特性做了高度抽象,形成了三个指标:
- 一致性(Consistency):多个结点的数据需时刻保持一致,用户无论访问哪个结点读取的数据是一致的(强一致性)。
- 可用性(Availability):指任何来自客户端的请求,不管访问哪个非故障结点,都能得到响应数据,但并不保证是最新的数据。可用性的两个关键一个是合理的时间,一个是合理的响应。
- 分区容错性(Partition Tolerance):将服务分散在多个节点上,当节点间出现任务数量的消息丢失或高延迟的时候,系统任能持续的工作。
在分布式系统中,只要有网络交互就一定存在延迟和数据丢失,这是我们必须接受的,同时还要保证系统不能挂掉。节点间的分区故障是必然存在的,也就是说分区容错性(P)是必须要保证的。现在就只剩下一致性(C)和可用性(A)之间选择一个了。
当选择了一致性(C),系统能保证读取到最新的数据,但节点之间数据同步需要时间,就会导致在读取数据时发生阻塞而超时,为了不破坏一致性,可能会因为无法响应最新数据而返回错误信息。
当选择了可用性(A),系统将始终响应客户端的查询,返回特点的信息,但如果发生网络分区、或者数据延迟,返回的数据并不一定是全局最新数据。
BASE理论
由于CAP理论我们只能在C、A、P中选择其中两个。BASE理论是对CAP中一致性和可用性权衡的结果。它的核心思想是,如果非必须,不推荐使用强一致性,鼓励优先考虑可用性,根据业务的场景来实现弹性的基本可用,以及实现数据最终一致性。
-
BA:Basic Available基本可用,当系统出现不可预知的故障,允许损失部分可用性,但不是完全不可用。
-
S:Soft State柔性状态,数据存在一个中间状态,并该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
-
E:Eventual Consisstency最终一致性,经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
分布式事务解决方案
两阶段提交(2PC)
- 事务发起阶段:
事务的发起者提出一个request(比如用户下单购买某个商品),要求其依赖的服务(事务的执行者)本地执行业务逻辑。执行成功本地事务不提交但要告诉发起者本地已经执行成功;执行失败执行者告诉发起者本地作业执行失败
- 事务提交/回滚阶段:
事务发起者根据事务发起阶段收集到的信息决定提交/回滚
如果全部执行者都反馈成功那么发起者通知所有执行者提交事务
如果存在执行者反馈失败,则发起者通知所有参与者取消事务
三阶段提交(3PC)
- CanCommit(询问阶段):
事务协调者向参与者发送事务执行请求,询问是否可以完成指令,参与者只需要回答是不是即可,不需要做正真的事务操作,这个阶段会有超时终止机制。
- PreCommit(准备阶段):
事务协调者会根据参与者的反馈结果决定是否继续执行,如果在询问阶段所有参与者都返回可以执行操作,则事务协调者会向所有参与者发送PreCommit请求,参与者收到请求后会写redo和undo日志,执行事务操作但是不提交事务,然后返回ACK响应等待事务协调者的下一步通知。如果询问阶段任意参与者返回不能执行操作的结果,那么事务协调者会向所有参与者发送事务中断请求。
- DoCommit(提交或回滚阶段):
这个阶段也会存在两种结果,根据上一步骤的执行结果来决定DoCommit的执行方式。如果每个参与者在PreCommit阶段都返回成功,那么事务协调者会向所有的参与者发起事务提交指令。反之,如果参与者中的任一个参与者返回失败,那么事务协调者就会发起中转指令来回滚事务。
TCC
TCC是将事务分为Try(尝试)、Confirm(确认)和Cancel(取消)三个阶段。每个阶段有开发者自己控制,避免了长事务的问题。
TCC事务模式需要考虑下面几个问题:
-
空回滚:当某分支事务的try阶段阻塞时,可能导致全局事务超时而触发二阶段的cancel操作。在未执行try操作时先执行了cancel操作,这时cancel不能做回滚,就是空回滚。
-
悬挂:对于已经空回滚的业务,如果以后继续执行try,就永远不可能confirm或cancel,这就是业务悬挂。
-
幂等:Confim、Cancel阶段不论网络原因或者业务异常,都会根据日志记录重复执行操作,所以需要保持接口的幂等性,也就是最多执行一次
Seata框架介绍
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata由三种角色组成:
- TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
XA模式
XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有****主流的数据库****都对 *XA 规范* 提供了支持。
1 TM负责定义全局事务的边界,向TC申请,开启一个全局事务;
2 全局事务创建成功后,生成全局唯一的XID;
3 XID会在微服务请求链路上下文中传播;
4 RM向TC注册分支事务,并归属到XID对应的全局事务进行调度;
5 TM向TC发起相应XID的全局事务提交或回滚决议;
6 TC完成对XID管理的全部分支事务提交或回滚的调度;
我们看下下面的demo,有order服务负责创建订单,并且通过Fegin调用库存服务扣库存,如果库存不足则回滚。
seata-order
@GlobalTransactional(rollbackFor = {Exception.class})
@Transactional
public String createOrder(String userId, String itemId, Integer num) {
Order order = new Order();
order.setUserId(userId);
order.setItemId(itemId);
order.setNum(num);
orderRepository.save(order);
// 扣除库存
inventoryFeign.adjust(itemId, num);
return "创建成功";
}
seata-inventory
public void adjust(String itemId, Integer num) {
Inventory inventory = inventoryDao.findByItemId(itemId);
if(inventory.getInventory() < num){
throw new RuntimeException("库存不足");
}
inventory.setInventory(inventory.getInventory() - num);
inventoryDao.save(inventory);
}
库存表中有商品编号为10000的商品,库存为10。以debug的方式启动服务,在扣库存的地方打上端点,你会发现在扣完库存,库存的数量并没有减少,订单也没用创建,并且行记录也被锁住,直到事务提交,才能看到订单创建和库存扣减,如果扣除不足,订单也会回滚。
前面我们提到过,AX是基于数据库的事务特征,属于两阶段提交,所以在整体事务提交前,各服务数据不可见。
XA模式的优点:
-
事务的强一致性,满足ACID原则。
-
常用数据库都支持,实现简单,并且没有代码侵入
XA模式的缺点:
- 性能较差,因为一阶段需要锁定数据库资源,等待二阶段结束才释放,所以性能较差
- 依赖关系型数据库实现事务。NoSQL参与不进来
AT模式
AT模式是基于两阶段协议的演变:
- 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
- 二阶段:
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
修改为AT模式只需要修改配置:
seata.dataSourceProxyMode=AT
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) E
AT模式需要新增一张undo_log表,用来记录执行SQL的回滚SQL,比如业务执行了INSERT操作,就会记录一下对应的DELETE语句。所以AT模式下是不依赖与数据库的事务。
AT模式的优点:
- 一阶段完成直接提交事务,释放数据库资源,性能比较好
- 利用全局锁实现读写隔离
- 没有代码侵入,框架自动完成回滚和提交
AT模式的缺点:
- 两阶段之间属于软状态,属于最终一致
- 框架的快照功能会影响性能,但比XA模式要好很多
TCC模式
TCC 模式,不依赖于底层数据资源的事务支持:
- 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
- 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
放数据库资源,性能比较好 - 利用全局锁实现读写隔离
- 没有代码侵入,框架自动完成回滚和提交
AT模式的缺点:
- 两阶段之间属于软状态,属于最终一致
- 框架的快照功能会影响性能,但比XA模式要好很多
TCC模式
TCC 模式,不依赖于底层数据资源的事务支持:
- 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
- 二阶段 commit 行为:调用 自定义 的 commit 逻辑。
- 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。