文章目录

  • 一、分布式事务的思路
    • 1.1 CAP定理
      • 1.1.1 Partition tolerance
      • 1.1.2 Consistency
      • 1.1.3 Availability
      • 1.1.4 Consistency 和 Availability 的矛盾
      • 1.1.5 几点疑问
    • 1.2.Base理论
  • 二、分布式事务解决方案
    • 2.1 分阶段提交
      • 2.1.1 DTP和XA
      • 2.1.2 二阶段提交
    • 2.2 TCC
      • 2.2.1 优势和缺点
    • 2.3 可靠消息服务
      • 2.3.1 基本原理
      • 2.3.2 RocketMQ事务消息
      • 2.3.3 RabbitMQ的消息确认
      • 2.3.4 消息事务的优缺点
    • 2.4 AT模式
      • 2.4.1 基本原理
      • 2.4.2 详细架构和流程
      • 2.4.3 优缺点

一、分布式事务的思路

为什么分布式系统下,事务的ACID原则难以满足?
这得从CAP定理和BASE理论说起。

1.1 CAP定理

  • Consistency(一致性)
  • Availability(可用性)
  • Partition tolerance (分区容错性)

它们的第一个字母分别是 C、A、P。这三个指标不可能同时做到。这个结论就叫做 CAP 定理。

1.1.1 Partition tolerance

Partition tolerance,中文叫做"分区容错"。大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在上海,另一台服务器放在北京,这就是两个区,它们之间可能因网络问题无法通信。

上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。一般来说,分布式系统,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。根据CAP 定理,剩下的 C 和 A 无法同时做到。

1.1.2 Consistency

Consistency 中文叫做"一致性"。意思是,写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。

接下来,用户的读操作就会得到 v1。这就叫一致性。

问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。

为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成 v1。

这样的话,用户向 G2 发起读操作,也能得到 v1。

1.1.3 Availability

Availability 中文叫做"可用性",意思是只要收到用户的请求,服务器就必须给出回应(对和错不论)。用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。

1.1.4 Consistency 和 Availability 的矛盾

答案很简单,因为可能通信失败(即出现分区容错)。
如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性不。
如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。
综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。

1.1.5 几点疑问

  • 怎样才能同时满足CA?
    除非是单点架构
  • 何时要满足CP?
    对一致性要求高的场景。例如我们的Zookeeper就是这样的,在服务节点间数据同步时,服务对外不可用。
  • 何时满足AP?
    对可用性要求较高的场景。例如Eureka,必须保证注册中心随时可用,不然拉取不到服务就可能出问题。

1.2.Base理论

BASE是三个单词的缩写:

  • Basically Available(基本可用)
  • Soft state(软状态)
  • Eventually consistent(最终一致性)

而我们解决分布式事务,就是根据上述理论来实现。
还以上面的下单减库存和扣款为例:
订单服务、库存服务、用户服务及他们对应的数据库就是分布式应用中的三个部分。

  • CP方式:现在如果要满足事务的强一致性,就必须在订单服务数据库锁定的同时,对库存服务、用户服务数据资源同时锁定。等待三个服务业务全部处理完成,才可以释放资源。此时如果有其他请求想要操作被锁定的资源就会被阻塞,这样就是满足了CP。
    这就是强一致,弱可用
  • AP方式:三个服务的对应数据库各自独立执行自己的业务,执行本地事务,不要求互相锁定资源。但是这个中间状态下,我们去访问数据库,可能遇到数据不一致的情况,不过我们需要做一些后补措施,保证在经过一段时间后,数据最终满足一致性。
    这就是高可用,但弱一致(最终一致)。

二、分布式事务解决方案

由上面的两种思想,延伸出了很多的分布式事务解决方案:

  • XA
  • TCC
  • 可靠消息最终一致
  • AT

2.1 分阶段提交

分布式事务的解决手段之一,就是两阶段提交协议(2PC:Two-Phase Commit)

2.1.1 DTP和XA

