前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于DB的分布式事务实现

基于DB的分布式事务实现

原创
作者头像
benjamimli
发布2024-03-07 17:40:21
980
发布2024-03-07 17:40:21

引言

分布式事务是计算机处理中比较复杂的场景,经常让人很头疼。

首先我们考虑单机情况下的事务实现,例如mysql,这类事务实现在本地可以通过redolog,undolog来保证同时成功,同时失败(虽然这里的实现也挺复杂的),因为他只涉及到了内部的业务逻辑,或者说在单机情况下,成功和失败都是属于可控范围,因为整个事件的完成提交只涉及到本地事务的成功与否。

为什么分布式事务就复杂了呢?因为他不可控。主要体现在以下几点:

  • 业务逻辑不可控,分布式事务下往往涉及到多接口的调用,每增加一个系统复杂性就会上升一个量级
  • 网络不可靠,可能出现超时,所以这时候你并不知道接口到底请求成功了没
  • 事务成本增加,为了满足事务的ACID特性,需要增加很多额外的逻辑

背景

下面,我们讨论下基于db的分布式事务实现。首先先介绍下场景,业务场景抽象出来比较简单,也就是当收到请求之后我们需要按顺序调用A->B->C三个系统(注意,这里三个系统全部都是写操作,并且写入的值都依赖于前一个系统的处理结果)返回给上游处理结果。乍一看是不是感觉很简单?三次RPC调用搞定。事情肯定没有这么简单,下面我列举几种异常情况来看看:

  • 写A成功,写B明确失败了
  • 写A成功,写B超时了,不知道成功还是失败
  • 写ABC都成功了,但是返回给上游请求失败了
  • 并发请求同时到来,都要写A

看到这里是不是感觉自己还是大意了,没想到有这么多问题要解决,下面给出一种基于db解决分布式事务的思路

设计方案

事务管理器

首先我们需要有一个分布式事务管理器来分派事件ID,标识整个事务的进展状态,表结构大致设计如下:

并且我们通过一个状态机来管理事务状态

该状态机可以标识当前事务的进展状态,并且可以为事务幂等提供状态标识,也就是查询到成功之后就可以组装结果直接返回了

事件任务表

事件任务表关联了这个事务id下即将要执行的任务,注意这里是"即将",这意味着我们是先写入任务再执行操作的,这么做是为了防止接口调用成功再写表时如果失败了,操作记录就丢失了,这对于事务调用是不可以容忍的。

同样的,我们也需要用一个状态机来管理任务的状态

该状态机可以表示当前任务的进展状态,在幂等/回滚控制,以及事务进展标识中起到作用。

调用过程

如图所示

注意这里其实是先在任务管理器注册为ready,然后调用完成之后再去更新为success的

回滚过程

在调用过程中的任何一个步骤都有可能出现失败,这个失败可能是接口调用失败,也有可能系统宕机直接终止了,这些类型的失败都是我们回滚中需要解决。

当调用接口出现失败或者超时就会触发系统的回滚逻辑,如下图所示可以看到此时A任务执行成功,B任务执行成功,C任务执行超时,此时触发了回滚。因为我们并不知道C任务是否执行成功了,所以A,B,C任务都需要执行回滚,执行完成后更新事务任务管理器为回滚成功状态。

但是这里问题又来了,如果我回滚到一半失败了呢?我们需要依赖上游的重试来继续完成回滚的流程,那么此时又有一个新的问题来了,如何确认回滚点?

回滚点的确认

回滚点是一个需要分类讨论然后再总结归纳的过程:

1、 A,B执行成功,C执行明确失败更新状态为失败状态

2、 A,B执行成功,C执行明确失败但更新状态失败,还是初始状态

3、 A,B执行成功,C执行失败;接口A,B回滚成功,C回滚失败

4、 A,B执行成功,C执行失败;接口A,B,C回滚成功,但更新事务管理器记录失败

也就是说当我们开始做回滚的时候,遇到这些情况都需要能够处理到。把上述四种情况统一一下得出结论,只要任务状态为初始化的都需要重新触发回滚。

细心的小伙伴应该发现这么处理会导致已经回滚了的单又继续回滚,因此下游系统需要能够支持回滚的幂等,上游可以通过加订单号来确保回滚的幂等性。

但是这里有一种状态是有歧义的,也就是回滚的时候发现任务状态记录为初始化的时候,因为此时任务的状态可能是任意的,既可以调用成功/失败,也可以是调用成功/失败,还可以是回滚成功/失败,我们为了简单处理,统一让这个记录回滚,或者业务可以根据需求做特定的处理。

防悬挂

不知道有小伙伴注意到没有,当我们在遇到调用接口超时的时候触发了回滚,可能由于网络波动的原因造成回滚请求先于调用请求的情况,也就是悬挂,此时可能会导致先收到取消接口之后又收到正常的系统调用,这就会导致出现了回滚失效的情况,所以这个时候就需要有对账机制能够及时发现相关问题;或者这里可以约束下游的业务接口需要能够拒绝已经调用回滚的请求。

空回滚

同时这里也可能因为网络的原因导致出现没有接口在还未收到调用请求后直接收到了回滚请求,所以业务接口需要能够兼容空回滚的情况

缺陷

分布式事务没有完美解法,只能在现有的业务场景下做出局部最优解,因此要意识到该方案也是有缺陷或者待优化的点:

  • 并不能保证强一致,而是最终一致。也就是在处理过程中会存在短暂的局部不一致的情况
  • 悬挂以及空回滚问题会导致业务长期处于不一致的状态,也就是极端情况下系统并不能实现闭环,会需要引入额外的人工消耗
  • 业务系统需要提供回滚接口,需要对事务架构做一定的适配
  • 临界资源的并发不能过高,否则很容易出现数据处理不一致的场景

总结

  1. 通过事务管理器对当前任务进行拆单,通过事件任务表维护任务的执行状态,并能够及时对处理异常的数据进行回滚
  2. 防悬挂以及空回滚是常见的分布式任务面临的问题,如果我们不能够通过逻辑完全解决,至少应该有相应的发现机制来做check
  3. 每个解决方案都有自己的局限性,我们应当根据业务场景选择合适的解决方案

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 背景
  • 设计方案
    • 事务管理器
      • 事件任务表
        • 调用过程
          • 回滚过程
            • 回滚点的确认
              • 防悬挂
                • 空回滚
                  • 缺陷
                  • 总结
                  相关产品与服务
                  数据库
                  云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档