本文目录导读:

- 方式一:传统 HTTP 轮询(简单、兼容性好、适合低频)
- 方式二:WebSocket(真正的实时)
- 方式三:只用 PHP + Swoole (不需 Node.js)
- 方式四:SSE(Server-Sent Events,单向推送)
在 PHP 项目中实时检测节点在线状态,常见思路分为两类:传统轮询和WebSocket,下面分别介绍这两种方式,以及一种使用 Node.js + Socket.IO 作为实时推送中间件的混合方案(这是目前 PHP 项目中最成熟、最流行的做法)。
传统 HTTP 轮询(简单、兼容性好、适合低频)
原理:前端定时(如 5 秒)发送 Ajax 请求到 PHP 后端,PHP 检查节点最后存活时间。
后端 PHP 代码示例
// 更新节点在线状态(节点主动上报)
function nodeHeartbeat($nodeId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 将节点ID存入有序集合,score为当前时间戳
$redis->zAdd('online_nodes', time(), $nodeId);
// 设置过期时间,清理超时节点(比如30秒无心跳视为离线)
$redis->expire('online_nodes', 30);
}
// 检查节点是否在线
function isNodeOnline($nodeId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$score = $redis->zScore('online_nodes', $nodeId);
if ($score === false) return false;
// 判断是否在超时阈值内(30秒)
return (time() - $score) <= 30;
}
// 前端轮询接口
function getOnlineNodes() {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$now = time();
// 获取30秒内有心跳的节点
$nodes = $redis->zRangeByScore('online_nodes', $now - 30, $now);
return json_encode(['online' => $nodes]);
}
缺点
- 延迟取决于轮询间隔(无法做到真正的实时)。
- 高频轮询对服务器和数据库压力大。
WebSocket(真正的实时)
PHP 本身不擅长长连接,推荐方案:
PHP 负责业务逻辑(节点状态判断) + Node.js/Swoole 作为 WebSocket 服务推送状态变化。
架构说明
节点 ——(心跳上报)——> PHP API ——> Redis ——> Node.js WebSocket 服务 ——> 前端浏览器
- 节点:每隔几秒调用 PHP API 上报在线状态。
- PHP:更新状态到 Redis,并发布一个 Pub/Sub 消息。
- Node.js:订阅 Redis 频道,收到消息后通过 WebSocket 推送给所有前端。
- 前端:监听 WebSocket 消息,实时更新 UI。
关键实现
PHP 端(节点心跳 + 发布消息)
// 节点调用此API
function nodeHeartbeat($nodeId) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 更新在线状态
$redis->zAdd('online_nodes', time(), $nodeId);
$redis->expire('online_nodes', 30);
// 发布状态变化事件到Redis频道
$redis->publish('node_status_channel', json_encode([
'nodeId' => $nodeId,
'status' => 'online',
'timestamp' => time()
]));
}
- 当节点离线(超时)时,需要额外处理:在 Node.js 端也可以通过定时清理过期节点的逻辑主动发送离线通知。
Node.js 服务(WebSocket 服务端 + Redis 订阅)
const WebSocket = require('ws');
const redis = require('redis');
const wss = new WebSocket.Server({ port: 8080 });
const subscriber = redis.createClient();
// 订阅Redis频道
subscriber.subscribe('node_status_channel');
subscriber.on('message', (channel, message) => {
// 当收到状态变化消息,广播给所有连接的WebSocket客户端
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
前端监听
const ws = new WebSocket('ws://your-server:8080');
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
// 更新UI,
updateNodeStatus(data.nodeId, data.status);
};
只用 PHP + Swoole (不需 Node.js)
如果希望全部用 PHP 实现,可以使用 Swoole 扩展(PHP 的异步框架)。
Swoole 方案简述
// 一个简单的 WebSocket 服务端
$server = new Swoole\WebSocket\Server("0.0.0.0", 9501);
$table = new Swoole\Table(1024);
$table->column('last_time', Swoole\Table::TYPE_INT);
$table->create();
// 节点上报心跳(通过WebSocket发送消息)
$server->on('message', function ($server, $frame) use ($table) {
$data = json_decode($frame->data, true);
if (isset($data['action']) && $data['action'] == 'heartbeat') {
$table->set($data['node_id'], ['last_time' => time()]);
// 广播在线状态
$server->push($frame->fd, json_encode([
'nodeId' => $data['node_id'],
'status' => 'online'
]));
}
});
// 定时清理离线节点(略)
$server->start();
稳定性不如 Node.js + Redis,但如果你是纯 PHP 栈且不愿引入 Node.js,可以考虑。
SSE(Server-Sent Events,单向推送)
如果只需要服务端主动推送状态到前端(不需要前端发消息到服务端),SSE 比 WebSocket 更简单:
// PHP 端(需要长连接,配合 Nginx 注意配置)
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
while (true) {
$onlineNodes = $redis->zRangeByScore('online_nodes', time()-30, time());
echo "data: " . json_encode($onlineNodes) . "\n\n";
ob_flush();
flush();
sleep(2); // 间隔2秒
}
前端用 EventSource 监听即可。
缺点:连接数多时对 PHP-FPM 进程占用大,不适合大量客户端。
| 方案 | 实时性 | 复杂度 | 适用场景 |
|---|---|---|---|
| 轮询 | 低 | 低 | 简单页面,节点数量少 |
| WebSocket + Node.js + Redis | 高 | 中 | 企业级推荐 |
| Swoole WebSocket | 高 | 中 | 纯 PHP 团队 |
| SSE | 中 | 低 | 只需服务端推送 |
推荐:如果你的 PHP 项目是基于 Laravel/Symfony 等主流框架,且对实时性要求高,搭配一个 Node.js 或 Go 的小服务做 WebSocket 推送是目前业界最成熟的模式。