那么到底什么是两阶段提交协议呢?
1994 年,X/Open 组织(即现在的 Open Group )定义了分布式事务处理的DTP 模型。该模型包括这样几个角色:

  • 应用程序( AP ):我们的微服务
  • 事务管理器( TM ):全局事务管理者
  • 资源管理器( RM ):一般是数据库
  • 通信资源管理器( CRM ):是TM和RM间的通信中间件

在该模型中,一个分布式事务(全局事务)可以被拆分成许多个本地事务,运行在不同的AP和RM上。每个本地事务的ACID很好实现,但是全局事务必须保证其中包含的每一个本地事务都能同时成功,若有一个本地事务失败,则所有其它事务都必须回滚。但问题是,本地事务处理过程中,并不知道其它事务的运行状态。因此,就需要通过CRM来通知各个本地事务,同步事务执行的状态。

因此,各个本地事务的通信必须有统一的标准,否则不同数据库间就无法通信。XA就是 X/Open DTP中通信中间件与TM间联系的接口规范,定义了用于通知事务开始、提交、终止、回滚等接口,各个数据库厂商都必须实现这些接口。

2.1.2 二阶段提交

二阶提交协议就是根据这一思想衍生出来的,将全局事务拆分为两个阶段来执行:

  • 阶段一:准备阶段,各个本地事务完成本地事务的准备工作。
  • 阶段二:执行阶段,各个本地事务根据上一阶段执行结果,进行提交或回滚。

这个过程中需要一个协调者(coordinator),还有事务的参与者(voter)。

正常情况:

