架构师-分布式事务

在分布式情况下,也需要事务操作,例如老生常谈的银行转账功能,必然面临分布式事务,先说下本地事务

事务

事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。

简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制

本地数据库ACID

  • A:原则性(Atomicity)
  • C:一致性(Consistency)
  • I:隔离性(Isolation)
  • D:持久性(Durability)

InnoDB数据库事务实现原理

  • 事务的 ACID 是通过 InnoDB 日志和锁来保证
  • 事务的隔离性是通过数据库锁的机制实现的
  • 持久性通过 Redo Log(重做日志)来实现
  • 原子性和一致性通过 Undo Log 来实现

Undo Log 的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为 Undo Log)。然后进行数据的修改。

如果出现了错误或者用户执行了 Rollback 语句,系统可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。

和 Undo Log 相反,Redo Log 记录的是新数据的备份。在事务提交前,只要将 Redo Log 持久化即可,不需要将数据持久化。

当系统崩溃时,虽然数据没有持久化,但是 Redo Log 已经持久化。系统可以根据 Redo Log 的内容,将所有数据恢复到***的状态。对具体实现过程有兴趣的同学可以去自行搜索扩展。

分布式式事务

CAP理论

CAP的含义:
C:Consistency 一致性 同一数据的多个副本是否实时相同。
A:Availability 可用性 可用性:一定时间内 & 系统返回一个明确的结果 则称为该系统可用。
P:Partition tolerance分区容错性 将同一服务分布在多个系统中,从而保证某一个系统宕机,仍然有其他系统提供相同的服务。

CAP理论告诉我们,在分布式系统中,C、A、P三个条件中我们最多只能选择两个。那么问题来了,究竟选择哪两个条件较为合适呢?
对于一个业务系统来说,可用性和分区容错性是必须要满足的两个条件,并且这两者是相辅相成的。业务系统之所以使用分布式系统,主要原因有两个:

  • 提升整体性能

当业务量猛增,单个服务器已经无法满足我们的业务需求的时候,就需要使用分布式系统,使用多个节点提供相同的功能,从而整体上提升系统的性能,这就是使用分布式系统的第一个原因。

  • 实现分区容错性

单一节点 或 多个节点处于相同的网络环境下,那么会存在一定的风险,万一该机房断电、该地区发生自然灾害,那么业务系统就全面瘫痪了。为了防止这一问题,采用分布式系统,将多个子系统分布在不同的地域、不同的机房中,从而保证系统高可用性。

这说明分区容错性是分布式系统的根本,如果分区容错性不能满足,那使用分布式系统将失去意义。
此外,可用性对业务系统也尤为重要。在大谈用户体验的今天,如果业务系统时常出现“系统异常”、响应时间过长等情况,这使得用户对系统的好感度大打折扣,在互联网行业竞争激烈的今天,相同领域的竞争者不甚枚举,系统的间歇性不可用会立马导致用户流向竞争对手。因此,我们只能通过牺牲一致性来换取系统的可用性和分区容错

Base理论

CAP理论告诉我们一个悲惨但不得不接受的事实——我们只能在C、A、P中选择两个条件。而对于业务系统而言,我们往往选择牺牲一致性来换取系统的可用性和分区容错性。不过这里要指出的是,所谓的“牺牲一致性”并不是完全放弃数据一致性,而是牺牲强一致性换取弱一致性

  • BA:Basic Available 基本可用
  • 整个系统在某些不可抗力的情况下,仍然能够保证“可用性”,即一定时间内仍然能够返回一个明确的结果。只不过“基本可用”和“高可用”的区别是:
    (1) “一定时间”可以适当延长 当举行大促时,响应时间可以适当延长

    (2) 给部分用户返回一个降级页面 给部分用户直接返回一个降级页面,从而缓解服务器压力。但要注意,返回降级页面仍然是返回明确结果。

  • S:Soft State:柔性状态

同一数据的不同副本的状态,可以不需要实时一致。

  • E:Eventual Consisstency:最终一致性

同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的。

柔性事务和刚性事务

  • 柔性事务满足BASE理论(基本可用,最终一致)

  • 刚性事务满足ACID理论

柔性事务分为

1.两阶段型
2.补偿型
3.异步确保型
4.最大努力通知型几种。

经典的X/OpenDTP事务模型-XA协议实现分布式事务

X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 是X/Open这个组织定义的一套分布式事务的标准,也就是定义了规范和API接口,由各个厂商进行具体的实现。

这个标准提出了使用二阶段提交(2PC – Two-Phase-Commit)来保证分布式事务的完整性。后来J2EE也遵循了X/OpenDTP规范,设计并实现了java里的分布式事务编程接口规范-JTA
在这里插入图片描述

