Node.js 轻量任务队列独立产品先把失败处理写清楚独立产品接入 AI、导出 PDF、发送邮件、同步文件后很快就会遇到后台任务。很多人第一反应是上复杂队列系统但早期产品未必需要那么重。真正要先写清楚的是任务状态、失败重试、幂等和可观察性。Node.js 做轻量任务队列可以从数据库表加 worker 开始。它不时髦但足够透明。小产品最需要的是可理解、可修复、可迁移而不是一开始就把系统拆得很散。一、任务队列的核心是状态机后台任务不要只用一个done字段。至少要区分 pending、running、succeeded、failed、retrying、canceled。状态清楚界面才能告诉用户发生了什么开发者也能定位问题。stateDiagram-v2 [*] -- pending pending -- running running -- succeeded running -- retrying retrying -- running running -- failed pending -- canceled failed -- [*] succeeded -- [*]导出任务尤其需要状态。用户点击导出后可能需要等待模型整理、渲染、上传文件。只显示一个转圈会让人不安显示“正在生成”“正在上传”“可下载”体验会稳定很多。二、用数据库表承载早期队列早期可以用一张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、临时限流才适合退避重试。四、幂等比队列技术更重要后台任务最容易被忽略的是幂等。一个任务可能因为超时被重试也可能 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每类任务都应定义幂等键。队列系统可以简单但幂等策略不能空白。它是小产品从“能跑”走向“可靠”的分界线。五、总结Node.js 轻量任务队列不一定要复杂。早期用数据库表、worker、状态机、退避重试和幂等键就能支撑很多独立产品场景。先把失败处理写清楚比先引入重型队列更重要。系统越小越需要每个边界都让人看得懂。