Go项目(分布式事务)
创始人
2025-05-29 20:18:06

文章目录

  • 简介
  • 分布式事务
  • CAP
  • BASE
  • 常见方案

简介

  • 目前,项目的主要代码已经开发完毕,其中最关键的是订单系统(钱),不能出错;但这里还有一些问题需要解决
  • 整个下单支付的流程没有问题,但是如果出现网络抖动/断网(远程调用其他服务会有网络问题,导致库存扣减不成功回滚)或者业务上没有超时机制,会出现问题或者给用户带来不好的体验
  • 这些问题需要用分布式事务解决

分布式事务

  • 回顾一下事务的特性
    • 原子性
    • 一致性
    • 隔离性
    • 持久性
  • 分布式事务满足不了 ACID,其实单机应用也并不能完全保证,不然怎么有四个隔离级别呢
    • 分布式事务的核心是保证数据一致性
  • 分布式事务面临最主要的问题就是网络问题,因为涉及到和其他机器的网络连接
    • 网络问题:请求没有发送出去,发出去了没有收到返回
    • 造成网络问题的常见原因:硬件故障、网络抖动、网络拥塞
      1
  • 网络调用和单机不一样,并不能因为出现网络问题就直接像 MySQL 那样触发回滚

CAP

  • 简单介绍一下 CAP 理论,分布式系统的理论基石
  • Consistency (一致性)
    • “all nodes see the same data at the same time
    • 即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致,这就是分布式的一致性,可以理解为同步操作
    • 一致性的问题在并发系统中不可避免,对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题
    • 从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致
  • Availability (可用性)
    • “Reads and writes always succeed”
    • 即服务一直可用,而且是正常响应时间
    • 好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况
  • Partition Tolerance (分区容错性)
    • 即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务
    • 分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体
    • 比如现的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,对于用户而言并没有什么体验上的影响
    • 分区容错性必须满足,否则不能称之为分布式系统
  • 但 CAP 三个特性一般只能满足其中两个,所以我们只有两种策略可用
    • CP
    • AP
      2

BASE

  • 在分布式系统中,我们希望满足 CAP,只能各方面做出一些让步
  • 或者说权衡一致性(C)和可用性(A)
    • 满足一致性时,只满足弱一致性,允许有时差,举个例子:转账操作后会提示两小时内到账
      1
    • 满足可用性时,只是基本满足,当系统在出现不可预知的故障时,允许损失部分可用性,举个例子:流量激增时,会将一部分用户引导到降级页面,提示当前系统繁忙,10s后刷新重试
  • BASE 是 Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)的缩写,BASE 理论就是对 CAP 中一致性和可用性权衡的结果
    • 基本可用上面已经解释了
    • 软状态:允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时
    • 最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态
  • 总的来说,BASE 理论面向的是大型高可用可扩展的分布式系统,和传统的事务 ACID 特性是相反的
    • 注:在实际的分布式场景中,不同业务单元和组件对数据一致性的要求是不同的
    • 因此在具体的分布式系统架构设计过程中,ACID 特性和 BASE 理论往往又会结合在一起
  • 理论知识需要逐步深化理解,不可能一蹴而就

