Heroku Scheduler执行Node脚本异常问题排查求助
首先明确:不需要在脚本开头添加请求唤醒Heroku实例——Heroku Scheduler在触发定时任务时,会自动唤醒处于休眠状态的Web Dyno来执行任务。你的问题大概率是其他细节导致的,以下是排查和修复方向:
1. 确认Scheduler的执行命令正确
确保你在Heroku Scheduler中配置的命令是直接调用脚本的正确指令,比如:
node createLottery.js
不要用curl https://your-app.herokuapp.com/xxx这类依赖Web服务的请求——虽然curl也能唤醒Dyno,但如果你的脚本是独立的Node.js脚本,直接用node命令更可靠,避免Web服务启动延迟导致的问题。
2. 修复时区偏差问题
Heroku服务器默认使用UTC时区,你的代码中new Date().getDay()是基于UTC时间判断周几的。如果你期望的是本地时区(比如北京时间)的周日/周三,这里会出现偏差,导致任务执行了但逻辑被跳过,看起来像是没触发。
解决方法:可以转换为目标时区判断周几,比如用原生API处理:
// 示例:转换为北京时间(UTC+8)判断周几 const currentDate = new Date(); const beijingDay = new Date(currentDate.getTime() + 8 * 60 * 60 * 1000).getDay(); // 0=周日,3=周三 if (beijingDay === 0 || beijingDay === 3) { // 执行生成逻辑 }
或者使用moment-timezone这类库来更精准处理时区。
3. 修复异步脚本的退出问题
你的脚本最后调用createTime()但没有等待异步操作完成,脚本可能在数据库操作结束前就退出,导致Heroku认为任务完成,但实际逻辑没执行完,日志也会不完整。
修改脚本末尾的调用逻辑,加上错误处理和资源清理:
createTime() .catch(err => { console.error('任务执行失败:', err); process.exit(1); // 非0退出码让Heroku标记任务失败,方便排查 }) .finally(() => { mongoose.connection.close(); // 关闭数据库连接,避免资源泄漏 process.exit(0); });
4. 去掉冗余的周几判断
既然你已经通过Heroku Scheduler设置了每周日和周三执行任务,脚本里再判断currentDay ===0 || 3属于冗余逻辑,反而可能因为时区问题导致任务白跑。直接去掉这个判断,让Scheduler完全控制执行时间,脚本只负责生成 lottery 数据即可。
5. 检查Dyno资源限制
如果使用的是Heroku免费版Dyno,每月有固定的执行时间配额,耗尽后任务会无法执行。可以在Heroku控制台的「Resources」页面查看Dyno的使用情况,确认配额是否充足。
修改后的完整代码示例
const LotteryModel = require("./models/LotteryModel"); const mongoose = require("mongoose"); require("dotenv").config(); mongoose.set("strictQuery", false); mongoose.connect(process.env.MONGODB_URI); const currentDate = () => { const date = new Date(); const day = date.getDate(); const month = date.getMonth() + 1; const year = date.getFullYear(); return `${day}-${month}-${year}`; }; const createNumbers = () => { let tempArr = []; let randomNumber; let specialNum = { number: Math.floor(Math.random() * 7) + 1, special: true }; // 原判断可简化:Math.random()*7+1的范围是1-8,不会出现0 // if (specialNum.number === 0) specialNum = { number: Math.floor(Math.random() * 7) + 1, special: true }; for (let i = 0; i < 6; i++) { let add = true; randomNumber = { number: Math.floor(Math.random() * 36) + 1, special: false }; // 优化重复判断逻辑,用some更高效 if (tempArr.some(item => item.number === randomNumber.number)) { add = false; } if (add) { tempArr.push(randomNumber); } else { i--; } } tempArr.push(specialNum); return tempArr; }; const createTime = async () => { console.log("starting function"); const currentTime = currentDate(); const findLottery = await LotteryModel.findOne({ lotteryDate: currentTime }); // 如果当天已经生成过,直接退出 if (findLottery) { console.log("今日lottery已存在,无需重复生成"); return; } console.log("开始生成lottery数据"); const allLottery = await LotteryModel.find({}); const lotteryNumbers = { lotteryNumbers: createNumbers(), lotteryDate: currentTime, id: allLottery.length }; console.log(lotteryNumbers); await LotteryModel.create(lotteryNumbers); console.log('finish create numbers'); }; // 处理异步执行和错误 createTime() .catch(err => { console.error('任务执行出错:', err); process.exit(1); }) .finally(() => { mongoose.connection.close(); process.exit(0); });
内容的提问来源于stack exchange,提问作者Lior98

