PHP实时输出在线状态不能仅靠echo,因受输出缓冲、Web服务器压缩及浏览器缓存影响;需禁用缓冲、关闭压缩、手动flush,并用Redis+EventSource或CLI订阅实现真正实时。
echo 直接刷屏因为 PHP 默认启用输出缓冲(ob_start),且 Web 服务器(如 Nginx + FPM)和浏览器都会做额外缓存,echo 后数据不会立刻发给客户端。你看到的“卡住”或“一次性全出来”,本质是缓冲未被强制刷新。
要让浏览器逐行接收、实时渲染,必须:禁用缓冲、关闭压缩、手动冲刷输出,并保持连接不中断。
ignore_user_abort(true) 和 set_time_limit(0),防止用户断开或超时终止ob_end_flush() 清空并关闭输出缓冲;若缓冲未开启,先 ob_end_clean() 再 ob_implicit_flush(true)
flush() 和 ob_flush()(顺序不能反)Content-Encoding: none,并禁用 Nginx 的 gzip 或 Apache 的 mod_deflate(否则压缩会阻塞流式输出)file_get_contents 轮询检测在线状态太耗资源每隔几秒就 file_get_contents('status.json') 或查数据库,对服务端压力大,且无法做到真正“实时”。更糟的是,频繁请求还容易触发限流或被 WAF 拦截。
推荐改用轻量级持久化方案:把用户心跳写入内存存储(如 Redis),再由一个独立的 PHP 长连接脚本持续读取并输出。这样检测逻辑和输出逻辑解耦,也避免阻塞主业务。
$redis->setex('user:123:online', 30, time())

$redis->keys('user:*:online') 或订阅 PUBLISH/PSUBSCRIBE 获取变更keys 在大数据量下是 O(n),生产环境应改用 SCAN + 过期时间过滤直接用 fetch 拿不到流式数据,因为默认等待 complete;XMLHttpRequest 也不支持分块解析。必须用 EventSource(SSE)或 ReadableStream + Response.body。
SSE 最简单:后端输出格式为 data: {"id":123,"online":true}\n\n,前端监听 message 事件即可。但注意 SSE 不支持自定义 HTTP 方法和 headers,且 IE 全系不支持。
echo "data: ".json_encode($msg)."\n\n"; flush();
new EventSource('/status-stream.php'),不要用 fetch 试图读 response.body —— 没有 text/html MIME 类型时,Chrome 会拒绝解析fetch + Response.body.getReader(),但需服务端返回 text/event-stream 且禁用 Transfer-Encoding: chunked 干扰当同时在线用户超 5000,用定时轮询 Redis keys 会产生明显延迟和 CPU 波动。改用 PUBLISH user:online:123 {"status":"online"},后端脚本 $redis->subscribe(['user:online:*'], $callback),能实现毫秒级响应。
但要注意:Redis SUBSCRIBE 是阻塞命令,不能和普通操作混用;PHP 的 phpredis 扩展在 CLI 模式下才支持,Web SAPI(如 FPM)里调用会卡死进程。
php redis-subscribe.php,通过 systemd 或 supervisord 管理shmop)或临时文件,再由 Web 脚本读取 —— 切勿在 FPM 进程里直接 subscribe
onMessage 中处理 Redis Pub/Sub,但需确保 Redis 连接是 connect 而非 pconnect,避免连接复用冲突Connection: keep-alive、浏览器是否真的收到了 text/event-stream、以及 Redis 的 SUBSCRIBE 是否真的在后台活着——这些环节任何一个断掉,都会让你以为逻辑有问题,其实只是中间链路静默失败了。