0%

分布式事务

如果本文有错,希望在下面的留言区指正。

在之前的一篇文章中,写了一篇 Spring 事务管理

本地事务管理主要考虑的是单台服务器上面的事务回滚。那么,如果在分布式服务之间如何管理事务呢?

如上图所示的分布式调用,客户端 client 调用服务A,而服务A 除了本身的对数据库的修改,还需要调用服务B和服务C。如果在调用服务B成功后在调用服务C,但是这时候服务C出错了,这时候如何解决事务的回滚?

CAP

CAP 理论是分布式系统的一个基础理论,描述任何一个分布式系统最多只能满足下面三种特性中的两个:

  • 一致性(Consistency):对某个指定的客户端来说,读操作保证能够返回最新的写操作结果。
  • 可用性(Availability):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。
  • 分区容忍性(Partition tolerance):当出现网络分区后,系统能够继续“履行职责”。

CAP 的选择

虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,对于一个分布式系统而言,分区容错性可以说是一个最基本的要求。分布式系统的组件必然是需要部署到不同节点,否则也就无所谓分布式系统了,因此必然出现子网络。而对于分布式系统而言,网络问题有时一个必定会出现的异常情况,因此分区容错性也就成为一个分布式系统必然需要面对和解决的问题了。

我们会发现必须选择 P(分区容忍)要素,因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因为 A 要求返回 no error 和 no timeout。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。

CAP 关注的粒度是数据,而不是整个系统。在实际设计过程中,每个系统不可能处理一种数据,而是包含多种数据,有的数据必须选择CP,有的数据必须选择AP。所以,我们在设计的时候,不能从整个系统去选择CP 还是AP。

CAP 是忽略网络延迟的。在分布式环境下,数据在不同服务器复制,是存在延迟的。如果是相同机房,耗时时间可能是几毫秒;如果是跨地域的机房,可能就是十几毫秒。这以为着,CAP 理论中的 C 在实践中是不可能完美实现的,在数据复制的过程中,节点A 和节点B 的数据并不一致。

正常运行情况下,不存在 CP 和 AP 的选择,可以同时满足 CA。

BASE

BASE 思想解决了 CAP 提出的分布式系统的一致性和可用性不可兼容的问题。BASE 思想与 ACID 原理完全不同,它满足于 CAP 原理,通过牺牲强一致性获取可用性,一般应用于服务化系统的应用层或者大数据处理系统中,通过达到最终一致性来尽量满足业务的绝大多数需求。

BASE 模型包含以下三个元素:

  • BA:Basically Available,基本可用
  • S:Soft state,软状态,状态可以在一段时间内不同步
  • E:Eventually consistent,最终一致性,在一定的时间窗口内,最终数据达到一致性即可。

目前的分布式事务的解决方案

两阶段提交 2PC

2PC 又称 XA Transactions,XA 是一个两阶段提交协议,该协议分为以下两个阶段:

  • 第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.
  • 第二阶段:事务协调器要求每个数据库提交数据。

补偿事务 TCC

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC模型是把锁的粒度完全交给业务处理。它分为三个阶段:

  • Try 阶段主要是对业务系统做检测及资源预留
  • Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
  • Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

基于 TCC 源码

本地消息表

本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:

消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。

消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。

生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。

这种方案遵循BASE理论,采用的是最终一致性,笔者认为是这几种方案里面比较适合实际业务场景的,即不会出现像2PC那样复杂的实现(当调用链很长的时候,2PC的可用性是非常低的),也不会像TCC那样可能出现确认或者回滚不了的情况。

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET 中 有现成的解决方案。

缺点: 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

MQ 事务

有一些第三方的MQ是支持事务消息的,比如RocketMQ,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

阿里的 GTS

阿里的 GTS 没有开源,需要购买它的产品,同时需要配套的分布式数据库。

Reference

客官,赏一杯coffee嘛~~~~