浅析分布式业务一致性方案

1 场景分析

现在有一种业务场景:A作为消息发送方,处理业务成功后,投递消息。B作为消息接收方,接收消息,处理业务。在这种业务场景中,我们希望A业务处理成功后,B业务也处理成功。这种抽象场景可以体现在具体业务场景:

  • 下单:用户支付成功,订单中心投递消息,物流中心发货
  • 报名:用户报名成功,活动中心投递消息,下游准备物料
  • 买券:用户支付成功,订单中心投递消息,营销中心发券

A和B作为各自独立系统,如果想要保证业务一致性,并不像单体应用那么简单。我认为从三个维度考虑,而不是单点思考这个问题:

  • 业务发送方
  • 业务消费方
  • 监控

2 业务发送方

业务发送方需要保证一件事情:当本业务处理成功后,一定可以投递成功业务消息。通常有两个方案:本地消息表、事务消息。

2.1 本地消息表

本地消息表思想是在业务表的同一个库中,引入消息表,通过数据库本地事务保证业务表和消息表操作强一致性。使用步骤:

  • 第一步:业务表和消息表强一致更新,消息状态为【待发送】
  • 第二步:发送消息至消息队列,修改为消息状态为【已发送】或【发送失败】
  • 第三步:定时任务查询X时间前【待发送】和【发送失败】消息,重新推送至消息队列

2.2 事务消息

RocketMQ事务消息特性可以满足本文业务场景,事务消息原理如下图:

事务消息使用步骤:

  • 第一步:业务方发送半消息至RocketMQ
  • 第二步:RocketMQ返回半消息发送成功结果
  • 第三步:执行业务代码
  • 第四步:业务执行成功,则提交半消息至RocketMQ,此时消息才算真正提交成功。业务执行失败,回滚半消息
  • 第五步:如果业务执行完成后,由于各种原因(例如网络原因)未返回结果,导致半消息无法确定提交还是回滚
  • 第六步:业务需要提供查询接口,RocketMQ回调这个接口,根据结果决定提交还是回滚半消息

3 业务接收方

业务接收方需要注意以下几个维度:

  • 维度一:接收到消息,如果处理成功需要告知RocketMQ消息处理成功,避免重复消息
  • 维度二:接收到消息,如果处理失败需要告知RocketMQ消息处理失败,等待重试消费
  • 维度三:因为RocketMQ重试机制存在,消息可能会被重复消费,所以必须做业务幂等

3.1 维度一

接收到消息后进行业务处理,如果处理成功则告知RocketMQ成功:

text 复制代码
public class MessageListenerImpl implements MessageListener {

    @Override
    public Action consume(Message message, ConsumeContext context) {
        boolean result = doBusiness(message);
        if(result) {
            // 业务处理成功
            return Action.CommitMessage;
        } else {
            return Action.ReconsumeLater;
        }
    }
}

3.2 维度二

3.2.1 代码编写

接收到消息后进行业务处理,如果处理则告诉RocketMQ当前消息消费失败,并希望在稍后重新尝试消费:

text 复制代码
public class MessageListenerImpl implements MessageListener {

    @Override
    public Action consume(Message message, ConsumeContext context) {
        boolean result = doBusiness(message);
        if(result) {
            return Action.CommitMessage;
        } else {
            // 业务处理失败
            return Action.ReconsumeLater;
        }
    }
}

消费者有三种方式告知RocketMQ消费失败,均会触发重试机制:

text 复制代码
public class MessageListenerImpl implements MessageListener {

    @Override
    public Action consume(Message message, ConsumeContext context) {
        boolean result = doBusiness(message);
        if(result) {
            return Action.CommitMessage;
        } else {
            // 方式一
            return Action.ReconsumeLater;
            // 方式二
            throw new RuntimeException("doBusiness fail");
            // 方式三
            return null;
        }
    }
}

3.2.2 重试机制

(1) 顺序消息

如果消费者在处理过程中失败,RocketMQ 消息队列会自动触发重试机制,每隔1秒尝试重新投递该消息。在此期间由于重试机制的运行,应用可能会遇到消息消费被暂时阻塞的现象。

(2) 无序消息

  • 无序消息类型
    • 普通消息
    • 定时消息
    • 延时消息
    • 事务消息
  • 重试次数
    • 默认16次
    • 自定义重试次数,超过16次后重试间隔均为2小时
  • 重试次数:与上一次时间间隔
    • 第1次:10秒
    • 第2次:30秒
    • 第3次:1分钟
    • 第4次:2分钟
    • 第5次:3分钟
    • 第6次:4分钟
    • 第7次:5分钟
    • 第8次:6分钟
    • 第9次:7分钟
    • 第10次:8分钟
    • 第11次:9分钟
    • 第12次:10分钟
    • 第13次:20分钟
    • 第14次:30分钟
    • 第15次:1小时
    • 第16次:2小时
  • 无序消息重试功能只有在集群消费模式下有效。广播模式下消费失败,失败消息将不会被重试,系统会继续消费下一条新消息

3.3 维度三

在计算机科学和数学中幂等(Idempotent)描述一个操作,无论执行多少次结果均相同。幂等性在分布式系统特别重要,因为这些环境中的操作可能会由于网络延迟、重试逻辑而被多次执行。如果一个操作不幂等,重复执行可能会导致错误结果。常见幂等方案:

  • 幂等表
  • 分布式锁
  • 版本控制
  • 状态机

4 监控

4.1 一个悖论

怎样保证一个工程系统的稳定性?有以下两种做法:

  • 思路1:考虑到所有意外情况,针对每一个意外的异常情况分别处理
  • 思路2:接受无法预料到所有意外情况的现实,把兜底方案做好,保证即使出现极端情况,系统也不会崩溃

我们仔细分析思路1会发现这其实是一个悖论。意外情况就是意料之外的情况,无法预料的情况。如果被考虑到了,那么也就不能称之为意外情况了。

塔勒布在经典著作《反脆弱》一直想告诉我们:黑天鹅事件是无法预测的,极端意外情况是无法预测的,尾部风险虽然概率小但破坏力却极大。所以我们要保护好系统。

4.2 事前、事中、事后

如何思考保护系统这个问题?我们可以从三个维度思考:

  • 事前:监控异常,及时响应
  • 事中:快速止血,迅速恢复
  • 事后:数据恢复,定损复盘

4.3 事前监控

异常监控存在三个维度:

  • 系统异常:出现一次就需要感知
  • 业务监控:X分钟出现Y次需要感知
  • 数据监控:数据量不匹配,状态X时间内未流转

5 延伸阅读

反脆弱与技术系统高可用性

分布式事务理论与实例分析

相关推荐
卷毛的技术笔记1 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
_codemonster1 小时前
30分钟快速搭建 Spring Cloud Alibaba 微服务实战(一)
微服务·架构·毕业设计·课程设计
会编程的土豆1 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
Cosolar1 小时前
从零写一个 Attention Is All You Need
人工智能·面试·架构
喵个咪2 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6162 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364572 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao3 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
qcx233 小时前
【系统学AI】09 Multi-Agent架构(2026版):从学术理论到工业级实践
java·人工智能·架构·multi-agent·claude agent
IT_陈寒4 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端