分布式实践:分布式事务

2,203 阅读10分钟

为什么

随着互联网技术的不断发展,业务复杂度越来越高,单体架构演变成分布式架构是必然的,虽然分布式架构解决了一些单体架构的问题,但同时也引入了一些分布式的问题,分布式事务几乎每个分布式系统都会遇到的一个问题。

在单体架构中,对于转账、下单扣库存等这类多资源的操作,由于都是本地操作,不涉及跨进程,所以我们可以很好的使用数据库本地事务就可以很容易的解决跨资源更新的不一致问题。

在演变成分布式架构后,单体服务被拆分成了不同的微服务,对于转账、下单扣库存等业务就需要跨进程服务进行资源的更新,单单依靠数据库事务就没办法解决。

举个简单的下单例子(可能没那么严谨),如果一切顺利,那么最终订单的状态和支付服务中期望的状态是一致的,也就是说

  • 当支付成功后,订单服务中的订单状态为 支付成功
  • 当支付失败后,订单服务中的订单状态为 支付失败

但在分布式系统中网络是不可靠的,如果在支付的过程中网络不稳定就可能会导致 支付成功了,但是订单服务没有更新成功订单状态

这种不一致显然是不能接受的,分布式事务则是用于解决这种问题,总的来说就是分为两种方式

  • 跨进程事务要么同时成功,要么同时失败,保证强一致性(也就是CP)
  • 允许业务上多个资源的状态不一致,但经过一段时间的“补偿”,最终能够达成一致(也就是AP)

也就是说对于分布式事务的解决方案的选择,首先选的是对于数据不一致的容忍程度,也就是在选 AP 还是 CP,其次才是选技术方案;比如对于支付场景,大部分都是 CP,我们在支付的时候会一直等待直到成功或失败,在这段时间内几乎是不可用的。而对于退款,往往选的是AP,我们在退款的时候系统往往会告诉我们保证在2小时内退款成功,也就是会有一个退款中的软状态。

分布式事务的原则

  • 假定网络或者服务的不可靠
  • 将全局事务建模成一组本地ACID事务
  • 引入事务补偿机制处理失败场景
  • 事务始终处在一种明确的状态(不管成功还是失败)
  • 尽可能考虑最终一致
  • 考虑隔离性
  • 考虑幂等性
  • 异步响应式,尽量避免直接同步调用

实现方式

2PC:二阶段提交

二阶段提交(Two-phase Commit)是为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。在分布式系统中,每个节点都可以知道自己的状态,但是无法知道其他节点的状态,所以二阶段提交的思路就是引入一个 协调者, 每个参与者将自己的状态反馈给协调者,由协调者统一决定参与者是否继续。

  1. 第一阶段(prepare):事务管理器会向所有的本地资源管理器(每个节点的RM)发起请求询问是否准备好,所有参与者都讲本事务是否能成功反馈给事务管理器。
  2. 第二阶段(commit/rollbak):事务管理器根据所有本地资源管理器的反馈,通知所有的本地资源管理器进行事务提交或回滚(所有的本地资源prepare阶段都ok才会进行 commit)。

2PC 虽然实用起来比较简单,但存在很多问题,真实场景一般也不会使用;

  • 性能问题:每个节点从开始事务到commit/rollback整个过程相对较长,整个过程节点都会阻塞,数据库节点也都会占用着数据库资源。
  • 事务管理器单点故障:一旦事务管理器出现故障,整个系统不可用
  • 数据不一致:如果事务管理器在阶段二只发送了部分的 commit 消息,此时网络出现故障,那么就只有部分参与者收到了 commit 消息,从而使得节点数据不一致的情况。

相对于2PC,3PC 主要解决了单点故障的问题,但还是无法完全解决数据不一致的问题,在这就不展开继续介绍了。

TCC

TCC(Try-Confim-Concel):提出了一种新的事务模型,基于业务层面的事务定义,在业务层面进行事务粒度的控制,将整个过程分为了两个阶段,Try 和 Confim/Cancel,每个阶段的逻辑都由业务代码进行控制,避免长事务,从而提高性能;TCC事务模型包含三个部分:

  • 主业务服务:整个业务流程的发起方,复杂业务流的编排和发起工作
  • 业务参与方服务:也就是主业务请求接收方,需要提供 try/confim/cancel接口
  • 事务协调器:记录管理整个业务活动,包含TCC的整个全局事务状态以及每个参与方业务的子事务状态。

  • Try阶段: 完成所有业务逻辑的检查,预留必要的业务资源(比如下单操作先在库存服务中冻结预留需要的数量)并提交事务
  • Confirm阶段: 执行真正业务,不做任何业务检查,Confirm 阶段应该满足幂等性,Confirm 失败后应该需要进行重试(对预处理的数据做正式处理)
  • Cancel阶段: 取消执行,释放 Try 阶段预留的业务资源,该阶段也应该满足幂等性(回滚预留资源)

