Node.js 定时任务的幂等与恢复设计:从重复触发到可靠执行
问题背景定时触发不等于可靠执行在 Node.js 服务中用setInterval或 cron 库启动任务很容易难点是让任务在重复触发、进程崩溃和服务停机之后仍然产生正确的业务结果。例如每天给符合条件的账户发放积分。部署多个实例时同一时刻可能有多个执行者任务执行中重启可能只处理了一部分账户服务停机一天恢复后也不能只等下一次触发。设计时需要分别回答三个问题哪个业务周期需要处理重复执行会不会重复发放没有完成的工作如何重新被发现定时器只负责唤醒数据库负责记录事实。## 原理稳定标识、原子提交与持久化进度幂等的关键是为一次业务操作定义稳定标识。积分发放可以使用“任务类型、业务周期、账户 ID”作为唯一键。重复触发必须生成相同的键不能每次创建随机 UUID 来判断重复。业务周期也不能直接取执行时刻。补跑昨天的任务应继续使用昨天的周期标识。跨时区业务需要先确定业务时区和周期边界再转换成统一的存储格式避免夏令时或服务器时区改变任务含义。对于同一个 PostgreSQL 数据库中的操作可以把业务修改和任务完成标记放进一个事务。提交成功两者一起生效提交前崩溃两者一起回滚。恢复时重新扫描未完成任务即可。这实现的是业务效果的幂等。任务函数仍可能执行多次数据库事务和唯一约束负责限制最终结果。## 代码示例用事务处理积分任务下面使用 TypeScript 和pg。假设已有accounts(id, points)表每个任务给一个账户增加 10 积分。sqlCREATE TABLE scheduled_jobs ( task_name text NOT NULL, slot timestamptz NOT NULL, account_id bigint NOT NULL, status text NOT NULL DEFAULT pending CHECK (status IN (pending, done)), completed_at timestamptz, PRIMARY KEY (task_name, slot, account_id));调度端按确定的周期写入任务重复写入不会生成第二份工作。参数slot应由业务日历计算不应直接使用当前时间。tsimport { Pool } from pg;const pool new Pool();async function enqueue(slot: Date, accountId: string) { await pool.query( INSERT INTO scheduled_jobs (task_name, slot, account_id) VALUES (daily_points, $1, $2) ON CONFLICT DO NOTHING, [slot, accountId] );}执行端通过行锁领取任务。SKIP LOCKED让其他实例跳过已被领取的行继续处理不同任务。tsasync function runOne(): Promiseboolean { const client await pool.connect(); let reusable true; try { await client.query(BEGIN); const result await client.query( SELECT task_name, slot, account_id FROM scheduled_jobs WHERE status pending AND slot now() ORDER BY slot, task_name, account_id FOR UPDATE SKIP LOCKED LIMIT 1 ); const job result.rows[0]; if (!job) { await client.query(COMMIT); return false; } const changed await client.query( UPDATE accounts SET points points 10 WHERE id $1, [job.account_id] ); if (changed.rowCount ! 1) { throw new Error(Account does not exist); } await client.query( UPDATE scheduled_jobs SET status done, completed_at now() WHERE task_name $1 AND slot $2 AND account_id $3, [job.task_name, job.slot, job.account_id] ); await client.query(COMMIT); return true; } catch (error) { try { await client.query(ROLLBACK); } catch { reusable false; } throw error; } finally { client.release(!reusable); }}积分增加和状态更新共享事务行锁一直保留到事务结束。连接断开并被数据库识别后未提交事务会回滚任务仍为pending。如果提交成功但客户端没有收到响应重新扫描会看到done不会再次增加积分。示例只展示一次领取。调用端应限制并发在空队列或失败后延迟轮询生产环境还应配置事务超时并为持续失败的任务保存重试次数、下次执行时间和错误信息避免坏任务不断占用资源。## 恢复补齐遗漏的任务扫描pending只能恢复已经入库的任务。服务停机期间没有生成的任务需要调度端主动补齐。可以为每种调度规则保存“已生成到哪个周期”的游标。恢复时从游标之后逐批生成任务并在同一事务里写入任务和推进游标。多个调度实例还需要锁定对应游标行。这样既不会因为提前推进游标而漏任务也能依靠唯一约束处理重复生成。历史补跑还需要明确账户资格按历史快照发放还是按恢复时的账户状态发放。这属于业务规则应在生成任务前确定。## 常见错误- 使用进程内布尔变量防重。它只能约束当前进程重启和多实例部署都会失效。- 先标记完成再修改业务数据。中途失败会留下无法自动恢复的漏处理反过来分两次提交则可能重复修改。- 把数据库事务当作外部接口的事务。邮件、支付和 HTTP 请求不会随数据库回滚。应使用事务发件箱并要求接收方按稳定业务键去重无法去重时需要查询结果或对账补偿。- 删除完成记录却允许历史补跑。去重依据消失后旧任务可能再次产生业务效果。保留期限应覆盖补跑窗口或另外保留业务流水唯一键。## 总结可靠的定时任务需要把周期生成、任务执行和失败恢复连接起来稳定业务键识别重复事务保护数据库内的业务效果持久化游标补齐遗漏周期。定时器负责触发检查任务记录负责告诉系统哪些工作还没有完成。

