分布式事务

一、分布式事务概念

1.事务

  • 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。
  • 数据库事务中的四大特性,ACID
    • 原子性(Atomicity)
    • 一致性(Consistency)
    • 隔离性(Isolation)
    • 持久性(Durability)

2.分布式事务

  • 分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
    • 指有一个事务试图将更改提交给两个或更多数据源,且这些数据源的改变往往无法在同一个原子操作完成。
    • 简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败
    • 本质上来说,分布式事务就是为了保证不同数据库的数据一致性。

3.分布式事务的新理论

CAP

在这里插入图片描述

  • C (一致性):对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。
  • A (可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。
  • P (分区容错性):当出现网络分区后,系统能够继续工作。打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。
  • 三者往往不能共有,对于CP来说,放弃可用性,追求一致性和分区容错性。对于AP来说,放弃一致性(这里说的一致性是强一致性),追求分区容错性和可用性,这是很多分布式系统设计时的选择,后面的BASE也是根据AP来扩展。

BASE

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

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

4.分布式计算的八大谬论

许多开发人员对分布式系统做出的八个错误假设。从长远来看,这些假设通常被证明是错误的,而且会导致难以修复的错误,我们应当注意避免这些错误假设带来的问题:

  1. 网络可靠。 The network is reliable.
  2. 延迟为零。 Latency is zero.
  3. 带宽是无限的。 Bandwidth is infinite.
  4. 网络是安全的。 The network is secure.
  5. 拓扑不会改变。 Topology doesn’t change.
  6. 只有一个管理员。 There is one administrator.
  7. 运输成本为零。 Transport cost is zero.
  8. 网络是同质的。The network is homogeneous.

5.分布式事务隐藏在冰山之下

即分布式事务将一个事务分为多个子事务,且必须保证所有的子事务全部成功,但我们往往只会关注其中的某几个子事务,其余的子事务对某些用户是透明的,如:买书的人只会关心自己的钱交没交成功,和书收没收到,对于库存的调度,商家是否收到款都不会在意,所以可能会出现付款服务可能会成功完成,但送货服务仍然可能失败的问题。


二、分布式事务:解决方案(思想)

1.每个业务事务都需要导致单个同步提交

(every business event needs to result in a single synchronous commit.)

简单理解

对于不同数据源的更新可以异步发生,但是要保证将每个数据源的更改效果传递给其他数据源。

实现方法:选择一个服务主要处理该事务,该服务应该负责

  • 对该事务进行单个提交;
  • 将修改效果异步传递给其他服务。
  • 例:下图的shipping service是主要服务,他既负责返回付款成功,也负责保证数据库的修改
    在这里插入图片描述

安全(鲁棒)的分布式事务

  • 为了保证上述的解决方案是相对安全和强大(鲁棒性)的, 我们应该保证以下三个方面: Commander(指挥官), Retries(重试) and Idempotence(幂等)
    • Commander
      负责管理分布式事务,它必须知道命令是否被执行并且协调着执行这些命令,在大多数情况下,只会有两条指令:
      1. Effect the remote update.(影响远程更新,即将修改效果传递给其他数据源)
      2. Remove the item from the commander’s persistent queue.(从队列中移除已经完成的命令,如果该命令未完成则会一直留在队列中)
    • Retry
      如果指挥官试图实现远程更新并且没有得到响应,则必须重试。如果指挥官获得成功的响应但未能更新自己的队列,那么当其队列再次可用时,它需要重试。即,通过不断重试确保失败的任务能够最终成功完成
    • Idempotence
      • 是一个特性:提交多次同样的事件得到的结果和提交一次该事件得到的结果是一样的。
      • 因为我们会使用重试,所以要确保提交多次的结果和提交一次是一样的。

2.最终一致性(Eventual Consistency)

定义

  • 最终一致是指经过一段时间后,所有节点数据都将会达到一致。在这段时间内允许部分不一致。

最终一致性的两个后果

  1. 当我们无法通过时,我们将重试。你重试多少次,多长时间?
    答:取决于具体情况。但有一件事是肯定的:如果你正在进行重试,那是因为你的系统出了问题,所以你要监视它。重试点是为了实现自我修复,但你需要意识到需要治疗。(即要对失败的地方进行跟踪和修复)
  2. 因为一致性是最终的,所以最终我们可能会称之为业务一致性冲突。
    • 即最后可能发现会出现冲突,无法一致,如:假用户显示“购买”页面时有一本书可用,但是在处理付款和发送到装运服务的装运请求时,没有剩余的库存可供发货。)
    • 我们需要做的是在系统中识别这样的冲突并将它们提升起来处理。这可以自动完成,例如通过退还用户和发送道歉电子邮件,或者手动完成,例如通过将冲突排队为雇员要处理的事情。

