如何保证Heroku部署的服务更新时持续运行、不遗漏API轮询数据
Heroku轮询服务无中断更新解决方案
方案1:原生多Dyno滚动部署(最推荐)
Heroku原生支持滚动发布机制,是实现无中断更新成本最低的方案:
- 只要将你的服务dyno数量扩容到至少2个,部署新版本时平台会先启动足额的新版dyno,健康检查通过后再逐个销毁旧版dyno,全程至少有1个实例在线,不会出现服务空窗。操作命令为
heroku ps:scale worker=2(如果是后台worker类型服务)或heroku ps:scale web=2(如果是web类型服务兼做轮询)。 - 必须配置Node.js进程的SIGTERM信号监听逻辑:收到平台的终止信号后,先清空
setInterval定时器,等待当前正在执行的API拉取、数据落地逻辑完全完成后再退出进程,避免中途打断正在执行的任务导致数据丢失。示例代码如下:
const pollTimer = setInterval(fetchNewData, 30000) // 你的轮询逻辑 let currentFetchPromise = null // 存储当前正在执行的拉取请求Promise async function fetchNewData() { currentFetchPromise = // 你的API拉取逻辑 await currentFetchPromise // 数据落地、更新拉取位置标识逻辑 currentFetchPromise = null } process.on('SIGTERM', () => { clearInterval(pollTimer) // 等待当前拉取任务执行完成后再退出 if (currentFetchPromise) { currentFetchPromise.finally(() => process.exit(0)) } else { process.exit(0) } })
- 做好拉取逻辑的幂等性:将每次拉取的截止标识(比如最新数据ID、拉取时间戳)持久化到Redis、PostgreSQL等外部存储中,每次轮询都从上次存储的截止位置开始拉取,就算两个实例同时拉取,也可以通过数据唯一标识去重,既不会漏也不会产生重复数据。
方案2:加分布式锁避免重复轮询
如果不希望多个实例同时跑轮询逻辑,可以增加分布式锁控制:
- 用Redis的
SETNX命令实现轻量分布式锁,每个轮询周期开始前先尝试抢锁,抢锁成功的实例执行拉取逻辑,抢锁失败的实例直接跳过当前周期。 - 锁要设置合理的过期时间(略长于单次轮询的最大耗时),避免实例异常退出导致锁永远不释放。
- 部署更新时旧实例销毁后,新启动的实例会自动承接抢锁逻辑,全程不会出现无实例拉取的情况。
方案3:低频轮询场景用定时任务替代常驻进程
如果你的轮询间隔≥10分钟,可以直接用Heroku原生的Scheduler定时任务,把轮询逻辑改成单次执行的脚本,由平台定时触发执行:
- 常驻进程只处理其他业务逻辑,轮询完全独立于常驻服务,部署更新完全不会影响定时任务的触发,不存在数据遗漏风险。
关键注意事项
- 绝对不要用单dyno部署:单dyno场景下Heroku部署时会先停旧实例再启动新实例,中间会有几十秒到几分钟的空窗期,必然会遗漏数据。
- 所有轮询状态不要存在本地内存:比如上次拉取位置、轮询开关等状态必须存在外部共享存储,否则实例重启或切换时状态会丢失,导致重复拉取或漏拉。即使出现极端的服务全断情况,恢复后也可以从存储的上次拉取位置补拉数据,完全不会遗漏。
内容的提问来源于stack exchange,提问作者Mario Lopez
相关产品推荐
相关产品推荐

