简单理解分布式事务及常见解决方案

什么是事务

事务是由一组操作组成的一个工作单元。怎么去理解这个问题呢?

我们从现实生活中去理解

那么事务有哪些特性呢?

事务特性

原子性:事务内部的一组操作要么同时成功,要么同时失败

隔离性:不同事务之间是互相不影响的

一致性:事务内部一组操作,各自操作产生的结果数据,要能够保证都是预期的状态

持久性:事务内部一组操作,各个操作产生的数据要能够持久的效应

什么是分布式事务

分布式事务就是一组服务操作的集合

例如:在分布式系统或者微服务系统内,完成一个任何,需要涉及到多个服务来共同完成,这一组服务操作组成的集合,就是分布式事务

什么是本地事务

本地事务就是由一组sql语句操作的集合,

本地事务主要就是指sql语句的操作

扫描二维码关注公众号,回复: 15799081 查看本文章

ACID

指数据库事务正确执行的四个基本要素:

  1. 原子性(Atomicity)
  2. 一致性(Consistency)
  3. 隔离性(Isolation)
  4. 持久性(Durability)

CAP

CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容忍性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。

  • 一致性:在分布式系统中的所有数据备份,在同一时刻是否同样的值。
  • 可用性:在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
  • 分区容忍性:以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

BASE理论

BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。

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

undo log

数据库事务具有原子性,如果事务执行失败,需要把数据回滚。

事务同时还具备持久性,事务对数据所做的变更就完全保存在了数据库,不能因为故障丢失。

原子性可以利用undo日志来实现。

undo log的原理

为了满足事务的原子性,在操作任何数据之前,首先把数据备份到undo log中,然后进行数据的修改,如果出现了错误或者用户执行了回滚(RollBack)语句,系统可以利用undo log中备份将数据恢复到事务开始之前的状态

数据库写入数据到磁盘直线,会把数据先缓存在内存中,事务提交时才会写入到磁盘中。

用undo log实现原子性和持久化的过程:

假设A,B两个数据,值分别为1,2

  1. 事务开始
  2. 记录A=1到undo log
  3. 修改A=3
  4. 记录B=2到undo log
  5. 修改B=4
  6. 将undo log写到磁盘
  7. 将数据写到磁盘
  8. 事务提交

如何保证持久性?

事务提交前,会把修改数据到磁盘前,也就是只要事务提交了,数据就肯定持久化了

如何保证原子性?

  • 每次对数据库修改,都会把修改前数据记录在undo log,那么需要回滚的时候,就可以读取undo log,恢复数据

  • 若系统在7和8之间崩溃

    此时,事务并未提交,需要回滚,而undo log已经持久化,可以根据undo log日志来恢复数据

    若系统在7之前崩溃

    此时数据并未持久化到硬盘,依然保持在事务之前的状态

缺点:

每个事务提交前都会将数据和undo log写入磁盘,这样会导致大量的磁盘IO,所以性能会很低。

redo log

redo log记录的是新数据的备份。在事务提交之前只要把redo log持久化就行了,不需要把数据持久化,减少了IO次数。它用的是顺序IO所有读写速度会很快

如何保证持久性

Redo Log记录的是新数据的备份。在事务提交前,只要将Redo Log持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是Redo Log已经持久化。系统可以根据Redo Log的内容,将所有数据恢复到最新的状态。

数据恢复的两种策略:

  • 恢复时,只重做已经提交的事务
  • 恢复时,重做所有事务包括未提交的事务和回滚了的事务,然后通过undo log回滚未提交的事务。

Inodb引擎采用的是第二种,所以undolog要在redo log前持久化

总结:

undo log记录更新前的数据,用于保证事务的原子性

redo log记录更新后的数据,用于保证事务的持久性

redo log有自己的内存buffer,先写入到buffer,事务提交时写入磁盘

redo log持久化后,意味着事务是可以提交的

分布式事务

分布式事务,是分布式环境下,对数据一系列操作的集合,通过以下三个特征是否实现来表述

  • 一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
  • 可用性(Availability) : 每个操作都必须以可预期的响应结束
  • 分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成

因为分布式系统中系统肯定部署在多台机器上,无法保证网络做到100%的可靠,所以网络分区一定存在,即P一定存在;

在出现网络分区后,就出现了可用性和一致性的问题,我们必须要在这两者之间进行取舍,因此就有了两种架构:CP架构,AP架构;

