MQ 异步 + 回调:外部系统集成模式详解
MQ 异步 回调外部系统集成模式详解一、要解决的核心问题当你的系统需要和外部系统第三方服务、下游平台协作完成一件事时天然面临三个难题外部系统慢或不稳定如果同步等待它返回你的主流程会被拖慢甚至拖垮处理是长流程对方可能需要人工介入或长时间处理不可能在一次 HTTP 请求内返回结果耦合风险外部系统宕机不应该导致你的核心业务不可用MQ 异步 回调就是解决这类问题的经典组合拳用 MQ 异步解决发起请求这一侧的解耦与削峰用回调解决获取结果这一侧的长流程异步通知注博客https://blog.csdn.net/badao_liumang_qizhi二、两个独立的概念先讲清2.1 消息队列MQ / Message Queue生产者把消息投递到队列就立即返回消费者独立地从队列取消息处理。核心价值价值说明解耦生产者不直接依赖消费者双方只认识队列异步生产者不等消费者处理完投递即返回削峰突发流量堆在队列里消费者按自己节奏消费容错消费失败可重试消费者宕机消息不丢2.2 回调Callback / WebhookA 系统请求 B 系统做一件耗时的事B 不在当次请求里返回结果而是事后主动调用 A 预留的接口把结果推回来。同步调用A 问 B「做完了吗」→ B 当场答请求-响应一来一回回调A 告诉 B「做完了打我这个电话」→ B 稍后主动打回来事件通知两次独立调用回调也叫Webhook当通知方式是 HTTP 回调时。三、组合起来一次完整的集成链路以向外部系统提交一个待处理任务并接收处理结果为例整个链路分两个方向【发起方向 —— MQ 异步】 业务系统 --(1)写业务数据-- DB 业务系统 --(2)发MQ------- 队列 | 消费者 --(3)消费-------- 消费者 --(4)调用外部系统提交任务-- 外部系统 消费者 --(5)返回任务ID(ack)------ 外部系统 // 只确认收到了,不是最终结果 消费者 --(6)保存任务ID与业务的映射-- DB ...... 外部系统内部处理 / 人工操作耗时不定 ...... 【结果方向 —— 回调】 外部系统 --(7)处理完,回调-- 业务系统的回调接口 业务系统 --(8)用任务ID反查业务-- DB 业务系统 --(9)处理结果,更新业务状态 业务系统 --(10)返回ack----- 外部系统关键点第(2)~(5)步用MQ 异步业务主流程只管写数据发消息不等外部系统响应飞快第(6)步保存任务ID ↔ 业务ID 的映射关系这是连接发起和回调两个独立调用的桥梁第(7)步的回调解决外部处理是长流程、结果不确定何时产生的问题四、通用示例代码4.1 发起侧业务提交 发 MQServicepublicclassTaskSubmitService{ResourceprivateTaskRepositorytaskRepository;ResourceprivateSubmitTaskMqSendermqSender;/** * 提交任务:只写本地数据 发MQ,不同步等外部系统 */Transactional(rollbackForException.class)publicLongsubmit(TaskRequestrequest){// 1. 写本地业务数据,状态待提交TaskEntitytasknewTaskEntity();task.setBizData(request.getData());task.setStatus(TaskStatus.PENDING.getCode());taskRepository.save(task);// 2. 事务提交后再发MQ(关键:避免消息发出但本地事务回滚)TransactionSynchronizationManager.registerSynchronization(newTransactionSynchronization(){OverridepublicvoidafterCommit(){mqSender.send(task.getId());}});returntask.getId();}}为什么事务提交后再发 MQ如果在事务里直接发 MQ可能出现消息已发出去、本地事务却回滚了导致消费者处理一个数据库里不存在的任务。注册afterCommit回调能保证消息只在本地数据真正落库后才发出。4.2 消费侧调用外部系统 保存映射ComponentSlf4jpublicclassSubmitTaskMqConsumer{ResourceprivateTaskRepositorytaskRepository;ResourceprivateExternalSystemClientexternalClient;RabbitListener(queuestask.submit.queue)publicvoidconsume(LongtaskId){log.info(消费提交任务MQ,taskId{},taskId);try{TaskEntitytasktaskRepository.findById(taskId).orElse(null);if(tasknull){log.warn(任务不存在,taskId{},taskId);return;// 幂等:消息对应数据不存在,直接丢弃}// 调用外部系统提交,外部只返回受理ID,不是最终结果ExternalResultresultexternalClient.submitTask(task.getBizData());// 关键:保存 外部任务ID - 本地业务ID 的映射,供回调时反查task.setExternalTaskId(result.getExternalTaskId());task.setStatus(TaskStatus.SUBMITTED.getCode());taskRepository.save(task);log.info(提交外部系统成功,taskId{},externalTaskId{},taskId,result.getExternalTaskId());}catch(Exceptione){// 消费异常:记录日志,交由MQ重试机制(或进死信队列)log.warn(提交任务消费异常,taskId{},taskId,e);throwe;// 抛出以触发重试}}}4.3 回调侧接收外部结果 更新业务RestControllerRequestMapping(/api/callback)Slf4jpublicclassTaskCallbackController{ResourceprivateTaskRepositorytaskRepository;/** * 外部系统处理完后主动回调此接口 * 入参格式由外部系统约定(常见form或json) */PostMapping(/task-result)publicResponseEntityBooleanonTaskResult(CallbackParamparam){log.info(收到外部回调,externalTaskId{},result{},param.getExternalTaskId(),param.getResult());// 1. 校验:关键字段为空直接返回成功(幂等容错,避免对方重试风暴)if(param.getExternalTaskId()null){log.warn(回调externalTaskId为空);returnResponseEntity.ok(Boolean.TRUE);}// 2. 用外部任务ID反查本地业务(发起侧保存的映射在此发挥作用)TaskEntitytasktaskRepository.findByExternalTaskId(param.getExternalTaskId());if(tasknull){log.warn(回调未查到本地任务,externalTaskId{},param.getExternalTaskId());returnResponseEntity.ok(Boolean.TRUE);// 幂等:查不到也返回成功}// 3. 幂等:已处理过的回调不重复处理if(TaskStatus.isFinished(task.getStatus())){log.info(任务已是终态,忽略重复回调,taskId{},task.getId());returnResponseEntity.ok(Boolean.TRUE);}// 4. 根据回调结果更新业务状态try{task.setStatus(param.isSuccess()?TaskStatus.SUCCESS.getCode():TaskStatus.FAILED.getCode());task.setResultMsg(param.getMessage());taskRepository.save(task);log.info(回调处理完成,taskId{},status{},task.getId(),task.getStatus());}catch(Exceptione){log.warn(回调处理异常,externalTaskId{},param.getExternalTaskId(),e);// 返回失败,让外部系统按其重试策略重试returnResponseEntity.ok(Boolean.FALSE);}returnResponseEntity.ok(Boolean.TRUE);}}五、这个模式里的关键设计点5.1 任务ID映射串联两次独立调用的桥梁发起时拿到外部系统的externalTaskId必须和本地bizId建立持久化映射。回调时外部系统只会带externalTaskId靠它反查本地业务。没有这个映射回调回来你不知道它对应哪笔业务。5.2 幂等性最重要回调可能被重复调用网络重试、对方故障恢复后补发所以回调接口必须幂等查不到记录 → 返回成功不报错已是终态 → 直接返回成功不重复处理用唯一键/状态机防止重复更新MQ 消费也可能重复消费侧同样要幂等。5.3 事务与消息的一致性事务提交后再发 MQ如 4.1避免消息发了、数据回滚了进阶方案本地消息表 / 事务消息如 RocketMQ 事务消息/ Outbox 模式保证写业务和发消息的最终一致5.4 回调接口的安全与返回约定鉴权回调接口要校验来源签名、token、IP 白名单防止被伪造调用返回值约定明确告诉对方成功/失败对方据此决定是否重试如返回 200true 表示已接收Content-Type按外部系统约定常见application/x-www-form-urlencoded或application/json5.5 可观测性全链路用关键 IDbizId、externalTaskId贯穿日志这样线上能把发起 → 提交 → 回调三段独立调用串成一条链路排查。5.6 兜底与补偿回调不是 100% 可靠可能丢失。生产级方案通常补充定时对账任务扫描长时间停在已提交未回调的记录主动查询外部系统状态超时处理超过约定时长未回调的任务标记异常/告警六、与其他方案的对比方案适用场景优点缺点同步调用外部处理快、结果立即可得简单直观外部慢会拖垮主流程强耦合轮询Polling外部无回调能力实现简单实时性差、空轮询浪费资源MQ 异步 回调外部处理慢/长流程、要求解耦解耦、削峰、不阻塞主流程复杂度高、需处理幂等/对账长连接推送(SSE/WebSocket)需要实时推送实时性强连接管理复杂不适合跨系统七、常见误区在事务内直接发 MQ可能消息发出但事务回滚 → 数据不一致。应事务后发或用事务消息。回调接口不做幂等对方重试导致重复处理如重复扣款、重复更新。忘记保存任务ID映射回调回来无法关联本地业务。回调完全信任、不鉴权回调接口被伪造请求攻击。只依赖回调、无兜底对账回调一旦丢失业务永远停在中间态。MQ 消费吞异常不重试catch后不抛出也不记录消息假装消费成功数据丢失。八、一句话总结MQ 异步让你发起请求时不被外部系统拖累解耦、削峰、不阻塞回调让你获取结果时不必苦等长流程事件驱动、异步通知。两者之间靠任务ID映射串联靠幂等 事务一致性 对账兜底保证可靠。它是对接慢、长、不稳定外部系统的标准范式。

