温故知新-分布式事务概述


摘要

本文主要回顾一下分布式事务的实现方案和基本原理

事务

事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。

数据库本地事务

  • 事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个特性,简称为事务ACID属性。
    事务ACID属性

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

UndoLog的原理很简单,为了满足事务的原子性,在操作任何数据之前,首先将数据备份到一个地方(这个存储数据备份的地方称为UndoLog)。然后进行数据的修改。如果出现了错误或者用户执行了ROLLBACK语句,系统可以利用Undo Log中的备份将数据恢复到事务开始之前的状态。

和Undo Log相反,RedoLog记录的是新数据的备份。在事务提交前,只要将RedoLog持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是RedoLog已经持久化。系统可以根据RedoLog的内容,将所有数据恢复到最新的状态。 对具体实现过程有兴趣的同学可以去自行搜索扩展。

更详细的分析可以看一下我之前的博客《Mysql锁&事务&MVCC》,说完数据库事务,接下来分析一下分布式事务;

分布式事务

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

分布式事务的基础

CAP

CAP的人都知道,三者不能共有,如果感兴趣可以搜索CAP的证明,在分布式系统中,网络无法100%可靠,分区其实是一个必然现象,如果我们选择了CA而放弃了P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是A又不允许,所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。

对于CP来说,放弃可用性,追求一致性和分区容错性,我们的zookeeper其实就是追求的强一致,我的上一篇文章也提高了。

对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,例如我们上一篇提到的Eureka,后面的BASE也是根据AP来扩展。

顺便一提,CAP理论中是忽略网络延迟,也就是当事务提交时,从节点A复制到节点B,但是在现实中这个是明显不可能的,所以总会有一定的时间是不一致。同时CAP中选择两个,比如你选择了CP,并不是叫你放弃A。因为P出现的概率实在是太小了,大部分的时间你仍然需要保证CA。就算分区出现了你也要为后来的A做准备,比如通过一些日志的手段,是其他机器回复至可用。

BASE

BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展

  1. 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
  2. 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。
  3. 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。

BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和 ACID 是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。


目前很多系统而言,都是选择了保证的最终一致性;

方案

是否真的要分布式事务在说方案之前,首先你一定要明确你是否真的需要分布式事务?

太多团队的团队都是一个人维护几个微服务,过度设计,搞得人疲劳不堪,而微服务过多就会引出分布式事务,此时最好还是把微服务聚合成一个单机服务,使用数据库的本地事务。防止增加系统复杂度,减少成本,不能因为追求某些设计,而引入不必要的成本和复杂度。

如果确定需要引入分布式事务可以看看下面几种常见的方案。

2PC

XA是一个分布式事务协议,由Tuxedo提出。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:

2PC 两阶段提交是一种使分布式系统中所有节点在进行事务提交时保持一致性而设计的一种协议;在一个分布式系统中,所有的节点虽然都可以知道自己执行操作后的状态,但是无法知道其他节点执行操作的状态,在一个事务跨越多个系统时,就需要引入一个作为协调者的组件来统一掌控全部的节点并指示这些节点是否把操作结果进行真正的提交,想要在分布式系统中实现一致性的其他协议都是在两阶段提交的基础上做的改进。

  • 完成:

image-20200616232958797

  • 回滚

image-20200617000839231

在XA协议中分为两阶段:

第一阶段:事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.

第二阶段:事务协调器要求每个数据库提交数据,或者回滚数据。

优点: 尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。

缺点:

  • 单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。
  • 同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。
  • 数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

总的来说,XA协议比较简单,成本较低,但是其单点问题,以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。

3PC

三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。

image-20200617000932474

与两阶段提交不同的是,三阶段提交有两个改动点。

  1. 引入超时机制。同时在协调者和参与者中都引入超时机制。

  2. 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommitPreCommitDoCommit三个阶段。

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

TCC

Try: 尝试执行业务
• 完成所有业务检查(一致性)
• 预留必须业务资源(准隔离性)

Confirm:确认执行业务
• 真正执行业务
• 不作任何业务检查
• 只使用Try阶段预留的业务资源
• Confirm操作要满足幂等性

Cancel: 取消执行业务
• 释放Try阶段预留的业务资源
• Cancel操作要满足幂等性

我们可以从阿里巴巴开源的Seata简单探究其原理,Seata管理的分布式事务的典型生命周期

  • TM要求TC开始一项新的全局事务。 TC生成代表全局交易的XID。
  • XID通过微服务的调用链传播。
  • RM将本地事务注册为XID到TC的相应全局事务的分支。
  • TM要求TC提交或回退相应的XID全局事务。
  • TC驱动XID的相应全局事务下的所有分支事务以完成分支提交或回滚。

image-20200617005526739

image-20200617005617467

image-20200617005644766

本地消息表

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

此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。

image-20200617010447932

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

RocketMQ事务

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

image-20200617011030517

  1. A服务先发送个Half Message给Brock端,消息中携带 B服务;

  2. 当A服务知道Half Message发送成功后,那么开始第3步执行本地事务。

  3. 执行本地事务(会有三种情况1、执行成功。2、执行失败。3、网络等原因导致没有响应)

  4. 如果本地事务成功,那么Product像Brock服务器发送Commit,这样B服务就可以消费该message。

    如果本地事务失败,那么Product像Brock服务器发送Rollback,那么就会直接删除上面这条半消息。

    如果因为网络等原因迟迟没有返回失败还是成功,那么会执行RocketMQ的回调接口,来进行事务的回查。

Saga事务

两阶段提交其实可以保证事务的强一致性,但是在很多业务场景下,我们其实只需要保证业务的最终一致性,在一定的时间窗口内,多个系统中的数据不一致是可以接受的,在过了时间窗口之后,所有系统都会返回一致的结果。

Saga 其实就一种简化的分布式事务解决方案,它将一系列的分布式操作转化成了一系列的本地事务,在每一个本地事务中我们都会更新数据库并且向集群中的其他服务发送一条的新的消息来触发下一个本地的事务;一旦本地的事务因为违反了业务逻辑而失败,那么就会立刻触发一系列的回滚操作来撤回之前本地事务造成的副作用。

在此不在详细赘述;

总结

上面只是一些解决方案简单介绍,如果真正的想要落地,其实每种方案需要思考的地方都非常多,复杂度都比较大,所以最后再次提醒一定要判断好是否使用分布式事务。我们调研了阿里的Seata,但还未落地,大多数场景我们还是使用MQ选择了最终一致性。

参考

分布式事务的实现原理

DTP模型之一:(XA协议之一)XA协议、二阶段2PC、三阶段3PC提交

再有人问你分布式事务,这篇文章扔给他

分布式事务(3)---RocketMQ实现分布式事务原理

seata


你的鼓励也是我创作的动力

打赏地址

原文地址:https://www.cnblogs.com/yangsanchao/p/13282929.html