投票阶段:协调组询问各个事务参与者,是否可以执行事务。每个事务参与者执行事务,写入redo和undo日志,然后反馈事务执行成功的信息(agree
提交阶段:协调组发现每个参与者都可以执行事务(agree),于是向各个事务参与者发出commit指令,各个事务参与者提交事务。

异常情况:

投票阶段:协调组询问各个事务参与者,是否可以执行事务。每个事务参与者执行事务,写入redo和undo日志,然后反馈事务执行结果,但只要有一个参与者返回的是Disagree,则说明执行失败。
提交阶段:协调组发现有一个或多个参与者返回的是Disagree,认为执行失败。于是向各个事务参与者发出abort指令,各个事务参与者回滚事务。

二阶段提交的问题:

  • 单点故障问题
    2PC的缺点在于不能处理fail-stop形式的节点failure. 比如下图这种情况.

    假设coordinator和voter3都在Commit这个阶段crash了, 而voter1和voter2没有收到commit消息. 这时候voter1和voter2就陷入了一个困境. 因为他们并不能判断现在是两个场景中的哪一种:
    (1)上轮全票通过然后voter3第一个收到了commit的消息并在commit操作之后crash了
    (2)上轮voter3反对所以干脆没有通过.
  • 阻塞问题
    在准备阶段、提交阶段,每个事物参与者都会锁定本地资源,并等待其它事务的执行结果,阻塞时间较长,资源锁定时间太久,因此执行的效率就比较低了。

面对二阶段提交的上述缺点,后来又演变出了三阶段提交,但是依然没有完全解决阻塞和资源锁定的问题,而且引入了一些新的问题,因此实际使用的场景较少。

2.2 TCC

TCC模式可以解决2PC中的资源锁定和阻塞问题,减少资源锁定时间。
它本质是一种补偿的思路。事务运行过程包括三个方法,

  • Try:资源的检测和预留;
  • Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
  • Cancel:预留资源释放。

执行分两个阶段:

  • 准备阶段(try):资源的检测和预留;
  • 执行阶段(confirm/cancel):根据上一步结果,判断下面的执行方法。如果上一步中所有事务参与者都成功,则这里执行confirm。反之,执行cancel

    粗看似乎与两阶段提交没什么区别,但其实差别很大:
  • try、confirm、cancel都是独立的事务,不受其它参与者的影响,不会阻塞等待它人
  • try、confirm、cancel由程序员在业务层编写,锁粒度有代码控制

2.2.1 优势和缺点

  • 优势
    TCC执行的每一个阶段都会提交本地事务并释放锁,并不需要等待其它事务的执行结果。而如果其它事务执行失败,最后不是回滚,而是执行补偿操作。这样就避免了资源的长期锁定和阻塞等待,执行效率比较高,属于性能比较好的分布式事务方式。
  • 缺点
    • 代码侵入:需要人为编写代码实现try、confirm、cancel,代码侵入较多
    • 开发成本高:一个业务需要拆分成3个步骤,分别编写业务实现,业务编写比较复杂
    • 安全性考虑:cancel动作如果执行失败,资源就无法释放,需要引入重试机制,而重试可能导致重复执行,还要考虑重试时的幂等问题

2.3 可靠消息服务

这种实现方式的思路,其实是源于ebay,其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。

2.3.1 基本原理

一般分为事务的发起者A和事务的其它参与者B:

  • 事务发起者A执行本地事务
  • 事务发起者A通过MQ将需要执行的事务信息发送给事务参与者B
  • 事务参与者B接收到消息后执行本地事务

几个注意事项:

  • 事务发起者A必须确保本地事务成功后,消息一定发送成功
  • MQ必须保证消息正确投递和持久化保存
  • 事务参与者B必须确保消息最终一定能消费,如果失败需要多次重试
  • 事务B执行失败,会重试,但不会导致事务A回滚

那么问题来了,我们如何保证消息发送一定成功?如何保证消费者一定能收到消息?

2.3.2 RocketMQ事务消息

RocketMQ本身自带了事务消息,可以保证消息的可靠性,原理其实就是自带了本地消息表。

2.3.3 RabbitMQ的消息确认

RabbitMQ确保消息不丢失的思路比较奇特,并没有使用传统的本地表,而是利用了消息的确认机制:

  • 生产者确认机制:确保消息从生产者到达MQ不会有问题

    • 消息生产者发送消息到RabbitMQ时,可以设置一个异步的监听器,监听来自MQ的ACK
    • MQ接收到消息后,会返回一个回执给生产者:
      • 消息到达交换机后路由失败,会返回失败ACK
      • 消息路由成功,持久化失败,会返回失败ACK
      • 消息路由成功,持久化成功,会返回成功ACK
    • 生产者提前编写好不同回执的处理方式
      • 失败回执:等待一定时间后重新发送
      • 成功回执:记录日志等行为
  • 消费者确认机制:确保消息能够被消费者正确消费
    • 消费者需要在监听队列的时候指定手动ACK模式
    • RabbitMQ把消息投递给消费者后,会等待消费者ACK,接收到ACK后才删除消息,如果没有接收到ACK消息会一直保留在服务端,如果消费者断开连接或异常后,消息会投递给其它消费者。
    • 消费者处理完消息,提交事务后,手动ACK。如果执行过程中抛出异常,则不会ACK,业务处理失败,等待下一条消息

经过上面的两种确认机制,可以确保从消息生产者到消费者的消息安全,再结合生产者和消费者两端的本地事务,即可保证一个分布式事务的最终一致性。

2.3.4 消息事务的优缺点

总结上面的几种模型,消息事务的优缺点如下:

  • 优点:

    • 业务相对简单,不需要编写三个阶段业务
    • 是多个本地事务的结合,因此资源锁定周期短,性能好
  • 缺点:
    • 代码侵入
    • 依赖于MQ的可靠性
    • 消息发起者可以回滚,但是消息参与者无法引起事务回滚
    • 事务时效性差,取决于MQ消息发送是否及时,还有消息参与者的执行情况

2.4 AT模式

2019年 1 月份,Seata 开源了 AT 模式。AT 模式是一种无侵入的分布式事务解决方案。可以看做是对TCC或者二阶段提交模型的一种优化,解决了TCC模式中的代码侵入、编码复杂等问题。
在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。

2.4.1 基本原理

先来看一张流程图:

有没有感觉跟TCC的执行很像,都是分两个阶段:

  • 一阶段:执行本地事务,并返回执行结果
  • 二阶段:根据一阶段的结果,判断二阶段做法:提交或回滚

但AT模式底层做的事情可完全不同,而且第二阶段根本不需要我们编写,全部有Seata自己实现了。也就是说:我们写的代码与本地事务时代码一样,无需手动处理分布式事务。

那么,AT模式如何实现无代码侵入,如何帮我们自动实现二阶段代码的呢?

一阶段
在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后获取全局行锁,提交事务。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
这里的before imageafter image类似于数据库的undo和redo日志,但其实是用数据库模拟的。

二阶段提交
二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。

二阶段回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。

不过因为有全局锁机制,所以可以降低出现脏写的概率。

AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。

2.4.2 详细架构和流程

Seata中的几个基本概念:

  • TC(Transaction Coordinator) - 事务协调者
    维护全局和分支事务的状态,驱动全局事务提交或回滚(TM之间的协调者)。
  • TM(Transaction Manager) - 事务管理器
    定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  • RM(Resource Manager) - 资源管理器
    管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

我们看下面的一个架构图

  • TM:业务模块中全局事务的开启者

    • 向TC开启一个全局事务
    • 调用其它微服务
  • RM:业务模块执行者中,包含RM部分,负责向TC汇报事务执行状态
    • 执行本地事务
    • 向TC注册分支事务,并提交本地事务执行结果
  • TM:结束对微服务的调用,通知TC,全局事务执行完毕,事务一阶段结束
  • TC:汇总各个分支事务执行结果,决定分布式事务是提交还是回滚;
  • TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。

一阶段:

  • TM开启全局事务,并向TC声明全局事务,包括全局事务XID信息
  • TM所在服务调用其它微服务
  • 微服务,主要有RM来执行
    • 查询before_image
    • 执行本地事务
    • 查询after_image
    • 生成undo_log并写入数据库
    • 向TC注册分支事务,告知事务执行结果
    • 获取全局锁(阻止其它全局事务并发修改当前数据)
    • 释放本地锁(不影响其它业务对数据的操作)
  • 待所有业务执行完毕,事务发起者(TM)会尝试向TC提交全局事务

二阶段:

  • TC统计分支事务执行情况,根据结果判断下一步行为

    • 分支都成功:通知分支事务,提交事务
    • 有分支执行失败:通知执行成功的分支事务,回滚数据
  • 分支事务的RM
    • 提交事务:直接清空before_imageafter_image信息,释放全局锁
    • 回滚事务:
      • 校验after_image,判断是否有脏写
      • 如果没有脏写,回滚数据到before_image,清除before_imageafter_image
      • 如果有脏写,请求人工介入

2.4.3 优缺点

优点:

  • 与2PC(XA)相比:每个分支事务都是独立提交,不互相等待,减少了资源锁定和阻塞时间
  • 与TCC相比:二阶段的执行操作全部自动化生成,无代码侵入,开发成本低
    缺点:
  • 与TCC相比,需要动态生成二阶段的反向补偿操作,执行性能略低于TCC

分布式事务二——解决思路相关推荐

  1. 分布式事务的解决思路与方案

    导航 一.事务的种类与场景 二.分布式事务解决方案 2.1 全局事务 2.2 可靠消息事务 2.3 最大努力通知 2.4 TCC 事务 三.TCC 模式常见问题 3.1 二阶段幂等 3.2 空回滚 3 ...

  2. 分布式事务——Saga实现思路

    分布式事务--Saga实现思路 1. 为什么要用Saga 在分布式的系统里,数据一致性往往是首先关注且最难解决的部分.市面上也有很多分布式事务框架,比如seata.hmily等,但貌似业界并没有大规模 ...

  3. 分布式事务二 基础理论

    分布式事务二 基础理论 前言: 书接上文<分布式事务一 分布式事务基础概念>,我们了解到了分布式事务的基础概念.与本地事务不同的是,分布式系统之所以叫分布式,是因为提供服务的各个节点分布在 ...

  4. 分布式事务 - 如何解决分布式事务问题?

    分布式事物 - 如何解决分布式事务问题? 面试题 分布式事务了解吗?你们是如何解决分布式事务问题的? 面试官心理分析 只要聊到你做了分布式系统,必问分布式事务,你对分布式事务一无所知的话,确实会很坑, ...

  5. 分布式事务实践 解决数据一致性 分布式事务实现:Event Sourcing模式

    详细介绍了分布式事务实现的模式中的Event Sourcing模式,并通过完整实例演示了Event Sourcing模式下,实现微服务系统的分布式事务的完整过程. 8-1 事件溯源模式介绍 8-2 事 ...

  6. 分布式事务实践 解决数据一致性 分布式事务实现:消息驱动模式

    分布式事务实现:消息驱动模式 详细介绍3种分布式事务实现的模式中的消息驱动模式并通过完整实例演示了消息驱动模式下,实现微服务系统的分布式事务的完整过程. 7-1 分布式事务实现:消息驱动模式 7-2 ...

  7. 分布式事务实践 解决数据一致性 分布式事务实现,模式和技术

    分布式事务实现,模式和技术 分布式事务实现,模式和技术 介绍分布式事务的定义.原则和实现原则,介绍使用Spring框架实现分布式事务的几种方式,包括使用JTA.Spring事务同步.链式事务等,并通过 ...

  8. MSDTC 分布式事务错误解决

    最近 在做项目的过程中总会遇到MSDTC的错误,网上也很多人问到这个问题,错误信息大约描述为: [COMException (0x8004d00e): 此事务已明地或暗地被确认或终止 (异常来自 HR ...

  9. LCN分布式事务框架解决分布式事务一致性问题

    LCN分布式事务框架 框架介绍 LCN分布式事务框架其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果. 核心步骤 创建事务组 是指在事务发起方开始执行业务代码之前先调用TxMana ...

最新文章

  1. 上升沿判断语句_FPGA入门系列6判断语句
  2. php 将内容中的图片的域名,php给编辑器中的图片地址添加域名
  3. 构建虚拟主机以及访问控制
  4. 遍历List 删除某条数据
  5. iOS之深入解析类Class的底层原理
  6. spring— Spring与Web环境集成
  7. python优先队列使用_Python优先队列实现方法示例
  8. 网络拓扑图自动生成_SAP ABAP关键字语法图和ABAP代码自动生成工具Code Composer
  9. 世界坐标和本地坐标之间的转换
  10. 自己编写vb进度条控件
  11. curl模拟登陆 php实例,php 使用curl模拟登录人人(校内)网的简单实例
  12. 《吴军-信息论40讲》摘录
  13. 基于张正友标定法的工业机器人视觉标定
  14. ztree 更新配置后重新渲染树_zTree 树形控件 ajax动态加载数据
  15. 编程数学读书笔记 -- 第二章逻辑
  16. 实例解读模拟电子技术完全学习与应用
  17. 模块化编辑器综合评测:Craft、Notion、FlowUs
  18. 靶机渗透练习97-hacksudo:ProximaCentauri
  19. git clone加速(实测推荐)
  20. GitHub添加SSH key

热门文章

  1. CMMI终于评估完了
  2. 织物印花中的常见问题与解决办法
  3. win10如何添加开机自己启动软件
  4. mysql为何不建议使用外键_MYSQL外键的使用以及优缺点
  5. CS231n关于Python使用教程翻译
  6. ubuntu安装firewalld 防火墙
  7. 【node进阶】深度解析express框架---编写接口|解决跨域问题
  8. 【大学四年自学Java的学习路线】写了一个月,这是一份最适合普通大众、非科班的路线,祝你零基础快速找到一份满意的工作
  9. 第十二届服务外包大赛|A01|第一次功能设想
  10. C++一行一行的读文件