写点什么

分布式事务与分布式系统

用户头像
邱学喆
关注
发布于: 2021 年 05 月 18 日
分布式事务与分布式系统

一. 概述

深入理解 spring 框架之事务管理我们讲解了 spring 事务的管理,只涉及到单数据源的问题,无论业务系统发生了任何异常,数据库都能有效的保证数据的一致性。然而现在微服务盛行的时代,业务系统不单单只与一个数据源打交道,还会和其他业务系统打交道,其他业务系统可以理解为另外一套数据库。

我们将"业务系统"为协调者,数据库以及其他业务系统为"参与者"。当协调者在整个事务过程中,将参与者 A 提交了事务,然而参与者 B 提交事务失败。这时协调者需要将参与者 A 需要回滚,然而回滚失败,协调者该如何去处理该异常场景。例如,协调者向参与者 B 提交事务超时,参与者是否已经提交了事务,还是未提交事务;这个就是微服务常见的分布式事务问题。

在数据量暴增,访问量很大的情况下,往往一个单机服务器是无法有效的提供服务的;以及当服务器宕机后无法提供服务。这时就提出了这么分布式系统这个概念,在《分布式系统概念与设计》一书中,对分布式系统做了如下定义:分布式系统是一个硬件或软件组件分布在不同的网络计算机上,彼此之间仅仅通过消息传递进行通信和协调的系统。基于该分布式系统,衍生出了分布式事务这个专业术语。

二. CAP 理论

CAP 理论有三个特性

  • Consistency 数据一致性:副本中的数据一致性;

  • Availability 可用性:当某个节点宕机后,其他节点依然可以提供服务

  • Partition Tolerance 分区容错性:当网络出现抖动,节点之间短时间出现通信中断。

三. Base 理论

基于无法满足 CAP 条件,提出了最终一致性解决方案。其摒弃短暂的不可用。现在说的 paxos 算法以及 raft 算法都是基于这样子理论进行设计,这纯属个人理解

  • 基本可用(Basically Available)

  • 软状态(Soft State)

  • 最终一致性(Eventually Consistent)

四. 分布式事务

分布式事务的解决方案。

分布式事务中涉及到几个关键的概念,X/Open 组织(即现在的 Open Group )定义了分布式事务处理模型。 X/Open DTP 模型( 1994 )包括应用程序( AP )、事务管理器( TM )、资源管理器( RM )、通信资源管理器( CRM )四部分。一般,常见的事务管理器( TM )是交易中间件,常见的资源管理器( RM )是数据库,常见的通信资源管理器( CRM )是消息中间件。



1. 两阶段提交协议