相关新闻

长代码场景首选 ClaudeCode 搭配全离线内网工具,TaoToken 统一 Key 打通 RPA 7×24 小时落地

长代码场景首选 ClaudeCode 搭配全离线内网工具,TaoToken 统一 Key 打通 RPA 7×24 小时落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:28:11 阅读更多 →
Spring Boot物联网平台密钥泄露应急:密钥轮换与设备禁用实战

Spring Boot物联网平台密钥泄露应急:密钥轮换与设备禁用实战

半夜三点,手机震动把我从梦里拉出来。电话那头是运维同事的声音:“平台上一批设备密钥出现在日志截图里,已经有人拿着这个 Key 在尝试调设备接口了。”那一瞬间我脑子里的第一反应不是“要不要切备份”,而是“设备密钥泄露了怎么办…

2026/10/11 10:28:11 阅读更多 →
批处理执行模型与避坑实战:变量展开、括号块与for /f解析

批处理执行模型与避坑实战:变量展开、括号块与for /f解析

简介:《Windows命令行(批处理)语法全解》是一份面向Windows运维人员、开发者和脚本初学者的批处理语法参考文档。文档系统介绍了Command Shell与PowerShell两种命令行环境,不仅讲解如何编写.bat批处理文件,还深入分析了命令重定向运算符、for…

2026/10/11 10:27:11 阅读更多 →

最新新闻

酒店管理系统毕设:房态流转、数据库设计与Spring Boot实战

酒店管理系统毕设:房态流转、数据库设计与Spring Boot实战

简介:这是一套面向Java毕业设计场景的酒店管理系统完整资料包,涵盖毕业设计论文、答辩PPT、源代码、数据库及讲解视频,主要帮助计算机类专业学生解决系统开发、毕业论文撰写和答辩准备等环节的实际问题。压缩包共13个文件,包含3个…

2026/10/11 13:04:46 阅读更多 →
用Python从零开发轻量停车管理系统:入场计费与GUI实战

用Python从零开发轻量停车管理系统:入场计费与GUI实战

上个月帮一个朋友收拾小区停车场的烂摊子,他们的登记方式还停留在一本纸质记录本加一个计算器的阶段:进场时手写车牌和入场时间,出场时翻遍整本本子找记录、算费用。高峰期出口堵成一团,保安一边对着单据皱眉,一边跟车…

2026/10/11 13:04:46 阅读更多 →
免root下的五层执行隔离:OpenClaw on Android 安全架构完全解读(SELinux、应用沙箱与版本锁定)

免root下的五层执行隔离:OpenClaw on Android 安全架构完全解读(SELinux、应用沙箱与版本锁定)

移动开发AI 应用CLI开发工具 【免费下载链接】openclaw-android Run OpenClaw on Android with a single command — no proot, no Linux 项目地址: https://gitcode.com/gh_mirrors/op/openclaw-android 点击查看 免费下载 OpenClaw on Android 让你免 root 在安卓…

2026/10/11 13:04:46 阅读更多 →
TSN网络离线规划器OpenPlanner:从约束建模到GCL下发

TSN网络离线规划器OpenPlanner:从约束建模到GCL下发

简介:OpenPlanner是一个开源TSN规划器,面向工业物联网、自动驾驶、远程医疗等低时延高可靠场景的工程师与研究者,核心价值在于通过调度算法为时间敏感网络提供确定性传输规划。资源包共217个文件,以91个Python脚本与87个JSON配置为…

2026/10/11 13:04:46 阅读更多 →
NMODBUS 实战:C# 实现 Modbus TCP 主站与从站模拟

NMODBUS 实战:C# 实现 Modbus TCP 主站与从站模拟

简介:NMODBUS.zip 是一套面向 C# 开发者的 MODBUS TCP/IP 通信库资源,适合刚接触工业自动化协议、需要快速与 PLC 建立数据交互的新手与中级开发者。它封装了 ModbusTcpMaster 等核心类,提供读写保持寄存器、线圈以及数据转换、异常处理等常用…

2026/10/11 13:04:46 阅读更多 →
为什么iwe拒绝规定笔记结构?揭秘文本图管理工具的3条设计原则

为什么iwe拒绝规定笔记结构?揭秘文本图管理工具的3条设计原则

人工智能Agent 记忆MCP 服务CLI知识管理开发工具 【免费下载链接】iwe Markdown knowledge graph — LSP for your editor, CLI MCP memory for your AI agents 项目地址: https://gitcode.com/gh_mirrors/iw/iwe 点击查看 免费下载 🤔 用过不少笔记工…

2026/10/11 13:03:46 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →