分布式事务

1 篇文章 0 订阅
1 篇文章 0 订阅


什么是分布式事务?

随着微服务架构的普及,一个大型业务系统往往由若干个子系统构成,这些子系统又拥有各自独立的数据库。往往一个业务流程需要由多个子系统共同完成,而且这些操作可能需要在一个事务中完成。
此时,我们就需要在数据库之上通过某种手段,实现支持跨数据库的事务支持,这也就是大家常说的“分布式事务”。


CAP理论

C:Consistency 一致性,同一数据的多个副本是否实时相同。


A:Availability 可用性,一定时间内 & 系统返回一个明确的结果 则称为该系统可用。


P:Partition tolerance 分区容错性,将同一服务分布在多个系统中,从而保证某一个系统宕机,仍然有其他系统提供相同的服务。


BASE理论

分区容错性是分布式系统的根本,如果分区容错性不能满足,那使用分布式系统将失去意义。因此,我们只能通过牺牲一致性来换取系统的可用性和分区容错性。

在分布式系统中,C、A、P三个条件中我们最多只能选择两个。对于业务系统而言,我们往往选择牺牲一致性换取系统的可用性和分区容错性。不过这里要指出的是,所谓的“牺牲一致性”并不是完全放弃数据一致性,而是牺牲强一致性换取弱一致性。下面来介绍下BASE理论

BA:Basic Available 基本可用
整个系统在某些不可抗力的情况下,仍然能够保证“可用性”,即一定时间内仍然能够返回一个明确的结果。只不过“基本可用”和“高可用”的区别是:

  1. “一定时间”可以适当延长:当举行大促时,响应时间可以适当延长
  2. 给部分用户返回一个降级页面:给部分用户直接返回一个降级页面,从而缓解服务器压力。但要注意,返回降级页面仍然是返回明确结果。

S:Soft State:柔性状态
同一数据的不同副本的状态,可以不需要实时一致。


E:Eventual Consisstency:最终一致性
同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的。


幂等性

定义

  • HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。
  • 幂等性是系统服务对外一种承诺(而不是实现),承诺只要调用接口成功,外部多次调用对系统的影响是一致的。声明为幂等的服务会认为外部调用失败是常态,并且失败之后必然会有重试。

场景

业务开发中,经常会遇到重复提交的情况,无论是由于网络问题无法收到请求结果而重新发起请求,或是前端的操作抖动而造成重复提交情况。 在交易系统,支付系统这种重复提交造成的问题有尤其明显,比如:

  1. 用户在APP上连续点击了多次提交订单,后台应该只产生一个订单;
  2. 向支付宝发起支付请求,由于网络问题或系统BUG重发,支付宝应该只扣一次钱。 很显然,声明幂等的服务认为,外部调用者会存在多次调用的情况,为了防止外部多次调用对系统数据状态的发生多次改变,将服务设计成幂等。

幂等更多使用的情况是第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求,目的是多次确认第一次请求成功,却不会因多次请求而出现多次的状态变化。

案例

以SQL为例,有下面三种场景,只有第三种场景需要开发人员使用其他策略保证幂等性:

  1. SELECT col1 FROM tab1 WHER col2=2,无论执行多少次都不会改变状态,是天然的幂等。
  2. UPDATE tab1 SET col1=1 WHERE col2=2,无论执行成功多少次状态都是一致的,因此也是幂等操作。
  3. UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,这种不是幂等的。

实施策略

幂等需要通过唯一的业务单号来保证。也就是说相同的业务单号,认为是同一笔业务。使用这个唯一的业务单号来确保,后面多次的相同的业务单号的处理逻辑和执行效果是一致的。
以支付为例,在不考虑并发的情况下,实现幂等很简单:

  1. 先查询一下订单是否已经支付过
  2. 如果已经支付过,则返回支付成功;
  3. 如果没有支付,进行支付流程,修改订单状态为‘已支付’。

分布式事务模型


两阶段提交协议 2PC

二阶段提交算法的成立基于以下假设

  • 该分布式系统中,存在一个节点作为协调者(Coordinator),其他节点作为参与者(Cohorts)。且节点之间可以进行网络通信。
  • 所有节点都采用预写式日志,且日志被写入后即被保持在可靠的存储设备上,即使节点损坏不会导致日志数据的消失。
  • 所有节点不会永久性损坏,即使损坏后仍然可以恢复。

两个阶段:

  1. 第一阶段(投票阶段):

