分布式事物梳理

分布式事务

数据库事务要知足几个要求:ACIDjava

Atomic(原子性) 事务必须是原子的工做单元sql

Consistent(一致性) 事务完成时,必须使全部数据都保持一致状态数据库

Isolation(隔离性) 并发事务所作的修改必须和其余事务所作的修改是隔离的编程

Duration(持久性) 事务完成以后,对系统的影响是永久性的网络

Mysql里的事务处理过程架构

  1. 记录redo(记录数据修改以前原始记录)和undo log文件,确保日志在磁盘上的持久化
  2. 更新数据记录
  3. 提交事务 ,redo 写入commit记录

事物处理完删除undo文件,更新redo文件并发

分布式事务

缘由

数据库分库分表

img

SOA化

拆分出来,每个模块是独立的工程,可以缩短开发周期回归测试app

img

​ 在分布式系统中,每个机器节点虽然都能明确的知道本身执行的事务是成功仍是失败,可是却没法知道其余分布式节点的事务执行状况。所以,当一个事务要跨越多个分布式节点的时候(好比,下单流程,下单系统和库存系统可能就是分别部署在不一样的分布式节点中),为了保证该事务能够知足ACID,就要引入一个协调者(Cooradinator)。其余的节点被称为参与者(Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。框架

X/OpenDTP事务模型

X/Open Distributed Transaction Processing Reference Model 异步

X/Open是一个组织机构,定义出的一套分布式事务标准, 定义了规范的API接口

2PC(two -phase-commit), 用来保证分布式事务的完整性

J2EE 遵循了X/open DTP规范,设计并实现了java里面的分布式事务编程接口规范-JTA

XA是X/Open DTP定义的中间件与数据库之间的接口规范。 XA接口函数由数据库厂商提供

X/OpenDTP 角色

image-20190318223350760

AP application :触发分布式事物指令 (应用程序)

RM resouces manager 资源管理器。 通常表示数据库,必须实现XA定义的接口

TM transaction manager 事务管理器,负责协调和事务管理

2PC(two -phase-commit)

  • 提交事务请求(投票)
  • 执行事务请求(提交或中断)

阶段一:提交事务请求(投票)

  1. TM向全部的AP发送事务内容,询问是否能够执行事务的提交操做,并等待各个AP的响应
  2. 执行事务

各个AP节点执行事务操做,将undo和redo信息记录到事务日志中,尽可能把提交过程当中所消耗时间的操做和准备都提早完成后确保后续

事务提交的成功率

  1. 各个AP向TM反馈事务询问的响应

各个AP成功执行了事务操做,那么反馈给TM yes的response;若是AP没有成功执行事务,就反馈TM no的response

阶段二:执行事务提交

详解

执行提交事务

img

​ 假设一个事务的提交过程总共须要30s, 其中prepare操做须要28(事务日志落地磁盘及各类io操做),而真正commit只须要2s

​ 那么,commit阶段发生错误的几率和prepare相比, 2/28 (<10%) .只要第一个阶段成功,那么commit阶段出现失败的几率就很是小

​ 大大增长了分布式事务的成功几率

中断事务提交

img

2pc的数据一致性问题

  • 数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求以后,发生了局部网络异常或者在发送commit请求过程当中协调者发生了故障,这回致使只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求以后就会执行commit操做。可是其余部分未接到commit请求的机器则没法执行事务提交。因而整个分布式系统便出现了数据部一致性的现象。
  • 同步阻塞问题。执行过程当中,全部参与节点都是事务阻塞型的。当参与者占有公共资源时,其余第三方节点访问公共资源不得不处于阻塞状态
  • 二阶段没法解决的问题:协调者在发出commit消息以后宕机,而惟一接收到这条消息的参与者同时也宕机了。那么即便协调者经过选举协议产生了新的协调者,这条事务的状态也是不肯定的,没人知道事务是否被已经提交
  • 单点故障。因为协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去

3PC(three phase commit)

阶段一:canCommit

​ 询问,按照全部人的返回决定是否能够执行操做

阶段二:preCommit

​ 预执行,若是超时认为执行失败

阶段三:doCommit

​ 若是超时了,仍是会真正执行

改进点

  • 增长了超时机制
  • 第二阶段,若是协调者超时没有接受到参与者的反馈,则自动认为失败,发送abort命令
  • 第三阶段,若是参与者超时没有接受到协调者的反馈,则自动认为成功开始提交事务(基于几率)

3pc的问题

​ 相对于2PC,3PC主要解决的单点故障问题,并减小阻塞,由于一旦参与者没法及时收到来自协调者的信息以后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。可是这种机制也会致使数据一致性问题,由于,因为网络缘由,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时以后执行了commit操做。这样就和其余接到abort命令并执行回滚的参与者之间存在数据不一致的状况。

XA/JTA

​ XA 就是 X/Open DTP 定义的事务管理器与资源管理器的接口规范(即接口函数),XA 接口函数由数据库厂商提供。

​ JTA是基于X/Open DTP模型开发的java transaction APi规范

概述

​ 经过2pc的方式去完成分布式事务,虽然经过这种方式可以达到预期的效果,可是咱们在现实中不多会用到2pc方式的提交的XA事务,有几个缘由

  1. 互联网电商应用的快速发展,对事务和数据的绝对一致性要求并无传统企业应用那么高
  2. XA事务的介入增长了TM中间件,使得系统复杂化
  3. XA事务的性能不高,由于TM要等待RM回应,因此为了确保事务尽可能成功提交,等待超时的时间一般比较长,好比30s到几分钟,若是RM出现故障或者响应比较慢,则整个事务的性能严重降低

互联网的分布式事务解决方案

​ 目前互联网领域里有几种流行的分布式解决方案,但都没有像以前所说的XA事务同样造成X/OpenDTP那样的工业规范,而是仅仅在具体的行业里得到较多的承认

业务接口整合,避免分布式事务

​ 这个方案就是把一个业务流程中须要在一个事务里执行的多个相关业务接口包装整合到一个事务中,好比咱们能够讲A/B/C整合为一个服务D来实现单一事务的业务流程服务

最终一致性方案之ebay模式

​ eBay在2008年公布了一个关于BASE准则提到一个分布式事务解决方案。eBay的方案实际上是一个最终一致性方案,它主要采用消息队列来辅助实现事务控制流程,方案的核心是将须要分布式处理的任务经过消息队列的方式来异步执行,若是事务失败,则能够发起人工重试的纠正流程。人工重试被更多的应用于支付场景,经过对帐系统对过后问题进行处理

​ 好比一个很常见的场景:某个用户产生了一笔交易,那么须要在交易表中增长记录,同时须要修改用户表的金额(余额),因为这两个表属于不一样的远程服务,因此就会涉及到分布式事务与数据一致性的问题

user(id, name, amt_sold, amt_bought)
transaction(xid, seller_id, buyer_id, amount)
begin;
INSERT INTO transaction VALUES(xid, $seller_id, $buyer_id, $amount); UPDATE user SET amt_sold = amt_sold + $amount WHERE id = $seller_id; <br />UPDATE user SET amt_bought = amt_bought + $amount WHERE id = $buyer_id;
commit;

那么在这里可使用消息队列(MQ)来作

先启动一个事务,更新交易表(transaction)后,并不直接更新user表,而是将要对user表进行的更新插入到消息队列中。

目标系统收到该消息之后,启动本地事务去对用户表的余额作调整

伪代码

bool result=dao.update();

if(result){

  mq.send();

}

根据上面的伪代码的实现方案,可能出现几种状况

  1. 数据库操做成功,向MQ中投递消息也成功
  2. 操做数据库失败,不会向MQ中投递消息
  3. 操做数据库成功,可是向MQ中投递消息时失败,向外抛出异常。数据库操做回滚

对于上面几种状况,问题都不大。那么咱们分析下消费端的问题

  1. 消息出队列之后,消费者对应的业务操做要执行成功。若是执行失败,消息不能失效或者丢失。须要保证消息和业务操做一致
  2. 尽可能避免消息重复消费,若是重复消费,也不能影响业务的执行结果

对于第一个问题,如何保证消息不丢失

如今用的比较广泛的MQ都具备持久化消息的功能,若是消费者宕机或者消费失败,均可以执行重试机制

对于如何避免消息的重复消费

  1. 保证消费者的幂等性

    ​ 也就是说若是队列中的消息由于网络异常致使发送屡次的状况下,仍然须要保证消息被应用屡次与应用一次产生的效果是同样的

  2. 经过消费日志表来记录消费状态

    ​ 增长一个message_applied(msg_id)表,用来记录已经被成功应用的消息。在目标系统执行更新操做以前,先检测该消息是否已经被消费过,消费完成后经过本地事务控制来更新这个“消费表状态”,用来避免消息重复消费问题

上面这种方式是很是经典的实现,基本避免了分布式事务,实现了“最终一致性”。

​ 各大知名的电商平台和互联网公司,几乎都是采用相似的设计思路来实现“最终一致性”的。这种方式适合的业务场景普遍,并且比较可靠。不过这种方式技术实现的难度比较大

保证最终一致性的模式

查询模式

​ 任何一个服务操做都提供一个查询接口,用来向外部输出操做执行的状态。服务操做的使用方能够经过接口得知服务操做执行的状态,而后根据不一样状态作不一样的处理操做

为了可以实现查询,每一个服务操做都须要有惟一的流水号

衰减查询 5s 10s 20s

补偿模式

​ 有了查询模式,咱们就可以得知操做所处的具体状态,若是整个操做处于不正常状态,咱们须要修正操做中的出现问题的子操做。也许是要从新执行,或者取消已完成的操做。经过修复使得整个分布式系统达到最终一致。这个过程就是补偿模式

根据发起形式又分为

  • 自动恢复:经过对发生失败操做的接口自动重试或者回滚已经完成的操做
  • 通知运营:若是程序没法自动完成恢复,则经过运营人员手动进行补偿
  • 通知技术:经过监控或者告警通知到技术人员,经过技术手段进行修复

X/OpenDTP模型的支付宝的DTS架构

DTS(Distributed Transaction Service)框架是由支付宝在X/OpenDTP模型的基础上改进的一个设计,定义了相似2PC的标准两阶段接口,业务系统只须要实现对应的接口就可使用DTS的事务功能。DTS最大的特色是放宽了数据库的强一致约束,保证了数据的最终一致性。

image-20190321211256865

TCC分为三个阶段

  • TRYING(SQL事务中的LOCK):

    • 阶段主要是对业务系统作检测及资源预留
  • CONFIRMING(SQL事务中的COMMIT)

    • 主要是对业务系统作确认提交
  • CANCELING(SQL事务中的ROLLBACKTRYING)

​ CONFIRMING 阶段

​ TRYING阶段执行成功并开始执行CONFIRMING阶段时,默认 CONFIRMING阶段是不会出错的。即:只要TRYING成功,CONFIRMING必定成功。 CANCELING 阶段主要是在业务执行错误,须要回滚的状态下执行的业务取消,预留资源释放。

​ 以上全部的操做须要知足幂等性,幂等性的实现方式能够是:

​ 一、经过惟一键值作处理,即每次调用的时候传入惟一键值,经过惟一键值判断业务是否被操做,若是已被操做,则再也不重复操做

​ 二、经过状态机处理,给业务数据设置状态,经过业务状态判断是否须要重复执行

如何更通俗的理解TCC事务模型

​ 支付系统接收到会员的支付请求后,须要扣减会员帐户余额、增长会员积分(暂时假设须要同步实现)增长商户帐户余额会员系统、商户系统、积分系统是独立的三个子系统,没法经过传统的事务方式进行处理。

​ TRYING阶段:咱们须要作的就是会员资金帐户的资金预留,即:冻结会员帐户的金额(订单金额)

​ CONFIRMING阶段:咱们须要作的就是会员积分帐户增长积分余额,商户帐户增长帐户余额

​ CANCELING阶段:若是confirming阶段出现问题,该阶段须要执行的就是解冻释放咱们扣减的会员余额

开源的tcc框架

  • tcc-transaction
  • bytetcc

最大努力通知型

​ 相信作过支付宝交易接口都知道,咱们通常会在支付宝的回调页面和接口里,解密参数,而后调用系统中更新交易状态相关的服务,将订单更新为付款成功。同时,只有当咱们回调页面中输出了success字样或者标识业务处理成功相应状态码时,支付宝才会中止回调请求。不然,支付宝会每间隔一段时间后,再向客户方发起回调请求,直到输出成功标识为止。

其实这就是一个很典型的补偿例子,跟一些MQ重试补偿机制很相似。

相关文章
相关标签/搜索