CP架构

当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性

  1. 当没有出网络分区时,系统A与系统B的数据一致,X=1
  2. 将系统A的X修改为2,X=2
  3. 当出现网络分区后,系统A与系统B之间的数据同步数据失败,系统B的X=1
  4. 当客户端请求系统B时,为了保证一致性,此时系统B应拒绝服务请求,返回错误码或错误信息

上面这种方式就违背了可用性的要求,只满足一致性和分区容错,即CP

CAP理论是忽略网络延迟,从系统A同步数据到系统B的网络延迟是忽略的

CP架构保证了客户端在获取数据时一定是最近的写操作,或者获取到异常信息,绝不会出现数据不一致的情况

强一致,弱可用

AP架构

当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性

  1. 当没有出网络分区时,系统A与系统B的数据一致,X=1
  2. 将系统A的X修改为2,X=2
  3. 当出现网络分区后,系统A与系统B之间的数据同步数据失败,系统B的X=1
  4. 当客户端请求系统B时,为了保证可用性,此时系统B应返回旧值,X=1

上面这种方式就违背了一致性的要求,只满足可用性和分区容错,即AP

CP架构保证了客户端在获取数据时无论返回的是最新值还是旧值,系统一定是可用的

CAP理论关注粒度是数据,而不是整体系统设计的策略

高可用,弱一致(最终一直性)

BASE

1、理解强一致性和最终一致性

CAP理论告诉我们一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)这三项中的两项,其中AP在实际应用中较多,AP即舍弃一致性,保证可用性和分区容忍性,但是在实际生产中很多场景都要实现一致性,比如前边我们举的例子主数据库向从数据库同步数据,即使不要一致性,但是最终也要将数据同步成功来保证数据一致,这种一致性和CAP中的一致性不同,CAP中的一致性要求在任何时间查询每个结点数据都必须一致,它强调的是强一致性,但是最终一致性是允许可以在一段时间内每个结点的数据不一致,但是经过一段时间每个结点的数据必须一致,它强调的是最终数据的一致性。

2、Base理论介绍

BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。BASE理论是对CAP中AP的一个扩展,通过牺牲强一致性来获得可用性,当出现故障允许部分不可用但要保证核心功能可用,允许数据在一段时间内是不一致的,但最终达到一致状态。满足BASE理论的事务,我们称之为“柔性事务”。

基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。如,电商网站交易付款出现问题了,商品依然可以正常浏览。
软状态:由于不要求强一致性,所以BASE允许系统中存在中间状态(也叫软状态),这个状态不影响系统可用性,如订单的"支付中"、“数据同步中”等状态,待数据最终一致后状态改为“成功”状态。
最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。如订单的"支付中"状态,最终会变为“支付成功”或者"支付失败",使订单状态与实际交易结果达成一致,但需要一定时间的延迟、等待。

1、分阶段提交

1、两阶段提交协议(2PC:Two-Phrase Commit)

两阶段提交协议的目标在于在分布式系统中保证数据的一致性:投票阶段和事务提交阶段

(1)协议参与者

在两阶段提交协议中,系统一般包含两类机器(或节点):一类为协调者(coordinator),通常一个系统中只有一个;另一类为事务参与者(participants,cohorts或workers),一般包含多个,在数据存储系统中可以理解为数据副本的个数。协议中假设每个节点都会记录写前日志(write-ahead log)并持久性存储,即使节点发生故障日志也不会丢失。协议中同时假设节点不会发生永久性故障而且任意两个节点都可以互相通信。

(2)两个阶段的执行

1.请求阶段(commit-request phase,或称表决阶段,voting phase)
在请求阶段,协调者将通知事务参与者准备提交或取消事务,然后进入表决过程。
在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)。

2.提交阶段(commit phase)
在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。
当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。
参与者在接收到协调者发来的消息后将执行响应的操作。

(3)两阶段提交的缺点

1.同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。
当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。

2.单点故障。由于协调者的重要性,一旦协调者发生故障。
参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)

3.数据不一致。在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。
而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。

(4)两阶段提交无法解决的问题

当协调者出错,同时参与者也出错时,两阶段无法保证事务执行的完整性。
考虑协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。
那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

2、三阶段提交协议

三阶段提交协议在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。

