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

能否用CronJob替代While循环实现PHP Server-Sent-Events以解决进程堆积?

解决SSE导致Apache max_children耗尽的替代方案

问题根源

你当前用PHP无限循环实现SSE的方式,会让每个用户的SSE长连接占用一个PHP-FPM进程,进程长期处于挂起(sleep等待)状态。当并发用户量上升时,很快就会耗尽Apache或PHP-FPM的max_children阈值,导致新请求无法处理。

可行解决方案

1. 事件驱动SSE服务(推荐)

改用基于事件驱动的PHP框架(如Workerman、Swoole)实现SSE,这类框架可以通过少量进程管理数千个并发长连接,彻底解决进程堆积问题。

以Workerman为例的实现代码:

use Workerman\Worker;
require_once __DIR__ . '/Workerman/Autoloader.php';

// 创建HTTP Worker监听指定端口
$worker = new Worker('http://0.0.0.0:8080');
$worker->count = 4; // 根据服务器配置调整进程数,4个进程即可应对大量并发

$worker->onMessage = function($connection, $data) {
    // 发送SSE响应头
    $connection->send(implode("\r\n", [
        'HTTP/1.1 200 OK',
        'Content-Type: text/event-stream',
        'Cache-Control: no-cache',
        'Connection: keep-alive',
        '', ''
    ]));
    
    // 定时检查并推送新事件,替代原有的sleep循环
    $timerId = \Workerman\Timer::add(3, function() use ($connection) {
        // 从缓存/数据库获取当前连接用户的新事件
        $newEvent = getLatestUserEvent($connection->getUserId());
        if ($newEvent) {
            $connection->send("data: {$newEvent}\n\n");
        }
    });
    
    // 连接关闭时清理定时器
    $connection->onClose = function() use ($timerId) {
        \Workerman\Timer::del($timerId);
    };
};

Worker::runAll();

这种模式下,单个进程可维持数百甚至上千个长连接,不会像传统PHP-FPM那样每个连接占用一个进程,从根源上避免max_children耗尽。

2. CronJob预生成消息+短连接轮询(折中方案)

如果不想引入事件驱动框架,可通过CronJob定时生成推送消息,结合客户端短连接轮询获取,替代长连接SSE:

步骤1:CronJob定时生成消息

编写PHP脚本generate_sse_events.php,负责查询新事件并写入Redis/Memcached等缓存:

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

// 查询需要推送的事件(示例:从数据库获取)
$events = queryNewEvents();
foreach ($events as $userId => $eventContent) {
    $redis->set("sse:event:{$userId}", $eventContent);
    // 设置过期时间,避免缓存堆积
    $redis->expire("sse:event:{$userId}", 60);
}

通过CronJob定时执行该脚本(比如每3秒一次):

*/3 * * * * php /path/to/generate_sse_events.php

步骤2:客户端轮询接口

客户端不再保持长连接,每隔3秒请求一次接口获取最新消息:

// 轮询接口代码
$userId = $_GET['user_id'];
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$newEvent = $redis->get("sse:event:{$userId}");
if ($newEvent) {
    echo "data: {$newEvent}\n\n";
    $redis->del("sse:event:{$userId}"); // 避免重复推送
} else {
    echo "data: ping\n\n";
}

这种方式每个请求都是短连接,PHP-FPM进程处理完成后立即释放,不会长期占用资源,有效避免进程堆积。

3. 临时配置优化(仅缓解)

如果暂时无法调整架构,可通过修改配置临时缓解:

  • 调整PHP-FPM的pm.max_children:根据服务器CPU核心数和内存配置合理提高(比如CPU核心数×4)
  • 启用Apache的mpm_event模式:替代默认的prefork模式,更适合处理长连接
  • 设置pm.process_idle_timeout:缩短闲置进程的回收时间,释放资源

但这只是临时方案,当用户量持续增长时,仍会遇到进程耗尽的问题。

内容的提问来源于stack exchange,提问作者Avatar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 02:27:47