分布式事务
单个数据库的性能产生瓶颈的时候,我们可能对数据库进行分区,这里所说的分区是指物理分区,分区之后可能不同的库就处于不同的服务器上了,这个时候单个数据库的ACID
已经不能适应这种情况了,而在这种ACID
的集群环境下,再想保证集群的ACID
会导致我们的系统变得很差,这个时候我们需要引入CAP
原则。
一致性(Consistency
) : 客户端知道一系列的操作都会同时发生(生效)
可用性(Availability
) : 每个操作都必须以可预期的响应结束
分区容错性(Partition tolerance
) : 即使出现单个组件无法可用,操作依然可以完成
具体地讲在分布式系统中,在任何数据库设计中,一个Web
应用至多只能同时支持上面的两个属性。显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。
[了解]
① 2PC的概念
数据库支持2PC
(两阶段提交),又叫XA Transactions
,MySQL5.5
、SQL Server2005
、Oracle7
开始支持。
其中,XA
是一个两阶段提交协议,该协议分为以下两个阶段:
1、第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit
)此操作,并反映是否可以提交。
2、第二阶段:事务协调器要求每个数据库提交数据。
如果任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在次数据库中的那部分信息。这里满足了一致性,那么可用性将会受到影响。
② base理论
在分布式系统中,我们往往追求的是可用性,它的重要程度比一致性要高,那么如何实现高可用性呢?那就是BASE
理论,它用来对CAP
定理进一步的扩充:
-
Basically Available
(基本可用) -
Soft state
(软状态) -
Eventually consistent
(最终一致性)
二、分布式事务解决方案
①两阶段提交(2PC
)
见上,就是有一个预提交的过程,两阶段提交就是使用XA
协议的原理
优点: 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)
缺点: 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景。
②补偿事务(TCC
)
其核心思想是:针对每一个操作,都要注册一个与其对应的确认和补偿(撤销)操作,它分为三个阶段:
· Try
阶段主要是对业务系统做检测及资源预留。
· Confirm
阶段主要是对业务系统做确认提交,Try
阶段执行成功并开始执行Confirm
阶段时,默认Confirm
阶段时不会出错的。即Try
成功,Confirm
一定成功。
· Cancel
阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
【示例】
假如Bob
要向Smith
转账,思路大概是:我们有一个本地方法,里面依次调用
1.首先在Try
阶段,要先调用远程接口把Smith
和Bob
的钱给冻结起来。 2.在Confirm
阶段,执行远程调用的转账的操作,转账成功进行解冻。
如何第2不步执行成功,那么转账成功,如果第二部执行失败,则调用远程冻结接口对应的解冻方法。
优点: 跟2PC
比起来,实现以及流程相对简单了一些,但数据的一致性比2PC
也要差一些
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC
属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC
不太好定义及处理。
③本地消息表(异步确保)
本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思想是来源于ebay
。我们可以从下面的流程图中看出其中的一些细节:
消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。如果消息发送失败,会进行重新发送。
消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成了,如果处理失败,那么就会重新执行。如果是业务上面的失败,可以给生产者发送一个业务补偿消息,通知生产方进行回滚等操作。
生产方和消费方定时扫描本地消息表,把还没有处理的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
这种方案遵循BASE
理论,采用的是最终一致性,笔者认为是这种方案里面比较适合实际业务场景的,即不会出现像2PC
那样的复杂的实现(当调用链很长的时候,2PC
的可用性是非常低的),也不会像TCC
那样可能出现确认或者回滚不了的情况。
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
④MQ
事务消息
有一些第三方的MQ
是支持事务消息的,比如RocketMQ
,他们支持事务消息的方式也是类似于采用的二阶段提交,但市面上一些主流MQ
不支持事务消息的,如RabbitMQ
、Kafka
。
如阿里的RocketMQ
第一阶段Prepared
消息,会拿到消息的地址。
第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要向消息队列提交两次消息,一次发送消息和一次确认消息。如果确认发送消息失败了RocketMQ
会定期扫描消息集群中的事务消息,这时候发现了Prepared
消息,它会想消息发送者确认,所以生产方需要实现一个check
接口,RocketMQ
会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
优点: 实现了最终一致性,不需要依赖本地数据库事务。
缺点: 实现难度大,主流MQ