flush() 作用是清空 Web 服务器缓冲区(如 Nginx FastCGI 层),非 PHP 自身缓冲;需与 ob_flush() 配合使用,且受服务器配置、浏览器缓存及 CDN 等多重限制,现代应用推荐改用 SSE 或 WebSocket。

flush() 的作用:把 Web 服务器缓冲区的内容真正推给浏览器
flush() 不是“让 PHP 输出”,而是告诉 Web 服务器:“别攒着了,现在就把手头这波数据发出去”。它操作的是 Web 服务器(如 Apache 或 Nginx 的 FastCGI 层)那一级的缓冲,和 PHP 自己的输出缓冲无关。
常见错误现象:
– PHP 脚本里写了 echo "step 1"; flush();,但浏览器等 5 秒才一次性看到全部输出;
– 本地开发用内置服务器(php -S)能看到实时效果,上线后却完全不生效。
这是因为:
– flush() 对 Nginx 默认无效(它自带 fastcgi_buffering on);
– Apache 模块模式下才真正起作用,而大多数线上环境是 PHP-FPM + Nginx;
– 即使 flush() 成功,浏览器也可能因未收到足够字节(比如少于 1KB)而自行缓存,不渲染。
实操建议:
– 必须配合 ob_flush()(清 PHP 缓冲)一起用,顺序固定为:ob_flush(); flush();;
– 在脚本开头加 header('X-Accel-Buffering: no');(Nginx 识别)和 header('Cache-Control: no-cache');;
– 输出前补足空格或注释(如 str_repeat(" ", 1024)),绕过浏览器最小缓冲门槛。
立即学习“PHP免费学习笔记(深入)”;
ob_flush() 和 flush() 的区别:PHP 层 vs 服务器层
它们不是“一个刷新内容、一个刷新格式”,而是作用在**不同缓冲层级**上的两个独立动作:
ob_flush():只清 PHP 输出缓冲区(output buffer),也就是你用 ob_start() 开启的那个内存缓冲;flush():只清 Web 服务器到客户端之间的缓冲(例如 Nginx 的 fastcgi buffer、Apache 的 mod_php 输出队列)。
所以单独调用任一函数都不保险:
– 只 ob_flush() → 数据还在 PHP 里,没到服务器;
– 只 flush() → PHP 缓冲还没吐出来,服务器那层压根没东西可发。
必须成对使用,且顺序不能反:
– ob_flush() 先把内容从 PHP 缓冲“倒进”服务器缓冲;
– flush() 再把服务器缓冲“倒进”客户端。
注意:
– CLI 模式下 flush() 无意义(没有 Web 服务器中间层);
– 若 PHP 配置了 zlib.output_compression = On,压缩会拦截并延迟所有输出,必须关掉;
– ob_implicit_flush(true) 是个陷阱:它会让每次 echo 后自动调用 ob_flush(),但不会调用 flush(),仍需手动补。
Nginx 下 flush() 失效?关键配置就这一行
绝大多数线上环境卡在这里:PHP 脚本明明调了 ob_flush(); flush();,但 Nginx 死活不转发——因为它的 fastcgi_buffering 默认是开的。
解决方法只有改 Nginx 配置(在对应 location ~ \.php$ 块内):fastcgi_buffering off;
其他常见干扰项:
– proxy_buffering off;(如果你用了反向代理);
– gzip off; 或至少确保 gzip_vary off;,否则 gzip 可能劫持流式响应;
– 禁用 Cloudflare 等 CDN 的“优化”功能(它们会强制缓冲 HTML 响应)。
验证是否生效:
– 用 curl -N http://yoursite.com/long-task.php(-N 关闭 curl 自身缓冲);
– 查看响应头是否含 X-Accel-Buffering: no;
– 用浏览器开发者工具 Network → Response,观察内容是否分段出现(而非整块返回)。
替代方案比死磕 flush() 更靠谱
当你要做“任务进度反馈”“日志回显”这类事时,硬靠 flush() 实现实时,本质是在对抗整个 HTTP 栈的设计惯性。越往后走,坑越多:超时、代理截断、HTTPS 中间设备缓存、移动端兼容性……
更稳健的做法是切换通信模型:
– 后端把耗时任务扔进消息队列(如 Redis LPUSH),立刻返回任务 ID;
– 前端用 EventSource 订阅 SSE 接口,或用 WebSocket 连接长活工作进程;
– 进度由独立 worker 更新 Redis 或数据库,SSE 接口只负责读取并流式推送。
这样既规避了 flush() 的所有环境依赖,又天然支持多客户端、断线重连、任务排队——而且 PHP 不再需要扛住几十秒甚至几分钟的请求连接。
真要保留同步脚本方式,至少加保底:
– 设置 set_time_limit(0) 和 ignore_user_abort(true);
– 用 register_shutdown_function() 记录最终状态;
– 所有 flush() 调用后,加 usleep(10000)(10ms),避免高频刷爆服务器缓冲。
最后提醒一句:flush() 不是实时通信的银弹,它是 HTTP/1.1 时代在妥协中挤出的一条缝。现代架构里,它该退居二线了。