两阶段提交协议分为两个阶段进行,第一阶段是预处理阶段,也就是发送 SQL 语句,业务系统校验数据库返回的响应进行校验。第二阶段是提交阶段,也就是发送 commit 指令给数据库。

  • 第一阶段 预处理阶段:事务协调者(事务管理器)给每个参与者(资源管理器)发送 Prepare 消息,每个参与者要么直接返回失败 ,要么在本地执行事务,写本地的 redo 和 undo 日志,但不提交。其预处理要分为三个子步骤:

  • 协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。

  • 参与者节点执行询问发起为止的所有事务操作,并将 Undo 信息和 Redo 信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)

    各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个”同意”消息;如果参与者节点的事务操作实际执行失败,则它返回一个”中止”消息。

  • 第二阶段 提交阶段:如果协调者收到了参与者的失败或超时,直接给每个参与者发送回滚(rollback)操作;否则发送提交(commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。

无论最后结果如何,第二阶段都结束当前事务。

两阶段有如下几个缺点:

  • 同步阻塞问题。执行过程中,所有参与节点都是事务阻塞型的。在第一阶段,做预处理时会锁住对应的资源,到第二阶段收到协调者发送的 commit 或者 rollback 消息才释放锁资源,则协调者发送回滚操作才释放锁资源。在锁资源到释放资源过程,会阻塞第三方节点访问该锁资源。

  • 单点故障。由于协调者的重要性,一旦协调者发生故障,则无法继续完成事务操作。

  • 数据不一致。在第二阶段中,当协调者向发送 commit 消息请求之后,发生了局部网络异常或者发送 commit 请求过程中协调者发生了故障,会出现部分参与者接收到 commit 消息并进行提交,而其他参与者未收到 commit 消息则无法提交,于是整个事务中就会出现数据不一致的现象。

  • 二阶段无法解决的问题。协调者再发出 commit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

其在应用工程中很少使用,但其有一定的参考性;

2. 三阶段提交协议

基于两阶段的问题,提出了三阶段提交协议,主要的改动点:

  • 引入了超时机制,同时在协调者和参与者都引入超时机制

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

三阶段提交提交协议,分为三个阶段,CanCommit 阶段、PreCommit 阶段、DoCommit 阶段。

简单理解即可,在日常的开发中,很少使用;我们重点了解 TCC 协议

3. TCC 事务协议

TCC 是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(回滚)操作。它分为三个阶段:

  • Try 阶段:主要是对业务系统做检测即资源预留

  • Confirm 阶段:主要是对业务系统做确认提交,Try 阶段执行成功并开始执行 Confirm 阶段时,默认 Confirm 阶段是不会出错的。即 Try 成功,Confirm 一定会成功

  • Cancel 阶段:主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。

  • 当其中一个子业务系统返回失败,或者超时,则这次事务以失败处理,进入不到第二阶段。那么需要向所有子业务系统发送回滚请求,使其子业务系统释放其资源当发送回滚请求超时时,则重复发送,以确保子业务系统回滚成功。在扣减库存而言,则解冻该库存数量。

补充:

  • 为了避免重复发送消息,所有的子业务系统都必须提供接口幂等性原则

  • 为了避免主业务系统宕机,无法知晓这次事务的执行情况,需提供一个机制,记录这次事务的执行状态,待服务器重新提供服务或者定时任务定时处理事务后续的动作,以确保事务的完整性

问题:

  • 当提交超时时,该怎么处理?

需业务场景需要,是否需要回滚,还是依然继续提交。

  • 当只要有一个子业务系统返回失败或者超时,跳到 Cancel 阶段进行回滚后直接结束这次事务。如果回滚超时或者失败该如何处理?或者中途宕机,有该如何处理?

在业务系统中需要记录该事务的执行状态以及子事务的执行状态;当发现超时或者宕机时,可以从中回复执行;

改善点:

在 Confirm 阶段、Cancel 阶段是否需要异步执行,可以有效的提供响应速度。

4. 最大努力通知

通过异步的形式,或者采用第三方组件将请求确保将消息发送给子业务系统。其需要评估该请求在子业务必须执行成功的;否则子业务系统执行失败,主业务系统还需要进行回滚,逻辑将会变得复杂;什么情况下,会采用该机制呢?主要是发短信等必须执行成功的场景下,才会采用该方法;

4.1 本地消息表

将请求保存到本地数据库,交由后台程序异步将请求推送给子业务系统。一般来讲,设计重试 3 次即可,当连续发送三次失败,即通知运维人员进行跟进处理,具体什么原因导致;如果涉及不限次数的发送,那么就会导致业务系统压力过大;

4.2 半消息/最终一致性

采用 MQ,redis 等中间件,事务性的将请求推送子业务系统;前提下,需要确保该中间件是否支持事务性操作,如不支持,需业务系统评估如何才能达到事务性推送。例如 RocketMQ 消息中间,是支持事务性操作,其原理是将消息保存到临时队列,当业务系统 commit 操作时,才将该消息从临时队列移出到目标队列。

五. 副本一致性

5.1 raft

该算法中有三种角色,follower(跟随着)、candidate(候选者)、Leader(领导者)

其主要有两个环节,第一个环节是选举 Leader;第二环节是副本拷贝。

当集群中,没有 Leader 时或者 Leader 宕机时,有资格的 follower 就会变为 candidate,接着会竞聘 Leader。谁先发起竞聘,谁就有限当 Leader。至于谁先发送,其给每个 candidate 设置一个随机时间,当时间一到还没收到其他 candidate 竞聘请求的,则其会向各个 candidate 发送竞聘请求;超过半数,自动变为 Leader。

当客户端发送请求过来时,有 Leader 接收处理,并广播给其他 follower;当收到半数的回复成功时,即可确认该请求处理成功。

5.2 paxos

5.2.1 目标

多节点之间对提案达成共识,意味着大多数节点都接受该提案。

5.2.2 角色

Proposer::提议者, 专门用来发出提案(一个值);当超过半数,就认为该提案被选定的。

Acceptor:接收者,专门用来接收提案,并判断是否通过该提案;

Learner:学习者,用来学习被选定的提案(个人理解,可有可无)。

5.2.3 basic-paxos

5.2.3.1 流程

其简单的流程图如下,这里不考虑异常,竞争下的场景;

在 prepare 阶段,Proposer 向 Acceptor 咨询自己是否有提议权(或者发表权)。一旦超过半数 Acceptor 回复 OK,则进入 accept 第二阶段。Proposer 发发送 accept 请求,超过 Acceptor 半数回复 OK。则结束这次提议。至于 Learner 通过谁去学习这个【proposal-value】,

  1. Acceptor 接收 prepare 请求的逻辑

如果 Acceptor 没有批准过提议权的,则无保留意见批准,并记录该 minProposal=【proposal-id】。

如果 Acceptor 之前已经批准过提议权 minProposal,会与现在收到的提议权携带的【proposal-id】进行比较。minProposal>=proposal-id,则拒绝该提议权;如果 minProposal<proposal-id,则批准该提议权,并记录该 minProposal=【proposal-id】。并将 Acceptor 自身的【proposal-value】以及【minProposal】发给 Proposer。这里留下一个提问,Acceptor 会返回不同的【proposal-value】麽?

  1. Proposer 处理 prepare 请求的响应的逻辑

prepare 响应中如果有【proposal-value】A,则设置 Proposer 的【proposal-value】为 A,否则自行做决定该值。

如果超过半数回复同意的,则进入第二阶段去发表意见。

否则重新发起提议,发送 prepare 消息获取提议权。

  1. Proposer 发送 accept 请求的逻辑

将【proposal-id】与【proposal-value】发送给 Acceptor。

  1. Acceptor 接收 accept 请求的逻辑

如果 Acceptor 当前的【proposal-ID】A 与这次 accept 请求中的【提议 ID】B 相比较,如果不相等,则回复【NO】。否则设置 Acceptor 的【proposal-value】的值为 accept 请求中的【proposal-value】,并回复【OK】响应。

  1. Proposer 接收 accept 请求的响应的处理逻辑

如果超过半数回复 OK 的,则不用管理其他回复【NO】响应,把该【proposal-value】发送给 Learner 进行学习。否则重新发送 prepare 消息,再进行一次提议;最终【proposal-value】是一致的。

5.2.3.2 深入探讨

  1. Acceptor 是否会存在多个不同的【proposal-value】?

答案:是不会存在多个不同【proposal-value】。因为在 prepare 阶段,如果 Acceptor 已经有【proposal-value】值,Proposal 会拿这个值在 accept 阶段进行发送;如果没有的话,Proposal 会自行生成一个【proposal-value】值到 accept 阶段进行发送;同时在 accpet 阶段,如果 Acceptor 会去校验【proposal-id】是否相同,相同则设置该【proposal-value】值。有点类似乐观锁的机制。

  1. 如果 Acceptor 小部分宕机,或者 Proposal 在提议过程中发生了宕机,会不会出现【proposal-value】不一致?

答案:不会。因为该协议是只要超过半数的 Acceptor 在运转,依然可以提供服务。而 Proposal 宕机,Proposal 重启进行下一轮提议或者交由其他 Proposer 提议,最终【proposal-value】是达成一致。

  1. 并发性问题,性能低效,以及容易出现活锁问题

在多个 Proposal 进行提议时,容易对【proposal-id】进行竞争,导致性能低效,极端情况下,出现活锁现象。以及为了达成共识,需要先 prepare 阶段以及 accept 阶段,会有损耗,也是导致性能低效的原因之一。

5.2.4 multi-paxos

这个就是基于 basic-paxos 中暴露的问题,提供出来的解决方案。

  • 添加一个 leader 角色,所有的提议都由 Leader 角色来发起,这样子有效的避免竞争带来的性能低效以及活锁现象。但是从而导致 Leader 角色的服务压力过大

  • 有效的减少 Prepare 阶段,在满足特定的场景,Leader 可以直接跳过 prepare 阶段,直接到 accept 阶段,减少交互次数,可以有效的减少性能低效问题。

问题,

  1. 什么场景下,可以直接跳过 prepare 阶段?

只有当 Acceptor 自身发现只接收到一个 Leader 的请求时,就会告知 Leader,让其跳过 prepare 阶段;

  1. Leader 是如何选举出来的?

兰伯特博士提出比较简单的方式,就是哪个 server 的 ID 最大,谁就是最大。也可以采用 Raft 机制,谁先发起竞聘,谁就是 Leader。

5.2.5 总结

出现网络分区,该怎么处理?兰伯特博士提出一个并发数参数来短时间避免网络分区问题,当网路分区现象发生时,超过多少笔请求接收完后,网络分区才真正会出现;例如,有 9 个节点,当网络分区发生时,6 个节点互通,而另外 3 个节点互通。当请求过来时,切好落在 3 节点区域,这时该请求是无法通过的,因为当前节点数是 9,需要过半数才能通过;当请求数超过指定的并发数时,该 3 个节点才真正的形成集群,只要超过 2 个节点的通过,即可接收该请求。

讲了 basic-paxos 和 multi-paxos,都是可以进行多副本拷贝的,意味着进行多次 basic-paxos 实例。但仍需全局记录多次 baxis-paxos 的状态,以便于保证各个 basic-paxos 实例的一致性。在引用文章介绍 multi-paxos 例子中,采用的是数组进行记录其状态。

并不是所有 Proposal 都可以发起 accept 请求的,只有”最大“的 Proposal 才能发起该请求。”最大“指的发起 basic-paxos 的数量。我们可以理解为 Proposal-id 的值。每发起一次 basic-paxos 实例,+1。所以,所有节点都必须保存该变量,以至于发起一次 basic-paxos,在其基础上+1。

当 Acceptor 接受 accept 请求时宕机再次重启时,向"最大"的 Proposal 发起同步请求。在 multi-paxos 例子中,是向 Leader 发送同步请求,导致 Leader 的压力过大,可以向其他 Proposal 发送同步请求。那么就需要每个节点记录各个节点的 proposal-id 的数据,可以统称元数据。这样子同步请求可以避免发给 Leader。有效减少 Leader 压力。这只是我的理解,纯属参考

有关更多细节讲解的 paxos 文章,可以仔细查阅我引用的文章。

六. 分布式系统

我们所说的分布式系统,主要是解决计算、存储数据量的问题。无论是计算还是存储,其归根于如何拆分数据,让其数据均匀的分布在各个节点上;

  • 哈希方式 通过哈希算法将数据分布到不同的机器上

  • 按数据范围分布 有点类似与报表系统对数据量大的表按时间维度分区

  • 一致性哈希 结合哈希方式,采用环形的形式,将数据分布到各个节点上;其中提出了虚拟节点的方法,有效的避免数据不均匀的问题。

同时分布式系统也需要高可用,所以需要多副本,从而避免服务宕机,其他副本服务依然提供服务;至于如何保证副本一致性,可以看上面的讲解。

七. 总结

分布式事务,我们主要是采用 TCC 协议,保证事务的一致性;当个别场景可以采用第三方组件,从而减少服务的压力;


引用

  1. 关于分布式事务、两阶段提交协议、三阶提交协议,Hollis 公众号

  2. https://www.bytesoft.org/

  3. 用paxos实现多副本日志系统--basic paxos部分

  4. 用paxos实现多副本日志系统--multi paxos部分

  5. Distributed Transaction Processing: Reference Model , Version 3

  6. 如何浅显易懂地解说 Paxos 的算法


发布于: 2021 年 05 月 18 日阅读数: 559
用户头像

邱学喆

关注

计算机原理的深度解读,源码分析。 2018.08.26 加入

在IT领域keep Learning。要知其然,也要知其所以然。原理的爱好,源码的阅读。输出我对原理以及源码解读的理解。个人的仓库:https://gitee.com/Michael_Chan

评论 (14 条评论)

发布
用户头像
paxos限制条件:
P1. 一个Acceptor必须“批准”它收到的第一个“提案”

P2. 如果一个值为v的“提案被选中“, 那么更高编号的被选中的提案的值必须也为v 

P2a. 如果值为v的提案被选中, 那么后续任何acceptor能批准的更高版本的提案都必须要有值v. (对Acceptor提出要求)

P2b. 如果值为v的提案被选中, 那么后续任意的proposer所起草的更高编号的提案的值必须也是v. (对Proposer提出要求)

P2c. 对于任意的N和V, 如果[N, V]被提出, 那么肯定存在一个由半数以上(majority)的acceptor组织的集合S, 满足下面两个条件之一

(a) S中不存在任何批准过编号小于N的提案的Acceptor

(b) S中所有Acceptor批准的编号小于N的提案中编号最大的值为V
展开
2021 年 05 月 21 日 22:12
回复
用户头像
一致性算法其实就是一个“投票”算法,保证分布式系统状态的一致和可追溯性。至于投票的目的因各个系统而异(例如以数据一致性为目的)。作者的理解是正确的👍

一致性是指数据的一致性呢,还是状态的一致性

2021 年 05 月 21 日 03:05
回复
我已删除该论述,状态一致性意味着副本的一致性的。该两个概念的同等的。为了避免误解,删除该文字。
2021 年 05 月 21 日 22:14
回复
用户头像
不同于Raft,Paxos在选举时是不会出现“锁”的,proposal number生成算法保证了其完备性

极端情况下,出现活锁现象

2021 年 05 月 21 日 02:59
回复
proposal number生成,其原理比较简单的。选举时是不会出现”锁“的,因为Proposal发起的proposal-id值大,就投票给它。并且会记录该proposal-id。同时接收其他proposal时,会拿这个值进行比较。
极端情况下,出现活锁现象。主要是指这两阶段结合起来,在高并发的情况下有可能会出现活锁现象。
paxos协议,你可以理解为java底层的cas算法。该cas算法是乐观锁,但在高并发的情况下,依然会性能低效的情况的。所以,在java中的synchronized关键字,在高版本时优化了该逻辑,先乐观锁,尝试几次依然失败,就采用重量级锁的原因。
2021 年 05 月 21 日 22:23
回复
是的,你说的没错,在高并发场景会产生活锁,是我的表述不严谨^^。
看来作者对Java很熟悉👍
作者用CAS做比喻很赞,事实上计算机本身就是一个“分布式”系统,各级的cache coherence就是一个微型分布式系统,有很多可以借鉴的关于分布式系统的优化^^。👍
2021 年 05 月 22 日 01:04
回复
用户头像
😂 从哪里得出的结论呢?分布式存储的事务80%都是2PC,包括一些所谓的TCC中间件

其在工程中很少使用

2021 年 05 月 21 日 02:50
回复
该论述是从《分布式系统原理介绍》作者刘杰,摘抄下来的。我之所以同意该结论,是因为两阶段的缺陷很多(有可能我两阶段的协议中细节了解不多)。很多协议,包括你说的TCC中间件都是基于2PC协议基础优化衍生出来的一种叫法。严格来讲,确实分布式事务80%都是2PC。但我们不会说TCC就是2PC。
2021 年 05 月 21 日 22:31
回复
TCC不是2PC,这点没问题。这个跟2PC在工程中使用很少没有关系吧?如果作者熟悉各个数据库的架构和代码的话(包括一些分布式数据库)则会发现很少有不用2PC的。如果这里”工程“值得是一些非数据存储的应用程序,那倒是讲得通。
2021 年 05 月 22 日 00:37
回复
是的,是业务系统
2021 年 05 月 22 日 10:05
回复
用户头像
Paxos,Raft是一致性算法,与ACID或者BASE没有关系。如果硬要扯上关系,Paxos和Raft也是保证的“强一致性”,跟BASE完全不同

现在说的 paxos 算法以及 raft 算法都是基于这样子理论进行设计,这纯属个人理解

2021 年 05 月 21 日 02:48
回复
一致性算法,与ACID没有关系的,但与BASE是有关系的。分布式系统目标是高可用(CAP理论中A可用性)。为了实现高可用,就需要有副本的存在。一旦一个节点宕机,副本可以直接顶上。那么如何确保副本的一致性(CAP理论中的P一致性),就是paxos算法与raft算法,当然还有其他的算法。同时paxos算法与raft算法也有衡量了可用性,所以采用的过半协议,只要超过半数同意即可。当出现网络分区时,会对其算法造成一定的影响,有可能会出现数据不一致问题。所以才提出来的BASE理论,保证”最终一致性“,保证服务基本可用。
2021 年 05 月 21 日 22:46
回复
你说的都没有问题,只是概念没搞清楚。类似paxos,raft这种“一致性”算法其实叫consensus algorithm, 严格来讲叫共识算法,是用来确保参与者对投票结果产生共识,跟BASE没有一点关系。我们所谓的ACID,BASE里面说的一致是“consistency”,是从client或者accessor的角度的。这两个概念是完全不同的领域。
碰巧昨天看到另一个作者在infoq介绍paxos的文章也把一致性算法说是“consistency algorithm”,似乎这个误区很普遍😂
2021 年 05 月 22 日 00:44
回复
所以说中文博大精深,也同时让人误解啊。在借鉴术语时,需要留意的
2021 年 05 月 22 日 10:08
回复
没有更多了
分布式事务与分布式系统