独立产品接入 AI、导出 PDF、发送邮件或同步文件后,后台任务很快会出现。早期不一定需要复杂队列系统,可以用数据库表加 worker 起步。这个方案不追求时髦,重点是把任务状态、失败重试、幂等和可观察性写清楚,让系统可理解、可修复、可迁移。
一、任务状态机不要只用一个 done 字段
后台任务至少要区分 pending、running、succeeded、failed、retrying、canceled。状态清楚后,界面能告诉用户任务处于什么阶段,开发者也便于定位问题。原文给出的状态流转是:pending 进入 running;running 可到 succeeded、retrying 或 failed;retrying 再回到 running;pending 也可被取消为 canceled;succeeded 和 failed 为结束状态。导出任务尤其需要这种状态表达:用户点击导出后,可能经历模型整理、渲染、上传文件等阶段,只显示转圈不够稳定,显示“正在生成”“正在上传”“可下载”会更清楚。
二、用 jobs 表承载早期队列
早期可用一张 jobs 表承载任务,关键是通过加锁领取任务,避免多个 worker 同时处理同一条记录。原文表结构如下:- CREATE TABLE jobs (
- id TEXT PRIMARY KEY,
- type TEXT NOT NULL,
- payload JSONB NOT NULL,
- status TEXT NOT NULL,
- attempts INTEGER NOT NULL DEFAULT 0,
- run_after TIMESTAMP NOT NULL DEFAULT now(),
- locked_at TIMESTAMP,
- last_error TEXT,
- created_at TIMESTAMP NOT NULL DEFAULT now(),
- updated_at TIMESTAMP NOT NULL DEFAULT now()
- );
复制代码 Worker 轮询时只取 pending 或到期 retrying。数据库支持 FOR UPDATE SKIP LOCKED 时,可以用它更安全地并发领取。小系统也要认真处理并发,否则上线后可能出现重复导出、重复扣额度、重复发邮件。
三、失败重试要有上限和退避
AI 调用、对象存储、邮件服务都可能临时失败,重试必要但不能无限。指数退避和最大次数是最低要求。原文示例:- function nextRunAfter(attempts: number) {
- const seconds = Math.min(60 * 10, Math.pow(2, attempts) * 10);
- return new Date(Date.now() + seconds * 1000);
- }
- function shouldRetry(error: Error) {
- return !error.message.includes("invalid_api_key");
- }
复制代码 不是所有错误都适合重试。参数错误、权限错误、额度不足应快速失败,并给用户可理解提示;网络超时、第三方 5xx、临时限流才适合退避重试。示例中 invalid_api_key 被排除在重试之外。
四、幂等比队列技术更重要
后台任务容易被忽略的是幂等。任务可能因超时重试,也可能 worker 崩溃后重新执行。导出重复跑只是浪费资源,支付或扣额度重复执行就是事故。原文给出按任务定义幂等键的思路:- idempotency_rules:
- export_markdown:
- key: project_id + export_format + content_version
- duplicate_behavior: return_existing_file
- ai_generation:
- key: request_id
- duplicate_behavior: return_previous_result
- billing_charge:
- key: provider_event_id
- duplicate_behavior: ignore_duplicate
复制代码 每类任务都应定义幂等键,队列可以简单,但幂等策略不能空白,这是小产品从“能跑”走向“可靠”的分界线。
五、适用场景与小结构价值
原文场景包括独立产品接入 AI、导出 PDF、发送邮件、同步文件。用数据库表、worker、状态机、退避重试和幂等键,可以支撑不少早期后台任务。表结构中的 attempts、locked_at、last_error 等字段也为排查提供抓手。相比一开始引入重型队列,先把失败处理写清楚更重要。系统越小,每个边界越要让人看得懂;逻辑集中在表结构和 worker 中,也更便于后续修复与迁移。 |