分布式事务
什么是分布式事务
分布式事务就是指事务的资源分别位于不同的分布式系统的不同节点之上的事务;
分布式事务产生的原因
-
2.1、数据库分库分表
在单库单表场景下,当业务数据量达到单库单表的极限时,就需要考虑分库分表,将之前的单库单表拆分成多库多表;
分库分表之后,原来在单个数据库上的事务操作,可能就变成跨多个数据库的操作,此时就需要使用分布式事务; -
2.2、业务服务化
业务服务化即业务按照面向服务(SOA)的架构拆分整个网站系统;
比如互联网金融网站SOA拆分,分离出交易系统、账务系统、清算系统等,交易系统负责交易管理和记录交易明细,账务系统负责维护用户余额,所有的业务操作都以服务的方式对外发布;
一笔金融交易操作需要同时记录交易明细和完成用户余额的转账,此时需要分别调用交易系统的交易明细服务和账务系统的用户余额服务,这种跨应用、跨服务的操作需要使用分布式事务才能保证金融数据的一致性;
分布式事务原理简介
数据库本地事务(ACID)
说到数据库事务就不得不说,数据库事务中的四大特性 ACID:
A:原子性(Atomicity),一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
就像你买东西要么交钱收货一起都执行,要么发不出货,就退钱。
C:一致性(Consistency),事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。
如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。
如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。
I:隔离性(Isolation),指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。
由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
打个比方,你买东西这个事情,是不影响其他人的。
D:持久性(Durability),指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。
即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。
打个比方,你买东西的时候需要记录在账本上,即使老板忘记了那也有据可查。
分布式事务的基础
从上面来看分布式事务是随着互联网高速发展应运而生的,这是一个必然。
我们之前说过数据库的 ACID 四大特性,已经无法满足我们分布式事务,这个时候又有一些新的大佬提出一些新的理论。
CAP 理论
在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)3 个要素最多只能同时满足两个,不可兼得。其中,分区容忍性又是不可或缺的。
-
一致性:分布式环境下,多个节点的数据是否强一致。
-
可用性:分布式服务能一直保证可用状态。当用户发出一个请求后,服务能在有限时间内返回结果。
-
分区容忍性:特指对网络分区的容忍性。
举例:Cassandra、Dynamo 等,默认优先选择 AP,弱化 C;HBase、MongoDB 等,默认优先选择 CP,弱化 A。
BASE 理论
核心思想:
-
基本可用(Basically Available):指分布式系统在出现故障时,允许损失部分的可用性来保证核心可用;
-
软状态(Soft state):指允许分布式系统存在中间状态,该中间状态不会影响到系统的整体可用性;
-
最终一致性(Eventual consistency):指分布式系统中的所有副本数据经过一定时间后,最终能够达到一致的状态;
1.原子性(A)与持久性(D)必须根本保障;
2.为了可用性、性能与降级服务的需要,只有降低一致性( C ) 与 隔离性( I ) 的要求;
3.酸碱平衡(ACID-BASE Balance) BASE 是对 CAP 中 AP 的一个扩展
一致性模型
数据的一致性模型可以分成以下三类:
-
强一致性:数据更新成功后,任意时刻所有副本中的数据都是一致的,一般采用同步的方式实现。
-
弱一致性:数据更新成功后,系统不承诺立即可以读到最新写入的值,也不承诺具体多久之后可以读到。
-
最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺立即可以返回最新写入的值,但是保证最终会返回上一次更新操作的值。
分布式系统数据的强一致性、弱一致性和最终一致性可以通过 Quorum NRW 算法分析。
X/OpenDTP
X/Open DTP(X/Open Distributed Transaction Processing Reference Model),是X/Open 这个组织定义的一套分布式事务的标准。
X/Open了定义了规范和API接口,由这个厂商进行具体的实现,这个标准提出了使用二阶段提交(2PC – Two-Phase-Commit)来保证分布式事务的完整性,如下图所示
X/Open DTP模型定义了三个角色和两个协议,其中三个角色分别如下:
-
AP(Application Program),表示应用程序,也可以理解成使用DTP模型的程序
-
RM(Resource Manager),资源管理器,这个资源可以是数据库, 应用程序通过资源
管理器对资源进行控制,资源管理器必须实现XA定义的接口
- TM(Transaction Manager),表示事务管理器,负责协调和管理全局事务,事务管理
器控制整个全局事务,管理事务的生命周期,并且协调资源。
两个协议分别是:
-
XA协议: XA 是X/Open DTP定义的资源管理器和事务管理器之间的接口规范,TM用它来通知和协调相关RM事务的开始、结束、提交或回滚。目前Oracle、Mysql、DB2都提供了对XA的支持; XA接口是双向的系统接口,在事务管理器(TM)以及多个资源管理器之间形成通信的桥梁(XA不能自动提交)
XA协议的语法,主流的数据库都支持 XA协议,从而能够实现跨数据库事务。
XA {START|BEGIN} xid [JOIN|RESUME] --负责开启或者恢复一个事务分支,并且管理XID到调用线程
XA END xid [SUSPEND [FOR MIGRATE]] --负责取消当前线程与事务分支的关联
XA PREPARE xid --负责询问RM 是否准备好了提交事务分支
XA COMMIT xid [ONE PHASE] --知RM提交事务分支
XA ROLLBACK xid --通知RM回滚事务分支
XA RECOVER [CONVERT XID]
- TX协议: 全局事务管理器与资源管理器之间通信的接口
常见的分布式事务解决方案
- 两阶段提交方案/XA方案
XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
为了实现多个数据库的事务一致性,就必然需要引入第三方节点来进行事务协调,如下图所示。
通过一个全局的分布式事务协调工具,来实现多个数据库事务的提交和回滚,在这样的架构下,事务的管理方式就变成了两个步骤。
- 第一个步骤,开启事务并向各个数据库节点写入事务日志。
- 第二个步骤,根据第一个步骤中各个节点的执行结果,来决定对事务进行提交或者回滚。
这就是所谓的2PC提交协议。
2PC提交流程如下:
-
表决阶段:此时 TM(协调者)向所有的参与者发送一个 事务请求,参与者在收到这请求后,如果准备好了(写事务日志)就会向 TM发送一个执行成功 消息作为回应,告知TM自己已经做好了准备,否则会返回一个失败消息;
-
提交阶段:TM 收到所有参与者的表决信息,如果所有参与者一致认为可以提交事务,那么TM就会发送提交消息,否则发送回滚消息;对于参与者而言,如果收到提交消息,就会提交本地事务,否则就会取消本地事务。
总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换回导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。
-
消息事务 + 最终一致性
所谓的消息事务就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性,具体原理如下:
首先A系统需要向MQ中发送prepare消息,然后执行A系统的业务,写入数据库成功之后向MQ发送confirm消息,当消息为confirm状态时,B系统就可以消费到消息,消费成功之后返回ACK确认消息给MQ。
需要注意的是:需要保证B系统消费消息的幂等性。
消息重复投递的幂等性保障!
在可靠性消息投递过程中,由于MQ的重试机制,有可能会出现消费者重复收到同一个消息的情况。
因此,我们需要保证消息投递的幂等性,所谓的幂等性,就是MQ重复调用多次产生的业务结果与调用一次产生的业务结果相同;
-
数据库唯一约束实现幂等
-
通过tokenid的方式去识别每次请求判断是否重复
-
redis中的setNX
-
状态机幂等性
-
本地消息表生成md5实现唯一约束
-
ZK基于分布式锁的原理,创建节点,重复消费消息,创建失败
-
TCC (Try-Confirm-Cancel)补偿模式(最终一致性)
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
它分为三个阶段:
1.Try 阶段主要是对业务系统做检测及资源预留
2.Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
3.Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
举例(Bob 要向 Smith 转账):
首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
如果第2步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优点:跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点:缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。
-
最大努力通知
业务发起方将协调服务的消息发送到MQ,下游服务接收此消息,如果处理失败,将进行重试,重试N次后依然失败,将不进行重试,放弃处理,这个应用场景要求对事物性要求不高的地方。
-
基于本地消息表实现重发
本地消息表这个方案最初是 eBay提出的,此方案的核心是通过本地事务保证数据业务操作和消息的一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再 将消息删除。
下面以注册送积分为例来说明:下例共有两个微服务交互,用户服务和积分服务,用户服务负责添加用户,积分服务负责增加积分。
总结
单数据库事务完全遵循ACID规范,属于刚性事务,分布式事务要完全遵循ACID规范比较困难, 分布式事务属于柔性事务,满足BASE理论;
BASE描述: BA(Basic Availability 基本业务可用性)、S(Soft state 柔性状态)、E(Eventual consistency 最终一致性);
柔性事务对ACID的支持:
1、原子性:严格遵循;
2、一致性:事务完成后的一致性严格遵循,事务中的一致性可适当放宽;
3、隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽;
4、持久性:严格遵循;
为了可用性、性能的需要,柔性事务降低了一致性(C)与隔离性(I) 的要求,即“基本可用,最终一致”;
柔性事务的分类
柔性事务分为:两阶段型、补偿型、异步确保型、最大努力通知型;
-
两阶段型
就是分布式事务两阶段提交,对应技术上的XA、JTA/JTS,这是分布式环境下事务处理的典型模式。
-
补偿型
TCC型事务(Try/Confirm/Cancel)可以归为补偿型;TCC思路是:尽早释放锁;在Try成功的情况下,如果事务要回滚,Cancel将作为一个补偿机制,回滚Try操作;
TCC各操作事务本地化,且尽早提交 (放弃两阶段约束);当全局事务要求回滚时,通过另一个本地事务实现“补偿”行为;
TCC是将资源层的两阶段提交协议转换到业务层,成为业务模型中的一部分;
-
异步确保型
将一些同步阻塞的事务操作变为异步的操作,避免对数据库事务的争用;比如消息事务机制;
-
最大努力通知型
通过通知服务器(消息通知)进行,允许失败,有补充机制;