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

如何在Heroku中运行需长时间执行的PHP消息推送脚本?

解决PHP长时间推送消息任务中断的问题

这个场景我碰到过不少,咱们一步步拆解问题,从临时解决到长期可靠方案都给你捋清楚:

先试试延长超时时间(临时应急方案)

你的脚本30分钟就停,大概率是PHP或服务器层面的超时限制在起作用,先逐个排查调整:

  • PHP脚本层面:在脚本最开头加上set_time_limit(0);,这会让PHP本身取消脚本执行时间限制,但注意如果是FPM环境,这个设置可能被全局配置覆盖。
  • FPM配置层面:找到你的PHP-FPM配置文件(比如www.conf),修改request_terminate_timeout和max_execution_time这两个参数,设为足够大的值(比如18000,也就是5小时,留足冗余),改完记得重启PHP-FPM服务。
  • Web服务器层面:如果是Nginx,要调整proxy_read_timeout和fastcgi_read_timeout;如果是Apache,修改Timeout指令,确保服务器不会提前断开和脚本的连接。

不过要提醒:长时间跑单个PHP脚本风险很高,服务器重启、脚本崩溃、内存泄漏都可能导致任务半途而废,这只是临时救急,更推荐下面的标准方案。

更可靠的长期方案:队列系统 + 常驻Worker

这是处理这类异步延迟任务的行业标准做法,稳定性拉满:
核心思路是把每个用户的推送任务拆成独立单元,放进队列,再用常驻Worker进程逐个消费,配合进程管理器保证Worker不挂。

举个简单的Redis延迟队列实现例子

  1. 先把任务批量存入延迟队列:
set_time_limit(0);
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

// 假设这是你的30个用户ID列表
$userIds = range(1, 30);
$currentTimestamp = time();

foreach ($userIds as $index => $userId) {
    // 计算每个任务的执行时间:第N个用户延迟6*N分钟
    $delaySeconds = $index * 6 * 60;
    $executeAt = $currentTimestamp + $delaySeconds;
    // 把任务序列化后存入有序集合(用时间戳作为分数)
    $redis->zAdd('message_push_queue', $executeAt, json_encode(['user_id' => $userId]));
}
  1. 写一个常驻Worker脚本消费队列:
set_time_limit(0);
ignore_user_abort(true); // 忽略客户端断开连接

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

while (true) {
    $now = time();
    // 取出当前时间点到期的第一个任务
    $tasks = $redis->zRangeByScore('message_push_queue', 0, $now, ['limit' => [0, 1]]);
    
    if (!empty($tasks)) {
        $task = json_decode($tasks[0], true);
        // 执行你的消息推送逻辑
        sendMessageToUser($task['user_id']);
        // 任务完成后从队列移除
        $redis->zRem('message_push_queue', $tasks[0]);
    }
    
    // 每隔10秒检查一次队列,避免占用过多CPU资源
    sleep(10);
}

// 你的消息推送函数
function sendMessageToUser($userId) {
    // 这里写具体的推送逻辑,比如调用短信API、站内信接口等
    error_log("推送消息给用户{$userId}成功,时间:" . date('Y-m-d H:i:s'));
}
  1. 用Supervisor管理Worker进程:
    写一个Supervisor配置文件(比如/etc/supervisor/conf.d/message_worker.conf):
[program:message_push_worker]
command=php /path/to/your/worker.php
autostart=true          # 开机自动启动
autorestart=true        # Worker挂了自动重启
user=www-data           # 运行Worker的用户(和你的Web服务用户一致)
stdout_logfile=/var/log/message_push.log   # 正常日志路径
stderr_logfile=/var/log/message_push_error.log # 错误日志路径

然后重启Supervisor:supervisorctl reread && supervisorctl update && supervisorctl start message_push_worker

这样Worker会一直后台运行,就算服务器重启,Supervisor也会自动把它拉起来,每个任务独立执行,单个任务失败也不会影响全局。

备选方案:Cron定时任务

如果不想搞队列和常驻进程,也可以用系统定时任务拆分任务:

  • 把每个用户的推送时间提前存在数据库里(比如send_at字段)
  • 配置一个每分钟执行一次的Cron任务:* * * * * php /path/to/check_send_task.php
  • 在check_send_task.php里,查询当前时间需要推送的用户,执行推送逻辑,然后标记为已推送

这个方案适合简单场景,不需要维护常驻进程,但灵活性不如队列系统。

最后提醒:不管用哪种方案,一定要加详细的日志记录,推送成功/失败都要记下来,方便后续排查问题。

内容的提问来源于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 09:14:29