在这里插入图片描述

  1. 协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
  2. 参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。
  3. 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者节点的事务操作实际执行失败,则它返回一个"中止"消息。
  1. 第二阶段(提交执行阶段):

当协调者节点从所有参与者节点获得的相应消息都为"同意"时:
在这里插入图片描述

  1. 协调者节点向所有参与者节点发出"正式提交(commit)"的请求。
  2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送"完成"消息。
  4. 协调者节点受到所有参与者节点反馈的"完成"消息后,完成事务。

如果任一参与者节点在第一阶段返回的响应消息为"中止",或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
在这里插入图片描述

  1. 协调者节点向所有参与者节点发出"回滚操作(rollback)"的请求。
  2. 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送"回滚完成"消息。
  4. 协调者节点受到所有参与者节点反馈的"回滚完成"消息后,取消事务。

几个缺点:

  1. 性能问题
    XA协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。
  2. 协调者单点故障问题
    事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。
  3. 丢失消息导致的不一致问题。
    在XA协议的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

三阶段提交协议 3PC

两个改动点

  1. 引入超时机制。同时在协调者和参与者中都引入超时机制
  2. 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

三个阶段
CanCommit阶段(询问与响应)

协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

  1. 事务询问
    协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
  2. 响应反馈
    参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

PreCommit阶段(执行与响应)
协调者根据参与者的反应情况来决定是否可以进行事务的PreCommit操作。根据响应情况,有以下两种可能:

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

  1. 发送预提交请求
    协调者向参与者发送PreCommit请求,并进入Prepared阶段。
  2. 事务预提交
    参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
  3. 响应反馈
    如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
    假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

假如协调者从所有的参与者获得的反馈出现No响应,那么就中断事务。

  1. 发送中断请求
    协调者向所有参与者发送abort请求。
  2. 中断事务
    参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

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

执行提交

  1. 发送提交请求
    协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
  2. 事务提交
    参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
  3. 响应反馈
    事务提交完之后,向协调者发送Ack响应。
  4. 完成事务
    协调者接收到所有参与者的ack响应之后,完成事务。

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

  1. 发送中断请求
    协调者向所有参与者发送abort请求
  2. 事务回滚
    参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
  3. 反馈结果
    参与者完成事务回滚之后,向协调者发送ACK消息
  4. 中断事务
    协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

缺点:

  1. 所有的操作必须是事务性资源(比如数据库、消息队列、EJB组件等),存在使用局限性(微服务架构下多数使用HTTP协议),比较适合传统的单体应用;
  2. 由于是强一致性,资源需要在事务内部等待,性能影响较大,吞吐率不高,不适合高并发与高性能的业务场景;

消息事务+最终一致性

消息事务就是基于消息中间件的两阶段提交,本质上是对消息中间件的一种特殊利用,它是将本地事务和发消息放在了一个分布式事务里,保证要么本地操作成功成功并且对外发消息成功,要么两者都失败,开源的RocketMQ就支持这一特性:

  1. A系统向消息中间件发送一条预备消息
  2. 消息中间件保存预备消息并返回成功
  3. A执行本地事务
  4. A发送提交消息给消息中间件

通过以上4步完成了一个消息事务。对于以上的4个步骤,每个步骤都可能产生错误,下面一一分析:

  1. 步骤一出错,则整个事务失败,不会执行A的本地操作
  2. 步骤二出错,则整个事务失败,不会执行A的本地操作
  3. 步骤三出错,这时候需要回滚预备消息,怎么回滚?答案是A系统实现一个消息中间件的回调接口,消息中间件会去不断执行回调接口,检查A事务执行是否执行成功,如果失败则回滚预备消息
  4. 步骤四出错,这时候A的本地事务是成功的,那么消息中间件要回滚A吗?答案是不需要,其实通过回调接口,消息中间件能够检查到A执行成功了,这时候其实不需要A发提交消息了,消息中间件可以自己对消息进行提交,从而完成整个消息事务

基于消息中间件的两阶段提交往往用在高并发场景下,将一个分布式事务拆成一个消息事务(A系统的本地操作+发消息)+B系统的本地操作,其中B系统的操作由消息驱动,只要消息事务成功,那么A操作一定成功,消息也一定发出来了,这时候B会收到消息去执行本地操作,如果本地操作失败,消息会重投,直到B操作成功,这样就变相地实现了A与B的分布式事务。
在这里插入图片描述
虽然此方案能够完成A和B的操作,但是A和B并不是严格一致的,而是最终一致的,我们在这里牺牲了一致性,换来了性能的大幅度提升。当然,这种玩法也是有风险的,如果B一直执行不成功,那么一致性会被破坏,具体要不要玩,还是得看业务能够承担多少风险。