因为已经在事务协调器中记录了全局事务和每个参与者的事务状态,也就意味着即使某个步骤失败了,TCC也可以根据事务日志进行重试,这也就需要对应的 confirm/cancel 接口支持幂等。

TCC 可以很好的保证分布式事务要么全部成功,要们全部回滚,且在出现失败时也能通过补偿实现最终一致。但整体对业务的侵入性非常强,需要编写大量的代码来完成TCC的功能。

本地消息表

本地消息表核心就是将分布式事务转换成本地事务进行处理,引入中间状态来实现最终一致性,避免引入分布式事务带来的复杂性。也是业务使用的比较多的分布式事务解决方案。

订单服务

  • 将需要调用支付服务的信息先保存在本地消息表中,使得更新订单状态和写消息在一个本地事务中
  • 通过引入 退款中 状态来实现BASE软状态,达到最终一致性
  • 引入定时器定时的从消息表中获取到待处理的消息,并将消息推送到MQ种供消费者消费

支付服务:

  • MQ为了保证消息不回丢失,所以对于消息的处理至少会发送一次,也就是说对于支付服务(consumer)需要保证幂等性

引入了消息表就是为了保证更新操作和消息保存是原子性的,不引入消息表是否也能实现这个功能呢?Rocket MQ 就可以支持事务消息(避免使用消息表)

Seata

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案 其中:

  • TC (Transaction Coordinator) - 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
  • TM (Transaction Manager) - 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  • RM (Resource Manager) - 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

下文主要介绍 AT 和 SAGA 模式

AT 模式

AT 模式通过引入写入 before image 和 after image 到 undo log 表来实现分布式事务自动回滚,整个 AT 还是基于两阶段提交协议的演进:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
    • 提交异步化,非常快速地完成。
    • 回滚通过一阶段的回滚日志进行反向补偿。

其中 :

  • before image 是根据 SQL 解析,将业务更新的 SQL 做了一个反向解析生成的一条查询语句,并将查询结果保存,用于在 rollback 的时候做数据比对。

//比如要执行的业务sql
update product set name = 'GTS' where name = 'TXC';

//通过SQL解析,生成的 before image 就是
select id, name, since from product where name = 'TXC';
  • after image 则是通过 before image 的结果通过主键定位的数据
select id, name, since from product where id = 1;
  • 保存 undo log:将 before image 和 after image 信息保存到 undo_log 表中

阶段二

  • commit:如果在第10步 TM 发起全局 commit,那么 TC 就会通知所有的 RM 进行 commit 操作,RM 直接删除本地数据库中的 undo log 即可
  • rollback:当 RM 收到来自 TC 的回滚请求,RM会开启一个本地事务
    1. 数据校验:拿 UNDO LOG 中的后镜与当前数据进行比较,如果有不同,说明数据被当前全局事务之外的动作做了修改。这种情况,需要根据配置策略来做处理
    2. 如果校验通过,根据 UNDO LOG 中的前镜像和业务 SQL 的相关信息生成并执行回滚的语句:

Saga 模式

Saga模式是 Seata 提供的长事务解决方案,在 Saga 模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。适应于业务流程比较长或者参与者服务无法提供 TCC 模式的要求。

Seata 提供的 Sage 模式是基于状态机引擎来实现

  1. 通过状态图来定义服务调用的流程并生成 json 状态语言定义文件
  2. 状态图中一个节点可以是调用一个服务,节点可以配置它的补偿节点
  3. 状态图 json 由状态机引擎驱动执行,当出现异常时状态引擎反向执行已成功节点对应的补偿节点将事务回滚
  4. 可以实现服务编排需求,支持单项选择、并发、子流程、参数转换、参数映射、服务执行状态判断、异常捕获等功能

总结

一致性复杂性侵入性局限性性能维护成本
2PC
TCC
本地消息表
AT 模式
Saga 模式