在X/OpenDTP事务模型中,定义了三个角色

  • AP: application, 应用程序,也就是业务层。哪些操作属于一个事务,就是AP定义的
  • RM: Resource Manager,资源管理器。一般是数据库,也可以是其他资源管理器,比如消息队列,
    文件系统
  • TM: Transaction Manager ,事务管理器、事务协调者,负责接收来自用户程序(AP)发起的XA事务指令,并调度和协调参与事务的所有RM(数据库),确保事务正确完成

在分布式系统中,每一个机器节点虽然都能够明确知道自己在进行事务操作过程中的结果是成功还是失败,但却无法直接获取到其他分布式节点的操作结果。因此当一个事务操作需要跨越多个分布式节点的时候,为了保持事务处理的ACID特性,就需要引入一个“协调者”(TM)来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点被称为AP。TM负责调度AP的行为,并最终决定这些AP是否要把事务真正进行提交到(RM)

XA 是X/Open DTP定义的资源管理器和事务管理器之间的接口规范,TM用它来通知和协调相关RM事务 的开始、结束、提交或回滚。目前Oracle、Mysql、DB2都提供了对XA的支持; XA接口是双向的系统接 口,在事务管理器(TM ) 以及多个资源管理器之间形成通信的桥梁(XA不能自动提交)

在这里插入图片描述
补充下在DTP定义了以下几个概念

  • 事务:一个事务是一个完整的工作单元,由多个独立的计算任务组成,这多个任务在逻辑上是原子的
  • 全局事务:对于一次性操作多个资源管理器的事务,就是全局事务
  • 分支事务:在全局事务中,某一个资源管理器有自己独立的任务,这些任务的集合作为这个资源管理器的分支任务
  • 控制线程:用来表示一个工作线程,主要是关联AP,TM,RM三者的一个线程,也就是事务上下文环境。简单的说,就是需要标识一个全局事务以及分支事务的关系

如果一个事务管理器管理着多个资源管理器,DTP是通过两阶段提交协议来控制全局事务和分支事务。
第一阶段:准备阶段 事务管理器通知资源管理器准备分支事务,资源管理器告之事务管理器准备结果
第二阶段:提交阶段 事务管理器通知资源管理器提交分支事务,资源管理器告之事务管理器结果

XA的ACID特性
原子性:XA议使用2PC原子提交协议来保证分布式事务原子性
隔离性:XA要求每个RMs实现本地的事务隔离,子事务的隔离来保证整个事务的隔离。
一致性:通过原子性、隔离性以及自身一致性的实现来保证“数据库从一个一致状态转变为另一个一致状态”;通过MVCC来保证中间状态不能被观察到。

XA的优缺点
优点:

  • 对业务无侵入,对RM要求高
  • 尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于 MySQL 是从 5.5 开始支持

缺点:

  • 同步阻塞:在二阶段提交的过程中,所有的节点都在等待其他节点的响应,无法进行其他操作。这种同步阻塞极大的限制了分布式系统的性能。

  • 单点问题:协调者在整个二阶段提交过程中很重要,如果协调者在提交阶段出现问题,那么整个流程将无法运转。更重要的是,其他参与者将会处于一直锁定事务资源的状态中,而无法继续完成事务操作。

  • 数据不一致:假设当协调者向所有的参与者发送commit请求之后,发生了局部网络异常,或者是协调者在尚未发送完所有 commit请求之前自身发生了崩溃,导致最终只有部分参与者收到了commit请求。这将导致严重的数据不一致问题。

  • 容错性不好:如果在二阶段提交的提交询问阶段中,参与者出现故障,导致协调者始终无法获取到所有参与者的确认信息,这时协调者只能依靠其自身的超时机制,判断是否需要中断事务。显然,这种策略过于保守。换句话说,二阶段提交协议没有设计较为完善的容错机制,任意一个节点是失败都会导致整个事务的失败。

XA最大的作用在数据库资源的横向扩展时,能保证多资源访问的事务属性
在分库分表请下使用XA,基于数据库方面

2PC

  • 第一阶段
    在这里插入图片描述
    RM在第一阶段会做两件事:
      1.记录事务日志:reduo,undo
      2.返回给TM信息,ok、error
    存在问题: 如果第一阶段完成后TM宕机或网络出现故障了,此时RM会一直阻塞,发生了死锁,因为没有timeout机制,3pc就针对此问题进行了改造,加入了timeout机制
  • 第二阶段
    在这里插入图片描述
    根据第一个阶段的返回结果进行提交或者回滚

2PC比较强一致性,虽然有一点问题,损失点性能,但在数据性需要强一致性的业务情况下使用,XA事务就是用2PC提交

TCC两阶段补偿方案

TCC是Try-Confirm-Cancel, 柔性事务
比如在支付场景中,先冻结一笔资金,再去发起支付。如果支付成功,则讲冻结资金进行实际扣除;如果支付失败,则取消资金冻结

