You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

node-cron每日定时任务执行频率异常 未按每日一次触发

问题原因
  • 核心问题是Heroku的dyno运行机制和内存级cron的特性不匹配:你写的cron表达式0 0 0 * * *本身语法正确,对应每日纽约时间零点触发,问题不在表达式配置上。Heroku免费/基础档位的dyno在30分钟无访问时会自动休眠,被请求唤醒、平台例行维护时都会重启进程。你的代码在全局作用域直接调用了一次randomize(),每次进程重启都会先执行一次这个初始化选歌,和cron调度无关。你观测到的1小时间隔,基本是定时健康检查/平台探测每小时左右唤醒休眠dyno,触发重启导致的重复选歌。
  • node-cron是内存内的调度器,调度状态完全存在当前进程内存里,进程一旦重启所有调度记录就会清空。你的dyno频繁重启的情况下,cron任务根本存活不到每天零点的触发时间点,你写的每日调度逻辑实际上从来没正常按预期执行过。
  • 额外代码bug:
    • Math.random()不接收参数,你写的Math.random(0,1)是无效写法,虽然不影响返回结果但属于冗余错误写法。
    • 递归选歌的逻辑有缺陷:当随机到已使用的歌曲ID递归调用randomize()后,代码依然会向下执行把当前重复的ID push进usedSongs数组,会导致数组出现重复值、长度异常的问题。
修复方案
  • 不要把当前选中歌曲、已使用歌曲列表存在进程内存里:Heroku dyno的内存和本地文件系统都是临时的,重启就会清空,需要把这类持久化状态存在外接存储里,比如Heroku自带的Postgres、Redis插件,存储内容至少包含:当日选中歌曲ID、已使用歌曲ID列表、上次选歌的日期。
  • 优先替换调度方案:不要用进程内的node-cron做每日定时任务,直接用Heroku平台官方的Scheduler插件,配置每日纽约时间零点触发选歌逻辑,平台级调度不受dyno休眠、重启影响,可靠性远高于进程内cron。
  • 如果一定要保留node-cron的用法,先把dyno升级到不会自动休眠的付费档位,保证进程持续在线不被随意重启,同时修复现有代码的逻辑bug,修正后参考代码如下:
const cron = require('node-cron');
// 注意:实际生产不要把这两个值存在内存,替换成持久化存储读写
let usedSongs = [];
let currentSongId;

function randomize() {
    if(usedSongs.length === 169) {
        usedSongs = [];
    }
    const test = Math.round(Math.random() * 169);
    if (usedSongs.includes(test)) {
        return randomize();
    }
    currentSongId = String(test);
    usedSongs.push(test);
    // 选完后把currentSongId、usedSongs、选歌日期写入持久化存储
}

// 服务启动时先读持久化存储,如果当日已经选过歌就直接用存好的值,不要重复选
// 伪代码逻辑:const lastSelectDate = 存储读上次选歌日期; if(lastSelectDate !== 当日日期) randomize(); else 读取存好的currentSongId和usedSongs赋值

cron.schedule('0 0 0 * * *', () => {
    randomize();
}, {
    scheduled: true,
    timezone: 'America/New_York'
});
  • 兜底校验:不管是启动初始化还是cron触发选歌,都先判断上次选歌日期是不是当日,如果是当日就跳过选歌逻辑,直接返回已选中的歌曲,从根源避免重复选歌。

内容的提问来源于stack exchange,提问作者J H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.31 10:54:47