针对两阶段提交存在的问题,三阶段提交协议通过引入一个“预询盘”阶段,以及超时策略来减少整个集群的阻塞时间,提升系统性能。三阶段提交的三个阶段分别为:

第一阶段:can_commit

3PC的CanCommit阶段其实和2PC的准备阶段很像。
协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

第二阶段:pre_commit

Coordinator根据Cohort的反应情况来决定是否可以继续事务的PreCommit操作。
根据响应情况,有以下两种可能。
A.假如Coordinator从所有的Cohort获得的反馈都是Yes响应,那么就会进行事务的预执行:
发送预提交请求。Coordinator向Cohort发送PreCommit请求,并进入Prepared阶段。
事务预提交。Cohort接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
响应反馈。如果Cohort成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

B.假如有任何一个Cohort向Coordinator发送了No响应,或者等待超时之后,Coordinator都没有接到Cohort的响应,那么就中断事务:
发送中断请求。Coordinator向所有Cohort发送abort请求。
中断事务。Cohort收到来自Coordinator的abort请求之后(或超时之后,仍未收到Cohort的请求),执行事务的中断。

第三阶段:do_commit

该阶段进行真正的事务提交,也可以分为以下两种情况:

执行提交

A.发送提交请求。Coordinator接收到Cohort发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有Cohort发送doCommit请求。
B.事务提交。Cohort接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
C.响应反馈。事务提交完之后,向Coordinator发送ACK响应。
D.完成事务。Coordinator接收到所有Cohort的ACK响应之后,完成事务。

中断事务

Coordinator没有接收到Cohort发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

三阶段提交协议的缺点

如果进入PreCommit后,Coordinator发出的是abort请求,假设只有一个Cohort收到并进行了abort操作,
而其他对于系统状态未知的Cohort会根据3PC选择继续Commit,此时系统状态发生不一致性。

三阶段提交协议和两阶段提交协议的不同

对于协调者(Coordinator)和参与者(Cohort)都设置了超时机制(在2PC中,只有协调者拥有超时机制,即如果在一定时间内没有收到cohort的消息则默认失败)。
在2PC的准备阶段和提交阶段之间,插入预提交阶段,使3PC拥有CanCommit、PreCommit、DoCommit三个阶段。
PreCommit是一个缓冲,保证了在最后提交阶段之前各参与节点的状态是一致的。

2、TCC

TCC是一种比较成熟的分布式事务解决方案,可用于解决跨库操作的数据一致性问题;

TCC是服务化的两阶段编程模型,其Try、Confirm、Cancel 3个方法均由业务编码实现;

**Try:**操作作为一阶段,负责资源的检查和预留

Confirm:操作作为二阶段提交操作,执行真正的业务

Cancel:预留资源的取消

实现流程:

分为两个阶段:

一阶段(Try):进行资源的检查和预留

二阶段(Confirm):执行真正的业务

优势和缺点

**优势:**TCC执行的每一个阶段都会提交本地事务并释放锁,不需要等待其他事务的执行结果。如果其他事务执行失败,最后不是进行回滚,而是执行补偿操作(生成一个逆向sql)。这样就避免了资源的长期锁定和阻塞等待。执行效率比较高,属于性能比较好的分布式事务方式。

缺点:

  • 代码入侵:需要认为编写代码实现Try,Confirm,cancel。代码入侵不叫多
  • 开发成本高:一个业务要拆分成三个步骤,分别编写业务实现,业务编写比较复杂
  • 安全性:cancel如果执行失败,资源就无法释放,需要引入重试机制,而重试机制可能导致重复执行,还要考虑重试的幂等问题

使用场景

  • 对事务有一定的一致性要求(最终一致)
  • 对性能要求较高
  • 开发人员具备较高编码能力和幂等处理经验

3、可靠消息服务

参考:https://blog.csdn.net/qq_39409110/article/details/88081689

将远程分布式事务拆分成一系列的本地事务

利用了 各系统本地的事务来实现分布式事务。

本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候 将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。

然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。

如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。

这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。

可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。

基本原理

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

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

如何保证上游服务对消息的 100% 可靠投递?

我们如何保证消息 100% 的可靠投递,一定会从上游服务投递到下游服务?别着急,下面我们来逐一分析。

如果上游服务给可靠消息服务发送待确认消息的过程出错了,那没关系,上游服务可以感知到调用异常的,就不用执行下面的流程了,这是没问题的。

