Heroku后台脚本提前终止?带延迟的PHP消息发送脚本运行异常排查
哥们,我太懂你这个痛点了!之前帮朋友排查过几乎一模一样的问题——你用Ajax调用脚本+sleep(360)的思路本身就有个致命问题:Heroku的Web Dyno对Web请求有严格的超时限制,默认只要请求超过30秒没响应,Heroku的路由层就会直接断开连接,你的脚本自然就被强制终止了,这就是为什么只能处理10-20个用户就停了。
下面给你几个靠谱的解决方案,按优先级排序:
1. 立刻改用Heroku Worker Dyno执行长任务
这是最直接有效的办法,Worker Dyno就是Heroku专门用来跑后台长时间任务的,完全没有Web请求的超时限制。具体步骤:
- 把你发送消息的核心逻辑抽出来,写成一个独立的脚本,比如
send_bulk_messages.php - 在项目根目录新建一个
Procfile(注意首字母大写),里面加一行:worker: php send_bulk_messages.php - 部署代码到Heroku后,启动Worker进程:在终端跑
heroku ps:scale worker=1 - 之后这个Worker就会在后台稳稳跑9小时,把90个用户的消息都发完
2. 别再用Ajax/Web请求跑长任务了!
你之前用Ajax调用脚本的思路本质还是Web请求,不管你加多少sleep都没用——Heroku的Web服务器和路由层会死死盯着请求时长,超时就断开,根本不会等你的脚本跑完9小时。Web请求天生就是用来处理短平快的请求的,长任务交给Worker才是正确姿势。
3. 给脚本加个“断点续传”的保险(可选)
万一Worker Dyno因为Heroku的例行重启或者意外故障中断了,你总不想重新从第一个用户开始发吧?可以加个简单的持久化记录:
- 建个小表(比如
processed_users),每次处理完一个用户就把他的ID存进去 - 脚本启动时先查这个表,跳过已经处理过的用户
- 示例代码片段:
// 假设用PDO操作数据库 $pdo = new PDO('mysql:host=...;dbname=...', 'user', 'pass'); // 获取已处理的用户ID列表 $processed = $pdo->query("SELECT user_id FROM processed_users")->fetchAll(PDO::FETCH_COLUMN); foreach ($users_list["users"] as $user) { // 跳过已处理的用户 if (in_array($user['id'], $processed)) { continue; } // 执行发送消息的逻辑 send_message($user['phone'], $user['message']); // 标记为已处理 $stmt = $pdo->prepare("INSERT INTO processed_users (user_id) VALUES (?)"); $stmt->execute([$user['id']]); // 6分钟延迟 sleep(360); }
4. 进阶方案:用任务队列拆分任务(更灵活)
如果以后你要处理更多用户,或者需要重试失败的任务,推荐用PHP的任务队列库,比如php-resque(基于Redis)。把每个用户的消息发送拆成一个独立任务,设置每个任务之间延迟6分钟,Worker进程会自动按顺序处理。这种方式更灵活,还能监控任务状态、重试失败任务。
总之,核心就是把长任务从Web Dyno转移到Worker Dyno,这是Heroku上跑长任务的标准姿势,别再跟Web请求超时死磕啦!
内容的提问来源于stack exchange,提问作者Jerin A Mathews

