长耗时任务的服务交互:从同步阻塞到异步解耦
假设有两个服务 A 和 B,B 负责执行一个耗时很长的任务(例如大文件转码、大批量报表生成),A 需要调用 B 并拿到最终结果。
如果 A 采用同步 RPC 一路等待,连接长期占用、超时重试等问题会接踵而至。
真正的考点是:如何在不拖垮调用方的前提下,让整个链路可追踪、可重试?
一、直觉方案:A 同步 RPC 等到 B 做完
传统写法:
- A 调用 B 的接口;
- B 内部执行一个长耗时任务;
- 任务结束后才给 A 返回结果。
问题:
-
调用链路被长时间占用
- 连接池、线程池资源被长时间拿着不放,高并发下会崩。
-
异常难以处理
- 一旦中途网络闪断/超时,调用方根本不知道任务是否已经执行成功。
-
没有任务状态的抽象
- 调用就是调用,任务只是一次 HTTP 请求,没有独立的生命周期和状态。
二、核心思路:任务抽象 + 异步交互
无论采用哪种技术手段,共同点都是:
- 把“任务的生命周期”从一次 HTTP 调用中抽离出来;
- A 不再同步等待任务执行完,而是:
- 下发任务
- 拿到任务 ID
- 之后再通过回调/轮询/消息等方式拿结果
三、方案一:异步回调(Callback)
1. 流程
- A 调用 B 的“创建任务”接口,并带上回调地址
callbackUrl。 - B 接到请求后,快速返回“任务已接收 + 任务 ID”。
- B 在后台异步执行任务。
- B 执行完成后,主动调用 A 提供的回调接口,将任务 ID 和结果推回给 A。
2. 优缺点
-
优点
- 实现简单,不一定需要 MQ。
- 延迟较低,适合实时性较高但并发量不极端的场景。
-
缺点
- A 需要暴露回调接口,存在安全要求。
- 回调失败需要额外的重试与补偿逻辑。
四、方案二:MQ 异步解耦(任务队列 + 结果队列)
1. 流程
- A 将任务消息发送到“任务队列”。
- B 订阅任务队列,异步消费并执行任务。
- B 执行完成后,将结果发送到“结果队列”。
- A 订阅结果队列,拿到对应任务 ID 的执行结果。
2. 优缺点
-
优点
- A/B 完全解耦,只通过消息交互。
- MQ 提供重试、堆积、顺序等能力,可靠性强。
-
缺点
- 系统架构复杂度更高。
- 需要处理消息幂等、重复消费、消息堆积等问题。
五、方案三:轮询(Polling)
1. 流程
- A 调 B 创建任务接口,B 返回任务 ID。
- A 按一定频率调用 B 的“查询任务状态”接口。
- 当任务状态变成“完成”,A 再去拉取详细结果。
2. 优缺点
-
优点
- A 不需要暴露回调接口,安全性较好。
- 实现逻辑简单,尤其适合已有 HTTP API 的场景。
-
缺点
- 存在天然延迟;
- 轮询频率太低:用户感知慢;
- 轮询频率太高:浪费资源。
六、选型:从场景出发做权衡
面对面试官的问题,思路可以这样展开:
-
先问清楚场景约束
- 并发量大不大?
- 实时性要求?
- 单次任务时长?
- 网络环境是否稳定?
-
再按场景选方案
- 并发量不大,实时性较高 → 异步回调。
- 并发量高、可靠性要求强 → MQ 异步解耦。
- 实时性要求一般、安全性优先 → 轮询方案。
-
最后点出共性
- 核心都在于:
- 把“任务生命周期”抽象为一个独立实体(任务 ID + 状态);
- 避免调用方线程长时间阻塞;
- 让结果“可追踪、可重试、可恢复”。
- 核心都在于:
七、面试思路
-
开篇表态:
- 同步阻塞等待是最直观但最不可取的,会拖垮 A 的资源池。
-
给出抽象:
- 把“长任务”抽成独立的任务实体,通过任务 ID 管理状态。
-
分三种方案展开:
- 异步回调、MQ 解耦、轮询,各自适用场景和优缺点。
-
最后强调:
- 面试官考的是你对“异步解耦 + 结果回传”的理解,而不是某个具体技术栈。

850

被折叠的 条评论
为什么被折叠?



