目录
前言
分布式事务方案
强一致性
2PC 两阶段提交(XA事务,阻塞)
3PC三阶段提交(非阻塞,引入超时和准备阶段)
TCC模式-本质也是2PC
Saga模式
最终一致性(BASE理论)
本地消息表
MQ消息队列
Paxos
Raft
ZAB 协议 ( Zookeeper Atomic Broadcast) 原子广播协议
总结
如果只有一个数据库,所有的逻辑都在一个db完成,那么本地事务很简单就可以处理。但是,在微服务架构中,功能服务化,服务拆分化,一个业务逻辑很可能需要多个service完成,每个service操作不同的数据库,分布式事务需要一套方案来实现,本文就来集中讲述一下分布式事务的常见方案。
比如我们支付宝余额转入到余额宝,支付宝余额和余额宝是不同的服务,再比如跨行转账,从你的工商银行账户A转1000到建设银行账户B,比如订单系统和库存系统,其实有些情况下不一定要利用分布式事务,能避免尽力避免,主要看业务场景的需要,究竟是强一致性,弱一致性,还是最终一致性。
电商系统常见的例子:订单支付的时候使用红包或者优惠券,必需同时成功或者失败
常见对分布式事务场景:
2PC协议:一种协议,在分布式系统保证事务的原子提交
XA:分布式事务规范
两阶段提交是处理分布式事务的经典方法。对数据库分布式事务有了解的同学一定知道数据库支持的2PC,又叫做 XA Transactions。
MySQL从5.5版本开始支持,SQL Server 2005 开始支持,Oracle 7 开始支持
主要通过增加事务协调者,对事务进行全局管理。
第一个阶段:
发出“准备”命令,所有事务参与者接受指令后进行资源准备,锁准备,undo log准备。如果都返回“准备成功”,如果不能执行,返回终止。
第二个阶段
协调者接受到第一个阶段的回复
Coordinator Cohort
QUERY TO COMMIT
-------------------------------->
VOTE YES/NO prepare*/abort*
<-------------------------------
commit*/abort* COMMIT/ROLLBACK
-------------------------------->
ACKNOWLEDGMENT commit*/abort* ------这个阶段如果执行失败怎么办?2PC没有处理
<--------------------------------
end
优点:
缺点:
开源实现:
优点:降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务。
缺陷:脑裂问题依然存在,即在参与者收到PreCommit请求后等待最终指令,如果此时协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
TCC模式本质也是2PC,只是TCC在应用层控制,数据库只是负责第一个阶段。XA在数据库层控制两阶段提交。
严格遵守ACID的分布式事务我们称为刚性事务,而遵循BASE理论(基本可用:在故障出现时保证核心功能可用,软状态:允许中间状态出现,最终一致性:不要求分布式事务打成中时间点数据都是一致性的,但是保证达到某个时间点后,数据就处于了一致性了)的事务我们称为柔性事务,其中TCC编程模式就属于柔性事务。
TCC是对二阶段的一个改进,try阶段通过预留资源的方式避免了同步阻塞资源的情况,但是TCC编程需要业务自己实现try,confirm,cancle方法,对业务入侵太大,实现起来也比较复杂。
分布式事务的基本原理本质上都是两阶段提交协议(2PC),TCC (try-confirm-cancel)其实也是一种 2PC,只不过 TCC 规定了在服务层面实现的具体细节,即参与分布式事务的服务方和调用方至少要实现三个方法:try 方法、confirm 方法、cancel 方法。
Saga模式是现实中可行的方案,采用事务补偿机制。每个本地事务都存储一个副本,如果出现失败,则利用补偿机制回滚。
参考:https://github.com/eventuate-tram/eventuate-tram-sagas
除了Saga模式,我们也有折中的方法,就是同步提交一个事务,异步通知其它数据源更新数据。这里我们放弃了事务参与者要么不执行,要么全部执行,采用异步方式达到最终一致性
ebay提出的方案,结构如下:
我们可以利用消息队列(消息服务有等级:最多一次,至少一次,只有一次),异步操作,实现最终一致性。在用消息队列的时候,注意以下几点:
最常见的两种服务等级就是 At-Most-Once 和 At-Least-Once,前者能够保证发送方不对接收方是否能收到消息作保证,消息要么会被投递一次,要么不会被投递,这其实跟一次普通的网络请求没有太多的区别;At-Least-Once 能够解决消息投递失败的问题,它要求发送者检查投递的结果,并在失败或者超时时重新对消息进行投递,发送者会持续对消息进行推送,直到接受者确认消息已经被收到,相比于 At-Most-Once,At-Least-Once 因为能够确保消息的投递会被更多人使用。
除了这两种常见的服务等级之外,还有另一种服务等级,也就是 Exactly-Once,这种服务等级不仅对发送者提出了要求,还对消费者提出了要求,它需要接受者对接收到的所有消息进行去重,发送者和接受者一方对消息进行重试,另一方对消息进行去重,两者分别部署在不同的节点上,这样对于各个节点上的服务来说,它们之间的通信就是 Exactly-Once 的,但是需要注意的是,Exacly-Once 一定需要接收方的参与。
我们可以通过实现 AMQP 协议的消息队列来实现分布式事务,在协议的标准中定义了 tx_select
、tx_commit
和 tx_rollback
三个事务相关的接口,其中 tx_select
能够开启事务,tx_commit
和 tx_rollback
分别能够提交或者回滚事务。支持事务消息的MQ有rocketMQ,但是rabbitmq和kafka不支持
使用消息服务实现分布式事务在底层的原理上与其他的方法没有太多的差别,只是消息服务能够帮助我们实现的消息的持久化以及重试等功能,能够为我们提供一个比较合理的 API 接口,方便开发者使用
所有的分布式一致性算法都是paxos算法的部分,只有paxos算法才能完美实现分布式一致性。
Raft和Paxos都是分布式一致性算法,Paxos提出更早,但是很难以理解,所以从实用性和落地性,提出了新的分布式一致性算法Raft,很多的开源框架都是基于者两个算法。具体原理参考下面的连接动画:
http://thesecretlivesofdata.com/raft/
https://zhukeyao.wordpress.com/2016/11/15/understanding-paxosraftzab-algorithm/
分布式事务的实现方式是分布式系统中非常重要的一个问题,在微服务架构和 SOA 大行其道的今天,掌握分布式事务的原理和使用方式已经是作为后端开发者理所应当掌握的技能,从实现 ACID 事务的 2PC 与 3PC 到实现 BASE 补偿式事务的 Saga,再到最后通过事务消息的方式异步地保证消息最终一定会被消费成功,我们为了增加系统的吞吐量以及可用性逐渐降低了系统对一致性的要求。
在业务没有对一致性有那么强的需求时,作者一般会使用 Saga 协议对分布式事务进行设计和开发,而在实际工作中,需要强一致性事务的业务场景几乎没有,我们都可以实现最终一致性,在发生脑裂或者不一致问题时通过补偿的方式进行解决,这就能解决几乎全部的问题。