3.备选方案

避免使用分布式事务

当您发现由于一个事件而需要在两个位置更新数据时,您可以考虑重构您的体系结构以移动一些数据,以便您可以在一个事务中在一个位置更新所有数据。

做不安全的分布式事务并准备好处理错误情况

  • 需要做以下两件事情
    1. 以数据协调的形式进行一些检测
    2. 你需要确保你拥有所有的数据
  • 这主要适用于上面提到的异步模式,唯一真正的区别在于:
    • 通过异步+重试,您可以获得自我修复系统的概率。
    • 使用分布式事务+检测半提交,您需要手动处理每个故障

三、分布式事务:常见解决方案

1. 2PC(2 phase commit)

在这里插入图片描述

  • 第一阶段:事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.(锁住相关资源)
  • 第二阶段:事务协调器要求每个数据库提交数据,或者回滚数据。

2. TCC(Try-Confirm-Cancel)

  • 对于TCC的解释:
    • Try阶段:尝试执行,完成所有业务检查(一致性),预留必须业务资源(准隔离性)
    • Confirm阶段:确认执行真正执行业务,不作任何业务检查,只使用Try阶段预留的业务资源,Confirm操作满足幂等性。要求具备幂等设计,Confirm失败后需要进行重试。
    • Cancel阶段:取消执行,释放Try阶段预留的业务资源。Cancel操作满足幂等性,Cancel阶段的异常和Confirm阶段异常处理方案基本上一致。
  • 举个简单的例子:
    如果你用100元买了一瓶水,Try阶段:你需要向你的钱包检查是否够100元并锁住这100元,水也是一样的。如果有一个失败,则进行cancel(释放这100元和这一瓶水),如果cancel失败不论什么失败都进行重试cancel,所以需要保持幂等。如果都成功,则进行confirm,确认这100元扣,和这一瓶水被卖,如果confirm失败无论什么失败则重试(会依靠活动日志进行重试)

3. 本地消息表

此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。
在这里插入图片描述
对于本地消息队列来说核心是把大事务转变为小事务。还是举上面用100元去买一瓶水的例子。

  1. 当你扣钱的时候,你需要在你扣钱的服务器上新增加一个本地消息表,你需要把你扣钱和写入减去水的库存到本地消息表放入同一个事务(依靠数据库本地事务保证一致性。
  2. 这个时候有个定时任务去轮询这个本地事务表,把没有发送的消息,扔给商品库存服务器,叫他减去水的库存,到达商品服务器之后这个时候得先写入这个服务器的事务表,然后进行扣减,扣减成功后,更新事务表中的状态。
  3. 商品服务器通过定时任务扫描消息表或者直接通知扣钱服务器,扣钱服务器本地消息表进行状态更新。
  4. 针对一些异常情况,定时扫描未成功处理的消息,进行重新发送,在商品服务器接到消息之后,首先判断是否是重复的,如果已经接收,在判断是否执行,如果执行在马上又进行通知事务,如果未执行,需要重新执行需要由业务保证幂等,也就是不会多扣一瓶水。

4. Saga事务(补偿交易Compensating Transaction)

其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。

  • Saga的组成:
    • 每个Saga由一系列sub-transaction Ti 组成
    • 每个Ti 都有对应的补偿动作Ci,补偿动作用于撤销Ti造成的结果, 这里的每个T都是一个本地事务。
      可以看到,和TCC相比,Saga没有“预留 try”动作,它的Ti就是直接提交到库。
  • 问题: 注意的是,在saga模式中不能保证隔离性,因为没有锁住资源,其他事务依然可以覆盖或者影响当前事务。
    • 例:
      还是拿100元买一瓶水的例子来说,这里定义
      T1=扣100元 T2=给用户加一瓶水 T3=减库存一瓶水
      C1=加100元 C2=给用户减一瓶水 C3=给库存加一瓶水
      我们一次进行T1,T2,T3如果发生问题,就执行发生问题的C操作的反向。
      上面说到的隔离性的问题会出现在,如果执行到T3这个时候需要执行回滚,但是这个用户已经把水喝了(另外一个事务),回滚的时候就会发现,无法给用户减一瓶水了。这就是事务之间没有隔离性的问题

四、参考资料

https://www.grahamlea.com/2016/08/distributed-transactions-microservices-icebergs/
https://juejin.im/post/5b5a0bf9f265da0f6523913b
https://zhuanlan.zhihu.com/p/25933039

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值