Agent 系统为什么使用消息队列,而不直接用数据库传递任务状态?

考查重点:权威状态与任务唤醒分工。数据库保存权威任务状态,消息队列负责唤醒、削峰和分发;比较数据库轮询的负载,再说明 Outbox 如何协调状态提交与消息投递

本题目录

参考答案

核心回答

数据库和消息队列解决的重点不同:数据库保存任务的权威状态,消息队列负责把“有任务可处理”可靠地传给执行者。Agent 的研搜、文档解析等长任务通常不适合一直占着用户请求;队列可以支持异步执行、削峰、多个 Worker 分摊任务,以及处理失败后的重新投递。

直接用数据库轮询也可以,尤其是任务少、流程简单时。引入消息队列不是必然更快或更可靠,还会增加投递一致性、重复消费和运维成本。选择依据是吞吐、唤醒时延、消费者数量和恢复要求,而不是“Agent 必须用 MQ”。

展开解析

1. 一条长任务怎样流转

以“收集资料并生成报告”为例:入口创建任务,数据库保存用户、输入、状态与版本,返回 task_id;提交之后通过队列通知 Worker。Worker 获得执行权,读取数据库中的当前状态,完成检索、生成与校验,把进度和产物写回。前端查任务状态或订阅进度,不必等待同一条请求一直存活。

队列消息通常携带任务 ID、事件 ID、租户路由信息等必要字段。大文件和完整上下文放对象存储或数据库,消息里保留可校验的引用,避免体积与敏感数据扩散。

2. 为什么不只靠不断查任务表

关注点 数据库任务表轮询 消息队列
获取新任务 周期查询或数据库通知,需自己设计唤醒 消费或推送机制,便于持续取任务
并发执行 需条件更新、行锁或租约抢占 便于分发给多个消费者,但仍需业务执行权控制
流量突增 数据库同时承担查询、状态更新和调度压力 可缓冲突发,但积压与存储仍有上限
失败恢复 自己设计扫描、超时接管和重试调度 可利用确认和重投机制,仍需幂等与补偿
状态查询与历史 适合按业务条件查询 通常不适合作为唯一业务状态查询接口

低频任务完全可以用数据库任务表,加索引、批量拉取和原子抢占。数据库不是不能做队列;只是规模和需求增加以后,要自己补的队列能力也会增加。

3. 数据库和队列双写要处理一致性

如果“数据库已提交、消息没发出”,任务会一直无人执行;如果先发消息、数据库回滚,消费者会读到不存在或未成立的任务。

一种方案是 Transactional Outbox:在同一个本地事务中写任务和待发送事件;投递器把已提交事件发到队列,收到确认后记录投递结果。投递器在确认后崩溃仍可能再次发送,因此消费者必须去重或幂等。也可以按数据库能力采用 CDC,并结合漏单扫描补偿。

生产者确认只说明 Broker 在其确认语义下接收了消息,不等于业务任务完成;消费者 ACK 也应与执行状态的持久化顺序协调,不能一取到消息就确认成功。

4. 防止重复投递变成重复执行

用稳定事件 ID 和逻辑任务 ID,结合数据库唯一约束、条件状态转换或带版本的租约抢占执行权。处理成功并可靠保存结果后再确认;若保存后、确认前进程退出,重投时识别已有结果,而不是重做外部动作。

长任务可把“接收并可靠登记调度”与“完整工作执行”分开,避免长期占用未确认消息;但登记后的调度器需要有租约续期和失败接管。毒消息进入受控失败或死信路径,不能立即无限重投。扩容消费者时还要尊重模型和工具配额,队列不会凭空增加下游处理能力。

追问

消息队列能保证业务 exactly-once 吗?

不能仅凭产品宣传推导任意外部副作用恰好一次。消息投递、消费提交和业务写入是不同边界;退款等操作仍要由业务服务用幂等键、事务和结果查询保证效果。

一个队列中的多个消费者都会收到同一任务吗?

要看队列与订阅模型。在 RabbitMQ 的同一队列里通常是竞争消费;需要多个模块各处理一份事件时,通常为各模块建立独立队列或订阅,不能把广播与增加 Worker 混为一谈。

参考资料

相关面经

5 篇

腾讯

后端开发工程师
实习一面
查看原面经 →
消息队列除了你刚才说的用途,还有什么作用?

老虎国际

后端开发
实习一面、二面
查看原面经 →
你使用过队列吗?使用过 MQ 吗?
在什么场景下使用 RabbitMQ?

美团

后端开发
一面
查看原面经 →
为什么用 MQ?

虾皮

Agent 开发工程师
秋招一面
查看原面经 →
消息队列在Agent系统中主要解决什么问题?为什么不直接使用数据库存储和传递任务状态?

字节跳动

AI Agent
秋招一面
查看原面经 →
消息队列在 AI Agent 系统中的作用是什么?为什么不直接通过数据库通信?

相关题目

4 题