常见方案

  • 先来看一致性(C)的解决方案,也就是常见的分布式事务实现方案
    • 基本可用(A)和分区容错(P)会在后续的熔断降级和项目部署时涉及到
  • 一、两阶段提交
    • Two-phase Commit,也叫 2PC
    • 这张图说明了流程
      1
    • 调用其他微服务但并不 commit,如果后续的逻辑执行成功,通知其他服务 commit,如果失败,则通知其他微服务 callback
    • 这是分布式事务最简单的实现方式,但性能问题严重,因为很多环节都可能出错或成为瓶颈;比如这里依赖的是其他服务中 MySQL 的事务机制,还是要加数据库锁
    • 一般不使用
  • 二、TCC 补偿模式
    • TCC 即包括了 try confirm cancel 三个函数,看一张网图理解一下
      2
    • 我们需要在数据库中加入 freeze 或 status 字段,并准备 trysell、cancel、confirm 接口,能够从业务层面实现服务的状态更新
    • 比如仓库服务,增加 trysell 接口,先更新 freeze 字段,等到所有的逻辑执行成功再统一 confirm,真正修改数据库;如果任何一处失败,直接 cancel,也就是回滚业务逻辑,并未影响数据库
    • TCC 的核心是它的事务管理器,它必须知道各个服务调用执行到哪一步,以便继续推进或回滚
    • 如果代码没有什么 bug,有充足的测试,而且 Try 阶段都基本尝试了,那么一般Confirm、Cancel 都是可以成功的,或者说出现问题的概率很小;如果实在解决不了,可以考虑发邮件通知人工处理(不要觉得人工就是成本高易出错的坏事,有些情况下还是可以介入的)
    • 实现起来比较复杂,不推荐个人开发,有专门的工具但 python/go 生态中还不是很完善,Java 里有阿里开源的 seata 可以了解一下
    • 优点
      • 解决了跨服务的业务操作原子性问题(一致性),例如组合支付,订单减库存等场景非常实用
      • 异步高性能,它采用了 try 先检查,然后异步实现 confirm
    • 缺点
      • 对微服务的侵入性强,微服务的每个事务都必须实现try,confirm,cancel 等3个方法,维护改造的成本也高
      • try,confirm、cancel 接口必须实现等幂性操作
      • 由于要记录事务日志,必定会损耗一定的性能
      • 还是需要通过锁(不是数据库的锁而已)来确保数据的一致性,加锁会导致性能不高
  • 三、基于本地消息表的最终一致性
    • 创建本地消息表,将调用其他微服务的消息请求)放在消息表中(未确认态)
    • 本地微服务操作和本地消息表写入放在数据库事务中,保证原子性,出错即回滚,不给把消息送到 MQ 的机会
    • 启动独立的线程,定时对消息表中的消息进行扫描并将未确认状态的消息发送至消息中间件(MQ),让其他微服务消费
    • 使用 MQ 的 ack(消息确认)机制,消费者接收到消息并且业务处理完成后向 MQ 发送 ack
    • 独立线程消费 ack 消息,更新消息表中消息的状态,从未确认改为已确认
    • 如何保证更新消息状态成功了呢?这个线程还需要查询操作流水表,每次在推未确认状态的消息前,先查询流水表,如果没查到,可能是库存操作失败,重新推消息进 MQ(库存服务需要将流水表的读写放进事务);如果查到了,那就更新状态为已确认
    • 但也可能是库存服务还未消费,会发送重复消息到 MQ,这就需要消费消息的微服务实现幂等性校验(后续会详细解释),丢掉重复的消息
      3
    • 这是一种非常经典的策略,实现了“最终一致性”;但是,关系型数据库的吞吐量和性能方面存在瓶颈,频繁的读写消息(消息表)会给数据库造成压力,所以,在真正的高并发场景下,该方案也会有瓶颈
    • 另一方面,将未确认消息发送到 MQ 可能会遇到网络问题(mq用的是amqp协议)或者宕机等故障,也就是不能保证表事务操作完到 MQ 之间的原子性,虽然我们有 task 线程轮询机制,但不免实现复杂,性能降低
    • 注:这里还有一个容易忽略的点,就是 MQ 不能出问题,一般我们用 rabbitmq 或 racketmq,是比较稳定的,后续也会搭建分布式消息队列
  • 四、基于可靠消息的最终一致性
    • 和本地消息表策略类似,但将本地消息表移动到了 MQ 内部,解决发送消息到 MQ 与本地事务执行之间的原子性问题,如图
      4
    • 先发送 half message(不能消费),得到 ack 后再执行本地事务,本地事务提交完毕再发送 commit 信息给 MQ,让它发布可以消费的消息
    • 但是这里也有网络问题呀,第 4 步如果出现问题,还是不能实现原子操作;这里 rocketmq 提供了回查机制(rabbitmq没有),一定时间后会询问生产者咋回事,生产者检查事务状态,如果成功会重发 commit,失败会发 rollback,相当于上一种策略的 Task 系统
    • 看起来和上一种策略没啥区别,其实不然
      • 简化业务逻辑,相当于把本地消息表和独立线程的工作交给 MQ,降低开发难度
      • 高并发性能好,因为没有把本地查询表锁在事务里,而是通过业务逻辑实现了原子性(三个东西变成了两个)
      • 但缺点就是依赖了第三方组件(高效回查),增加维护成本
    • 本地消息表策略虽然有高并发瓶颈,但是可以改善(数据库优化),而且不依赖第三方组件,所以用的也比较多
  • 五、最大努力通知
    • 比如调用支付宝服务,用户支付后会通知商家已支付,这里用到了最大努力通知策略
    • 这里需要确保消息已通知到商家
      6
    • 但是支付宝要抗住并发也需要引入 MQ,然后用通知系统发送消息
    • 因为这个 MQ 是内部的,我们的系统只能被动接收消息
    • 最大努力通知,也就是说会有次数上限,一般为 20 次(时间间隔上有策略),但是要确保消息送到,还需要提供查询接口,商家没有收到通知时,能主动查看支付状态,保证不与用户发生冲突
  • 以上就是分布式事务的理论部分,还是要结合具体操作才能深化理解,不必站在空中楼阁细究底层原理
    • 接下来学习一下 MQ 相关的知识,为完善订单服务做准备

相关内容

热门资讯

美的瞬间初一作文(实用4篇) 美的瞬间初一作文 篇一美的瞬间初一作文初一,一个新的起点,一个新的开始。在这个充满希望和憧憬的时刻,...
初中英语作文常用短语【最新6... 初中英语作文常用短语 篇一In My HometownMy hometown is a small ...
初中的作文(优秀6篇) 初中的作文 篇一:我的梦想作文一:我的梦想初中的作文 篇二:如何养成良好的阅读习惯作文二:如何养成良...
初中优秀作文600字优秀(精... 初中优秀作文600字优秀 篇一:友谊的力量友谊是一种宝贵的情感,它能给我们带来无尽的欢乐和支持。我曾...
面对作文800字【优秀6篇】 面对作文800字 篇一如何应对面对作文的困扰作为学生,面对作文是我们每天都要面临的任务。然而,作文对...