PHP 8.1 实现 Mistral 流式输出需禁用 output_buffering 和 zlib 压缩,手动调用 ob_flush() 与 flush(),逐行解析 SSE 数据并实时输出,同时适配 Nginx/Apache 缓冲策略及安全终止机制。

PHP 8.1 中调用 Mistral(或其他 LLM API)实现流式输出时,不能依赖默认的 ob 缓冲自动逐块刷新。因为 PHP 默认输出缓冲是“全量缓存后一次性发送”,而流式响应需要边接收、边处理、边输出。必须主动干预缓冲行为,否则浏览器收不到中间 chunk,只等到脚本结束才看到全部内容。
确保底层输出不被阻塞
流式输出的前提是:PHP 脚本产生的每一小段响应都立刻发给客户端。这要求:
- 禁用 PHP 的全局输出缓冲(如
output_buffering = Off或设为0),避免php.ini层级拦截 - 关闭 zlib 压缩(
zlib.output_compression = Off),否则压缩器会等待完整数据才能编码 - 调用
ob_end_flush()或@ob_end_clean()清掉可能已启动的缓冲区(比如框架或 autoload 触发的) - 执行
flush()和ob_flush()配合使用——ob_flush()清空 PHP 用户级缓冲,flush()将 Web 服务器(如 Apache/Nginx)或 CGI 层的缓冲也推下去
手动管理流式 chunk 输出
Mistral 的流式接口(如 /v1/chat/completions?stream=true)返回的是以 data: 分隔的 SSE 格式。PHP 需要逐行读取、解析、转义并输出:
- 用
curl_setopt($ch, CURLOPT_WRITEFUNCTION, ...)或stream_socket_client+stream_set_blocking(false)实现非阻塞读取 - 每次收到一个完整
data: {...}行,提取delta.content,用echo "data: ".json_encode([...])."\n\n"输出 - 每输出一行后立即执行
ob_flush(); flush();,必要时加usleep(1000)防止过快刷屏或触发代理截断 - 避免在循环中调用
ob_start()或任何会新建缓冲的操作,否则嵌套缓冲会让ob_flush()失效
适配不同部署环境的刷新策略
本地开发(PHP 内置服务器)和生产(Nginx + FPM)对 flush() 支持差异大:
立即学习“PHP免费学习笔记(深入)”;
- Nginx 默认启用
fastcgi_buffering on,会缓存响应直到满 4KB 或结束。需在 server 配置中加:fastcgi_buffering off;或fastcgi_max_temp_file_size 0; - Apache + mod_php 下,
apache_setenv('no-gzip', '1')+ 关闭mod_deflate更可靠 - 若无法改服务器配置,可用“注释填充法”:每行输出前加几十个空格或换行符,凑够 1KB 触发 Nginx 自动 flush(兼容性高但略丑)
安全终止与错误兜底
流式请求中途失败或用户断连时,需及时释放资源:
- 用
connection_aborted()检测客户端是否断开,提前exit - 设置
set_time_limit(0)防止超时中断流 - 在
register_shutdown_function()中清理 curl 句柄或关闭 stream - 不要在输出处理函数(如
ob_start($handler)的回调里)调用ob_flush()或flush(),会触发 fatal error



















