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

Heroku后台脚本提前终止?带延迟的PHP消息发送脚本运行异常排查

解决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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:34:12