相关新闻

烟台智能建造技术导则解析与BIM应用实践

烟台智能建造技术导则解析与BIM应用实践

1. 政策背景与核心目标烟台作为山东省重要的沿海城市,近年来在建筑产业转型升级方面持续发力。这份《建设工程智能建造技术导则(试行)》的出台,标志着烟台正式将智能建造纳入城市建设标准体系。导则最核心的价值在于为当地建筑企业…

2026/10/14 8:59:39 阅读更多 →
Windows Server 2019 双机热备与 SQL Server 2019 群集部署实战

Windows Server 2019 双机热备与 SQL Server 2019 群集部署实战

简介:这份图文教程面向需要在 Windows Server 2019 环境下搭建高可用数据库的运维工程师与系统管理员,围绕 MSCS 故障转移群集与 SQL Server 2019 群集部署展开,解决单节点故障导致业务中断的问题。资源共 1 个 PDF 文件,压缩包约…

2026/10/14 8:59:39 阅读更多 →
Kotlin协程原理与实践:从线程控制到结构化并发

Kotlin协程原理与实践:从线程控制到结构化并发

1. Kotlin协程的本质与设计哲学 第一次接触Kotlin协程时,很多人会被它的轻量级线程宣传所迷惑。但经过三年实际项目锤炼,我发现协程本质上是一个更高级的线程控制框架。它的核心价值不在于创建线程,而在于用同步代码写出异步逻辑,…

