PHP实时输出在线状态不能仅靠echo,因受输出缓冲、Web服务器压缩及浏览器缓存影响;需禁用缓冲、关闭压缩、手动flush,并用Redis+EventSource或CLI订阅实现真正实时。

PHP 实时输出用户在线状态为什么不能靠 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 长连接脚本持续读取并输出。这样检测逻辑和输出逻辑解耦,也避免阻塞主业务。
立即学习“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 全系不支持。
- PHP 端每条消息结尾必须是双换行:
echo "data: ".json_encode($msg)."\n\n"; flush(); - 前端初始化时加
new EventSource('/status-stream.php'),不要用fetch试图读response.body—— 没有text/htmlMIME 类型时,Chrome 会拒绝解析 - 如果必须用 POST 或带 token,只能退回到
fetch+Response.body.getReader(),但需服务端返回text/event-stream且禁用Transfer-Encoding: chunked干扰
Redis 订阅模式比轮询更适合高并发在线检测
当同时在线用户超 5000,用定时轮询 Redis keys 会产生明显延迟和 CPU 波动。改用 PUBLISH user:online:123 {"status":"online"},后端脚本 $redis->subscribe(['user:online:*'], $callback),能实现毫秒级响应。
但要注意:Redis SUBSCRIBE 是阻塞命令,不能和普通操作混用;PHP 的 phpredis 扩展在 CLI 模式下才支持,Web SAPI(如 FPM)里调用会卡死进程。
- 必须将订阅逻辑单独部署为常驻 CLI 脚本:
php redis-subscribe.php,通过systemd或supervisord管理 - CLI 脚本收到消息后,写入一个共享内存区域(如
shmop)或临时文件,再由 Web 脚本读取 —— 切勿在 FPM 进程里直接subscribe - 若用 Swoole,可直接在
onMessage中处理 Redis Pub/Sub,但需确保 Redis 连接是connect而非pconnect,避免连接复用冲突
Connection: keep-alive、浏览器是否真的收到了 text/event-stream、以及 Redis 的 SUBSCRIBE 是否真的在后台活着——这些环节任何一个断掉,都会让你以为逻辑有问题,其实只是中间链路静默失败了。