关于 TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出
在这里插入图片描述

  • Try阶段

完成所有业务检查(一致性),预留业务资源(准隔离性)

  • Confirm阶段

确认执行业务操作,不做任何业务检查,只使用Try阶段预留的业务资源。

  • Cancel阶段

取消Try阶段预留的业务资源。Try阶段出现异常时,取消所有业务资源预留请求

TCC 事务机制相比于上面介绍的 XA,解决了如下几个缺点:

  • 解决了协调者单点,由主业务方发起并完成这个业务活动。业务活动管理器也变成多点,引入集群。
  • 同步阻塞:引入超时,超时后进行补偿,并且不会锁定整个资源,将资源转换为业务逻辑形式,粒度变小。
  • 数据一致性,有了补偿机制之后,由业务活动管理器控制一致性。

TCC缺点:

  • TCC模型对业务侵入强,改造难度大
  • 需要服务实现对应的接口,对主业务侵入大

TCC跨服务的分布式事务,在业务层上面解决事务,适用于执行时间较短的业务,如互联网金融业务最核心的三个服务:交易、支付、账务

最大努力通知方案

在这里插入图片描述

本地消息表(异步确保)

本地消息表这个方案最初是 eBay 提出的,eBay 的完整方案 https://queue.acm.org/detail.cfm?id=1394128

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

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

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

此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。

人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。

对于本地消息队列来说核心是把大事务转变为小事务

本地消息队列是 BASE 理论,是最终一致模型,适用于对一致性要求不高的情况。实现这个模型时需要注意重试的幂等。

MQ 事务

在 RocketMQ 中实现了分布式事务,实际上是对本地消息表的一个封装,将本地消息表移动到了 MQ 内部。

RocketMQ和其他消息中间件最大的一个区别是支持了事务消息,这也是分布式事务里面的基于消息的最终一致性方案
在这里插入图片描述

  1. 生产者执行本地事务,修改订单支付状态,并且提交事务
  2. 生产者发送事务消息到broker上,消息发送到broker上在没有确认之前,消息对于consumer是不可见状态
  3. 生产者确认事务消息,使得发送到broker上的事务消息对于消费者可见
  4. 消费者获取到消息进行消费,消费完之后执行ack进行确认
  5. 这里可能会存在一个问题,生产者本地事务成功后,发送事务确认消息到broker上失败了怎么办?这个时候意味着消费者无法正常消费到这个消息。所以RocketMQ提供了消息回查机制,如果事务消息一直处于中间状态,broker会发起重试去查询broker上这个事务的处理状态。一旦发现事务处理成功,则把当前这条消息设置为可见

具体代码案例可以参考RocketMQ事务性消息

开源的分布式事务解决方案

GTS/seata/TX-LCN

其他补充

幂等

简单来说:重复调用多次产生的业务结果与调用一次产生的业务结果相同; 在分布式架构中,我们调用一个远程服务去完成一个操作,除了成功和失败以外,还有未知状态,那么针对这个未知状态,我们会采取一些重试的行为; 或者在消息中间件的使用场景中,消费者可能会重复收到消息。对于这两种情况,消费端或者服务端需要采取一定的手段,也就是考虑到重发的情况下保证数据的安全性。一般我们
常用的手段

  1. 状态机实现幂等
  2. 数据库唯一约束实现幂等
  3. 通过tokenid的方式去识别每次请求判断是否重复

关于状态机

在使用最终一致性的方案时,一定要提到的一个概念是状态机。
什么是状态机?
是一种特殊的组织代码的方式,用这种方式能够确保你的对象随时都知道自己所处的状态以及所能做的操作。它也是一种用来进行对象行为建模的工具,用于描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。
状态机这个概念大家都不陌生,比如TCP协议的状态机。同时我们在编写相关业务逻辑的时候经常也会需要处理各种事件和状态的切换,比如switch、if/else。所以我们其实一直在跟状态机打交道,只是可能没有意识到而已。在处理一些业务逻辑比较复杂的需求时,可以先看看是否适合用一个有限状态机来描述,如果可以把业务模型抽象成一个有限状态机,那么代码就会逻辑特别清晰,结构特别规整。

比如我们来简单描述一个订单
我们以支付为例,一笔订单可能会有等待支付、支付中、已支付等状态,那么我们就可以先去把可能出
现的状态以及状态的流程画出来
在这里插入图片描述
比如我们来简单描述一个订单,我们以支付为例,一笔订单可能会有等待支付、支付中、已支付等状态,那么我们就可以先去把可能出现的状态以及状态的流程画出来。

状态机的作用

  • 实现幂等
  • 通过状态驱动数据的变化
  • 业务流程以及逻辑更加清晰,特别是应对复杂的业务场景
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值