2026/10/14 8:59:39 阅读更多 →

最新新闻

shadcn-dashboard-landing-template部署指南:Vercel、Netlify与静态托管方案对比

shadcn-dashboard-landing-template部署指南:Vercel、Netlify与静态托管方案对比

【免费下载链接】shadcn-dashboard-landing-template Accelerate your development with a sleek, open-source admin dashboard and landing page built on Vite-React, Next.js, Tailwind CSS, and Shadcn/UI which is fully customizable and production-ready. 项目地址&am…

2026/10/14 16:06:12 阅读更多 →
推箱子自动寻路实战:IDA*搜索与死锁检测优化

推箱子自动寻路实战:IDA*搜索与死锁检测优化

推箱子自动寻路,这东西看起来是个小游戏,真做起来才知道水有多深。网上讲这个主题的文章不少,但大多是拿BFS跑个小地图就收工,真正能处理中等规模关卡、能跑完不爆内存、不卡死的完整实现其实不多。我这边之前写过一个版本&#x…

2026/10/14 16:06:12 阅读更多 →
Godot TorusMesh 环面网格完整指南:参数详解、代码创建与实战应用

Godot TorusMesh 环面网格完整指南:参数详解、代码创建与实战应用

文档教程游戏开发 【免费下载链接】godot-docs Godot Engine official documentation 项目地址: https://gitcode.com/GitHub_Trending/go/godot-docs 点击查看 免费下载 引言:为什么需要 TorusMesh TorusMesh(环面网格,即甜甜圈…

2026/10/14 16:06:12 阅读更多 →
Kong网关7.5分SQL注入:混合模式的信任假设错了

Kong网关7.5分SQL注入:混合模式的信任假设错了

2026 年 10 月 12 日,Kong Gateway 的一个 SQL 注入漏洞公开:CVE-2026-1907,CVSS v4.0 评分 7.5(High),影响 Kong Enterprise Gateway 与开源版 Kong Gateway。漏洞的位置很具体:混合模式&#…

2026/10/14 16:06:12 阅读更多 →
腾讯云Python服务生产级部署:systemd+nginx+venv实战

腾讯云Python服务生产级部署:systemd+nginx+venv实战

简介:本资源是一份面向高校计算机专业学生与Python初学者的实战部署指南,聚焦疫情期间无法返校使用本地服务器时,如何借助腾讯云短期免费资源快速运行Python3.7PyTorch CPU版代码。文档详细覆盖从新用户微信认证领15天云服务器、CentOS/Ubunt…

2026/10/14 16:06:12 阅读更多 →
社交类iOS开发:即时通讯链路与性能优化实战地图

社交类iOS开发:即时通讯链路与性能优化实战地图

提到iOS开发,很多人第一反应是"不就是用Swift画页面嘛"。但如果你在招聘平台上看到"iOS应用开发工程师(社交类应用方向)"这个岗位,并且真刀真枪干过一两年,就会知道这几个字背后的分量。我从一线i…

2026/10/14 16:05:10 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →