客户的问题并不是“我们需要队列”,而是操作很慢,用户等待时得不到明确答复。
队列只是实现细节,产品问题是无法确定发生了什么。用户点下按钮,应用长时间没有回应,没人知道工作是否启动、失败,还是稍后会完成。
对于报表导出、CSV 导入等流程,我倾向将慢操作移到持久后台任务中。网页请求做基础校验、创建任务,再带用户进入状态页,由 worker 处理耗时部分。
await boss.send("report.export", {
reportId,
requestedBy: userId,
idempotencyKey,
});
任务名与载荷是示意,但结构贴近实际:请求留下足够的信息供稍后执行,用户得到一个可追踪的任务。
我喜欢在这个阶段使用 pg-boss,因为它基于 PostgreSQL。应用已经用 Postgres 保存持久状态时,数据库队列能避免过早增加单独的队列服务。
状态页是关键界面。用户需要理解排队、运行、完成、失败等状态:
queued
running
done
failed
用户与支持人员都不用再猜按钮是否生效,系统有任务记录和最新状态。
重试只有在任务能承受重复执行时才有用。它不应造成重复记录、重复邮件或重复扣款。幂等键让 worker 知道“这个逻辑请求已经处理过”。
失败也需要产品层面的答案,不能只消失在工程师才能看懂的日志里。用户可能需要简短错误说明、重试按钮或下载校验错误的入口。具体界面取决于流程,但失败状态必须设计。
队列不会消除复杂性,只是转移它。你仍需维护 worker、监控、重试策略及部署过程中的行为。相比把长任务塞进一个请求让用户等待,这通常更合理,但并非没有成本。
应用已使用 PostgreSQL、任务量适中、重视运维简单且不想增加独立队列服务时,我会选择 pg-boss。任务吞吐成为瓶颈、worker 需要特殊扩缩容,或已有队列基础设施时,则考虑独立队列。
后台任务也是产品功能。队列重要,但用户看得见的流程更重要。持久任务没有清楚的状态页,用户依然只能猜。