如果上游服务操作完本地数据库之后,通知可靠消息服务确认消息或者删除消息的时候,出现了问题。

比如:没通知成功,或者没执行成功,或者是可靠消息服务没成功的投递消息到 MQ。这一系列步骤出了问题怎么办?

其实也没关系,因为在这些情况下,那条消息在可靠消息服务的数据库里的状态会一直是“待确认”。

此时,我们在可靠消息服务里开发一个后台定时运行的线程,不停的检查各个消息的状态。

如果一直是“待确认”状态,就认为这个消息出了点什么问题。此时的话,就可以回调上游服务提供的一个接口,问问说,兄弟,这个消息对应的数据库操作,你执行成功了没啊?

如果上游服务答复说,我执行成功了,那么可靠消息服务将消息状态修改为“已发送”,同时投递消息到 MQ。

如果上游服务答复说,没执行成功,那么可靠消息服务将数据库中的消息删除即可。

通过这套机制,就可以保证,可靠消息服务一定会尝试完成消息到 MQ 的投递。

如何保证下游服务对消息的 100% 可靠接收?

那如果下游服务消费消息出了问题,没消费到?或者是下游服务对消息的处理失败了,怎么办?

其实也没关系,在可靠消息服务里开发一个后台线程,不断的检查消息状态。

如果消息状态一直是“已发送”,始终没有变成“已完成”,那么就说明下游服务始终没有处理成功。

此时可靠消息服务就可以再次尝试重新投递消息到 MQ,让下游服务来再次处理。

只要下游服务的接口逻辑实现幂等性,保证多次处理一个消息,不会插入重复数据即可。

如何基于 RabbitMQ 来实现可靠消息最终一致性方案?

4、Seata模式

事务消息与普通消息的区别就在于消息生产环节,生产者首先预发送一条消息到MQ(这也被称为发送half消息)
MQ接受到消息后,先进行持久化,则存储中会新增一条状态为待发送的消息
然后返回ACK给消息生产者,此时MQ不会触发消息推送事件
生产者预发送消息成功后,执行本地事务
执行本地事务,执行完成后,发送执行结果给MQ
MQ会根据结果删除或者更新消息状态为可发送
如果消息状态更新为可发送,则MQ会push消息给消费者,后面消息的消费和普通消息是一样的
注意点:由于MQ通常都会保证消息能够投递成功,因此,如果业务没有及时返回ACK结果,那么就有可能造成MQ的重复消息投递问题。因此,对于消息最终一致性的方案,消息的消费者必须要对消息的消费支持幂等,不能造成同一条消息的重复消费的情况。

Seata解决方案
解决分布式事务问题,有两个设计初衷

对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入
高性能:减少分布式事务解决方案所带来的性能消耗
seata中有两种分布式事务实现方案,AT及TCC

AT模式主要关注多 DB 访问的数据一致性,当然也包括多服务下的多 DB 数据访问一致性问题
TCC 模式主要关注业务拆分,在按照业务横向扩展资源时,解决微服务间调用的一致性问题

原文链接:https://blog.csdn.net/hosaos/article/details/89136666

5、AT模式(Automatic Transaction)

是一种无侵入的分布式事务解决方案。

可以看作是对TCC或者二阶段提交模型的一种优化,解决了TCC 模式中的代码侵入、编码复杂等问题

AT模式下只需要关注自己“业务SQL”,用户的“业务SQL”作为一阶段,Seata框架会自动生成事务的第二阶段提交和回滚

举个例子:

当用户下订单时,执行以下三步流程:

  1. 订单系统保存订单
  2. 订单系统调用库存服务,减少商品库存
  3. 订单系统调用账户服务,扣减用户金额

这三步要作为一个整体事务进行管理,要么整体成功,要么整体失败。

角色:

  • Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚
  • Transaction Manager ™: 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
  • Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚

AT基本原理

Seata AT 事务分两个阶段来管理全局事务:
第一阶段: 执行各分支事务,返回执行结果

第一阶段的你写的SQL语句会被Seata框架自动拦截,根据你的sql生成一个select存到一个镜像里去名叫before image,然后再放行你的sql,然后再查一次sql存到镜像after image里。

第二阶段: 控制全局事务最终提交或回滚

如果决议是全局提交,此时分支事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),Phase2 可以非常快速地完成

如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚

猜你喜欢

转载自blog.csdn.net/python_mopagunda/article/details/116724816