TCC编程模式

TCC即为Try Confirm Cancel,它属于补偿型分布式事务,也是两阶段提交的一个变种。

3个操作步骤

  • Try:尝试待执行的业务
    这个过程并未执行业务,只是完成所有业务的一致性检查,并预留好执行所需的全部资源
  • Confirm:执行业务
    这个过程真正开始执行业务,由于Try阶段已经完成了一致性检查,因此本过程直接执行,而不做任何检查。并且在执行的过程中,会使用到Try阶段预留的业务资源。操作满足幂等性。
  • Cancel:取消执行的业务
    若业务执行失败,则进入Cancel阶段,它会释放所有占用的业务资源,并回滚Confirm阶段执行的操作。操作满足幂等性

业务流程分为两个阶段:
在这里插入图片描述

  • 第一阶段:主业务服务分别调用所有从业务的try操作,并在活动管理器中登记所有从业务服务。当所有从业务服务的try操作都调用成功或者某个从业务服务的try操作失败,进入第二阶段。
  • 第二阶段:活动管理器根据第一阶段的执行结果来执行confirm或cancel操作。如果第一阶段所有try操作都成功,则活动管理器调用所有从业务活动的confirm操作。否则调用所有从业务服务的cancel操作。
  • P.S. 需要注意的是第二阶段confirm或cancel操作本身也是满足最终一致性的过程,在调用confirm或cancel的时候也可能因为某种原因(比如网络)导致调用失败,所以需要活动管理支持重试的能力,同时这也就要求confirm和cancel操作具有幂等性。

一个例子:
订单服务要调用库存服务扣减库存(假设数量为2),还要调用支付服务从用户余额扣钱(假设为100)

在这里插入图片描述

  • Try
    订单服务通知库存服务,冻结2个库存。库存服务尝试冻结2个库存。
    订单服务通知支付服务,冻结100块钱。支付服务尝试冻结100元。
    库存和支付系统尝试冻结成功,便进入Confirm阶段
    库存和支付系统尝试过程发生任何异常,均进入Cancel阶段
  • Confirm
    库存服务扣减2个库存,修改状态,记录流水。
    支付服务扣减100元,修改状态,记录流水。
    Confirm过程发生任何异常,均进入Cancel阶段
    Confirm过程执行成功,则该事务结束
  • Cancel
    库存服务解冻并恢复2个库存,修改状态,记录流水。
    支付服务解冻并恢复100元,修改状态,记录流水。

P.S. 异常处理机制:

  • Q:库存服务的Try操作完成, 支付服务的Try操作没有完成,怎么办?
  • A:订单服务可以尝试去调用库存的Cancel操作(这应该是个幂等操作,可以多次调用),把冻结的库存释放。

  • Q:如果出现网络问题,订单服务无法联系库存服务,怎么办?
  • A:库存服务的TCC组件能够发现冻结的时间已经超时,会自动把冻结的库存给释放

  • Q:两个微服务的Try操作都完成, 然后发生网络故障,导致两个Confirm都无法进行,怎么办?
  • A:TCC组件会发现超时,释放冻结的资源, 当然,冻结的这部分资源在释放前的一段时间内不可以被使用

  • Q:如果库存服务所在的机器已经挂掉,怎么计算超时?
  • A:TCC系统必须得记录日志,把那些没有完成的事情记录下来,持久化到硬盘上。这样下次重启就可以接着执行了。

  • Q:Try操作都已完成,资源已经冻结,在第三步中库存服务Confirm成功,库存做了扣减, 但是支付服务挂掉了,余额还处于冻结状态, 怎么办?
  • A:可以多尝试几次, 让支付服务做Confirm操作(很明显,这个Confirm操作必须得是一个幂等操作才行)。如果实在是无法成功,那就可以让库存服务做Cancel操作。 如果还是不行,只有让人工介入了。

参考资料

  1. 漫画:什么是分布式事务?
  2. 理性撕逼!分布式事务:不过是在一致性、吞吐量和复杂度之间,做一个选择
  3. 分布式的事务该怎么做?(文末问卷抽奖)
  4. 分布式事务入门指南 · 常用分布式事务解决方案
  5. 分布式事务?No, 最终一致性
  6. 深入理解分布式事务
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断路器保护灵敏度校验整改及剩余电流监测试点应用站用交流系统断

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值