今天,我们来聊聊分布式事务

前言

我们都知道数据库的事务满足"ACID"特性,A是指事务的原子性,C是指事务的一致性,I指事务的隔离性,D指持久性。

最开始我们的数据量都很小,所有的数据都落在一个数据库中。MySQL数据库单表的最大数据量在百万条左右,随着系统变大,数据越来越多,这个时候我们不得不将数据分布在不同的数据库中存放,也就是常说的数据分片(sharding)。我们可以通过一定的分库策略将同一个交易链路上的数据放到一个数据库中,例如我们可以将一个订单所有产生的数据放到一个数据库中,按照订单号来分库,这样我们在生成订单相关数据的时候可以在单个数据库上开启事务来完成。

这样看似完美的解决方案其实并不完美。例如我们想记录用户维度的订单数据时,这个方案就无能为力了。于是分布式事务应运而生。

分布式系统CAP BASE理论

说到分布式系统,不得不说CAP和BASE理论,它是指导我们分布式系统的理论基础。

CAP理论

加州大学伯克利分校Eric Brewer教授支持一个分布式系统无法满足这样三条特性:

Consistency,一致性:多个操作同时生效,不会出现部分生效的情况

Availability,可用性:客户端的每个请求在服务端能够正确被响应

Partition tolerance,分区容错性:分区中部分节点挂了不会影响整体服务可用性,这也是分布式系统最基本的要求

分区容错性是一个分布式系统最基本的要求,因此一般分布式系统都会满足分区容错性,否则就失去了分布式系统的意义。分布式系统一般会在一致性和可用性上做出取舍。例如牺牲一致性换区可用性,这里说的牺牲一致性是指牺牲掉系统的"强一致性",最终我们的系统还是一致的,即所谓的"弱一致性"或者"最终一致性"。当我们的系统需要保证强一致性时我们不得不牺牲掉可用性:当系统部分节点延迟或者down机整个系统的服务将变得不可用,也因此系统数据是强一致性的。

BASE理论

BASE理论是对CAP理论的进一步扩充:

Basically Available(基本可用)

Soft state(软状态)

Eventually consistent(最终一致性)

基本可用是指我们的系统无法做到百分百可用,但是可以保证例如"3个9"(99.9%)可用。软状态是指允许我们的系统存在中间状态,在最终我们的系统可以达到最终一致性。BASE理论强调的是系统的最终一致性。

分布式事务

目前分布式事务的解决方案主要有:二阶段提交(2PC)、柔性补偿事务(TCC)、本地消息表(异步确保)、MQ事务消息等。

二阶段提交

二阶段事务分为两个阶段:阶段一事务协调者通知各个节点执行事务预提交;阶段二协调者根据各个节点的响应来通知各个节点提交或者回滚事务操作。

两阶段提交这种解决方案属于牺牲了一部分可用性来换取的一致性。

优点: 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)

缺点: 实现复杂,牺牲了可用性,同步阻塞对性能影响较大,不适合高并发高性能场景。

补偿事务(TCC)

TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:

Try 阶段主要是对业务系统做检测及资源预留

Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。

Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

举个例子,假入 Bob 要向 Smith 转账,思路大概是:

我们有一个本地方法,里面依次调用

首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。

在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。

如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。

优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些

缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

本地消息表(异步确保)

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

基本思路就是:

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

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

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

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

优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。

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

MQ事务消息

有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。

以阿里的 RocketMQ 中间件为例,其思路大致为:

第一阶段Prepared消息,会拿到消息的地址。

第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。

也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。

优点: 实现了最终一致性,不需要依赖本地数据库事务。

缺点: 实现难度大,主流MQ不支持。

MQ事务消息

总结

目前分布式事务主要有:二阶段提交、TCC柔性事务、本地消息表(异步确保)和MQ事务消息等解决方案。二阶段提交可以确保数据的强一致性,其他的解决方案属于最终一致性解决方案。

分布式事务虽然能保证数据的强一致性,但是性能低。使用前需要确定是否真的需要分布式事务。

现在加群即可获取Java工程化、高性能及分布式、高性能、高架构、zookeeper、性能调优、Spring、MyBatis、Netty源码分析和大数据等多个知识点高级进阶干货的直播免费学习权限及相关视频资料,群号:923116658

点击链接加入群聊【Java架构解析】:https://jq.qq.com/?_wv=1027&k=5e1QsXb

推荐阅读更多精彩内容