高流量PHP 5.6网站Server-Sent Event高性能实现方案咨询
针对高流量PHP网站的高性能Server-Sent Event(SSE)实现方案
首先得明确你面临的核心问题:传统PHP-FPM架构下,每个SSE长连接会占用一个FPM进程,而你日均25万UV、并发2000的场景下,这种模式会直接耗尽FPM进程池,导致新请求无法处理——这也是为什么你觉得那些长循环示例和libSSE-php不适用的原因。
下面是几个经过验证的高性能方案,适配你的PHP 5.6环境:
一、Nginx + 消息队列(Redis):最优高并发方案
这个方案的核心是把长连接的维持工作从PHP转移到Nginx,PHP只负责生产消息,不处理客户端连接,彻底解决FPM进程被占满的问题。
实现原理
- 客户端直接和Nginx建立SSE长连接,Nginx作为代理,监听Redis的指定频道;
- 当你的PHP业务系统产生通知时,通过Redis的
PUBLISH命令将消息发送到对应频道; - Nginx收到Redis推送的消息后,立即转发给所有订阅该频道的客户端。
关键步骤
- 配置Nginx:需要安装支持SSE和Redis订阅的模块(比如
ngx_http_push_stream_module或OpenResty的lua-resty-redis)。示例配置大致如下:
location /sse/notifications { push_stream_subscriber; push_stream_channels_path $arg_channel; push_stream_message_template "data: {message}\n\n"; push_stream_redis_host 127.0.0.1; push_stream_redis_port 6379; push_stream_redis_db 0; push_stream_redis_channel_prefix "sse:"; }
- 客户端代码:连接Nginx的SSE端点,指定用户专属频道(比如用户ID):
if (!!window.EventSource) { const userId = '12345'; // 替换为当前用户ID const source = new EventSource(`/sse/notifications?channel=${userId}`); source.addEventListener('message', function(e) { const data = JSON.parse(e.data); console.log('收到通知:', data); // 更新页面通知数量 }, false); // 处理重连和错误 source.addEventListener('error', function(e) { if (e.readyState === EventSource.CLOSED) { console.log('连接断开,尝试重连...'); // 这里可以实现指数退避重连逻辑 } }); }
- PHP业务代码:当有新通知时,推送到Redis:
<?php // 连接Redis(需要安装phpredis扩展) $redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 推送消息到用户专属频道 $userId = '12345'; $message = json_encode(['unread_count' => 5]); $redis->publish("sse:{$userId}", $message);
优势
- PHP完全脱离长连接,只做消息生产,性能开销极低;
- Nginx天生擅长处理高并发长连接,能轻松支撑数千甚至上万的SSE连接;
- 扩展性强,后续可以轻松扩容Redis或Nginx节点。
二、常驻进程框架(Workerman/Swoole):纯PHP生态的高性能方案
如果不想改动Nginx配置,或者更倾向于纯PHP技术栈,可以用常驻进程框架来实现SSE,这类框架基于EventLoop,一个进程能处理上千个连接,彻底摆脱FPM短进程的限制。
以Workerman为例(适配PHP 5.6)
Workerman对PHP 5.6支持良好,无需额外编译复杂模块。
1. 安装Workerman
composer require workerman/workerman:^3.5 # 3.x版本支持PHP 5.3+
2. 编写SSE服务端代码
<?php require_once __DIR__ . '/vendor/autoload.php'; use Workerman\Worker; // 创建一个HTTP服务,监听8080端口 $worker = new Worker('http://0.0.0.0:8080'); // 设置进程数,根据CPU核心数调整 $worker->count = 4; // 当客户端连接时触发 $worker->onConnect = function($connection) { // 设置SSE响应头 $connection->send("HTTP/1.1 200 OK\r\n"); $connection->send("Content-Type: text/event-stream\r\n"); $connection->send("Cache-Control: no-cache\r\n"); $connection->send("Connection: keep-alive\r\n"); $connection->send("\r\n"); // 记录用户ID(假设从请求参数获取) $userId = $_GET['user_id'] ?? ''; if ($userId) { // 将连接绑定到用户频道(可以用全局数组存储,或者Redis) global $userConnections; $userConnections[$userId][] = $connection; } }; // 当客户端断开连接时触发 $worker->onClose = function($connection) { // 从用户频道中移除连接 global $userConnections; foreach ($userConnections as $userId => $connections) { $key = array_search($connection, $connections); if ($key !== false) { unset($userConnections[$userId][$key]); break; } } }; // 启动Worker Worker::runAll();
3. PHP业务代码推送消息
<?php // 连接Workerman的内部通讯端口(或者用Redis中转) $client = stream_socket_client('tcp://127.0.0.1:8080'); if (!$client) { die('连接失败'); } // 构造推送消息(这里需要自定义协议,或者直接用Workerman的API) // 示例:向指定用户发送通知数量 $userId = '12345'; $message = json_encode(['unread_count' => 5]); $sendData = "data: {$message}\n\n"; // 找到该用户的所有连接并发送 global $userConnections; if (isset($userConnections[$userId])) { foreach ($userConnections[$userId] as $connection) { $connection->send($sendData); } } fclose($client);
优势
- 纯PHP实现,无需依赖Nginx模块,部署简单;
- 常驻进程复用资源,单进程支持上千连接,性能远高于FPM;
- 灵活度高,可以自定义消息路由和连接管理逻辑。
三、传统方案的临时优化(不推荐用于高并发)
如果暂时无法重构架构,可以尝试优化FPM配置临时过渡,但这只是权宜之计:
- 调高
pm.max_children和pm.start_servers,但会大幅增加内存消耗; - 启用
pm.process_idle_timeout,回收闲置进程; - 给SSE连接设置合理的超时时间,定期断开重连。
但这种方式无法从根本上解决长连接占用进程的问题,并发量上来后依然会崩溃。
额外注意事项
- 心跳机制:长连接可能被网关或CDN断开,需要定期发送心跳消息(比如每隔30秒发送
data: ping\n\n),客户端收到后无需处理,仅维持连接; - 连接重连:客户端要处理
error事件,实现自动重连(推荐指数退避策略,避免频繁重试); - PHP 5.6兼容性:选择框架时要确认版本支持,比如Swoole 1.x、Workerman 3.x都兼容PHP 5.6;
- 消息可靠性:如果需要确保消息不丢失,可以用Redis的持久化或者消息队列的ACK机制。
内容的提问来源于stack exchange,提问作者Yuseferi
相关产品推荐
相关产品推荐

