thinkphp默认不支持分块传输超大json,需手动流式输出;json()和success()会因内存溢出或超时崩溃;应使用response()->stream()或手动echo+flush配合nginx/fpm调优实现chunked编码。

ThinkPHP 默认不支持分块传输(chunked transfer encoding)输出超大 JSON,必须手动接管响应流,否则内存爆掉或超时。
为什么 json() 和 success() 会崩
ThinkPHP 的 json() 方法会把整个数据先序列化成字符串,再一次性写入响应体。当数据量达几十 MB 以上时,PHP 内存占用飙升,memory_limit 很容易触发,同时 max_execution_time 也常超限。
常见错误现象:Fatal error: Allowed memory size of XXX bytes exhausted 或 504 Gateway Timeout(Nginx 层)。
- 不要试图用
ini_set('memory_limit', '-1')撑——只是掩盖问题,实际仍卡死 -
return json($hugeData)或$this->success('', $hugeData)都不可行 - 即使开了
output_buffering = Off,ThinkPHP 中间件和视图层仍会缓存输出
用 response() 手动流式输出 JSON 数组
核心思路:不拼完整 JSON 字符串,而是分批 echo JSON 片段(如数组元素),并手动控制 HTTP 头启用 chunked 编码。
关键点:JSON 格式需合法,所以通常输出为 JSON 数组流(JSON Text Sequences 不常用,兼容性差)。
- 开头输出
[,每条记录后加,,最后补] - 每次
echo后调用ob_flush()+flush()强制推送 - 必须提前设置
header('Content-Type: application/json; charset=utf-8')和禁用压缩:header('Content-Encoding: none') - 关闭 ThinkPHP 自动 JSON 包装:返回
response()实例,不要 return 数组
示例(简化版):
public function hugeJsonStream()
{
header('Content-Type: application/json; charset=utf-8');
header('Content-Encoding: none');
http_response_code(200);
echo '[';
$first = true;
// 假设从数据库游标逐条读取
$query = Db::table('big_table')->cursor();
foreach ($query as $row) {
if (!$first) echo ',';
echo json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$first = false;
// 强制刷新缓冲区
if (ob_get_level() > 0) ob_end_flush();
ob_flush();
flush();
}
echo ']';
exit;
}
Nginx 和 PHP-FPM 配置必须同步调优
光改 PHP 代码没用——Nginx 默认会缓冲响应,PHP-FPM 也可能截断流式输出。
- Nginx 需关闭代理缓冲:
proxy_buffering off;,并设proxy_cache off; - 确保
fastcgi_buffering off;(OpenResty 1.21.4+ / Nginx 1.23.3+ 支持;旧版需用fastcgi_max_temp_file_size 0;+fastcgi_buffers 1 4k;) - PHP-FPM 中
php_admin_value[output_buffering] = Off(不能只靠ini_set) - 避免使用
gzip on,chunked 和 gzip 冲突;如需压缩,改用 Brotli 或客户端解压
更稳的替代方案:用 yield + StreamResponse
ThinkPHP 6.1+ 支持 think\Response\StreamResponse,配合生成器可更干净地管理流式输出,避免手动处理逗号、括号和 flush 时机。
- 把数据源封装为生成器函数,每 yield 一个数组项
- 用
StreamResponse包裹,它会自动处理 chunked 头和分块发送 - 注意:仍需在生成器外手动输出
[和],或改用 NDJSON(每行一个 JSON 对象)规避结构问题
NDJSON 示例更鲁棒(适合前端用 fetch().body.getReader() 流式解析):
public function ndjsonStream()
{
return response()->stream(function () {
header('Content-Type: application/x-ndjson');
$query = Db::table('big_table')->cursor();
foreach ($query as $row) {
echo json_encode($row, JSON_UNESCAPED_UNICODE) . "\n";
ob_flush();
flush();
}
});
}
真正难的不是写几行 echo,而是确认整个链路(PHP → FPM → Nginx → 客户端)都允许未缓冲的 chunked 输出;漏掉任意一环,前端拿到的就是空响应或截断数据。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











