You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何监听PHP流数据传输,排查Laravel流场景下的慢查询

排查Laravel流传输中的慢数据块间隔问题

你需要监控流传输时数据块的发送间隔,定位因慢查询导致间隔超1分钟的端点,以下是无需在业务代码中逐个修改的解决方案:

方案一:自定义PHP流包装器监控输出流

通过自定义流包装器拦截php://output的写入操作,自动记录每次数据发送的时间戳并计算间隔:

  1. 创建流监控类
<?php

namespace App\Helpers;

class StreamMonitor
{
    private $stream;
    private $lastWriteTime;
    private $requestPath;

    public function __construct()
    {
        $this->lastWriteTime = microtime(true);
        $this->requestPath = request()->path();
    }

    public function stream_open($path, $mode, $options, &$opened_path)
    {
        $this->stream = fopen($path, $mode);
        return $this->stream !== false;
    }

    public function stream_write($data)
    {
        $currentTime = microtime(true);
        $interval = $currentTime - $this->lastWriteTime;

        // 记录间隔超过60秒的情况
        if ($interval > 60) {
            logger()->warning('流数据块间隔过长', [
                'request_path' => $this->requestPath,
                'interval_seconds' => round($interval, 2),
                'timestamp' => date('Y-m-d H:i:s', $currentTime)
            ]);
        }

        $this->lastWriteTime = $currentTime;
        return fwrite($this->stream, $data);
    }

    public function stream_close()
    {
        return fclose($this->stream);
    }

    // 实现其他必要的流方法
    public function stream_read($count) { return fread($this->stream, $count); }
    public function stream_eof() { return feof($this->stream); }
    public function stream_stat() { return fstat($this->stream); }
}
  1. 在服务提供者中注册包装器
    在AppServiceProvider的boot方法中添加:
if (!in_array('monitor', stream_get_wrappers())) {
    stream_wrapper_register('monitor', \App\Helpers\StreamMonitor::class);
}
  1. 统一替换流打开方式
    将业务代码中所有fopen('php://output', 'w')替换为fopen('monitor://php://output', 'w'),如果想彻底避免修改业务代码,可以结合Laravel响应宏统一处理。

方案二:Laravel全局中间件包装流响应

通过中间件拦截所有StreamResponse,自动包装其回调函数添加监控逻辑,完全无需修改业务代码:

  1. 创建中间件
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\StreamedResponse;

class MonitorStreamResponse
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);

        if ($response instanceof StreamedResponse) {
            $originalCallback = $response->getCallback();
            $lastWriteTime = microtime(true);
            $requestPath = $request->path();

            $wrappedCallback = function () use ($originalCallback, &$lastWriteTime, $requestPath) {
                // 重写fputcsv函数实现监控
                $originalFputcsv = 'fputcsv';
                $GLOBALS['fputcsv'] = function ($stream, $fields) use (&$lastWriteTime, $requestPath) {
                    $currentTime = microtime(true);
                    $interval = $currentTime - $lastWriteTime;

                    if ($interval > 60) {
                        logger()->warning('流数据块间隔过长', [
                            'request_path' => $requestPath,
                            'interval_seconds' => round($interval, 2),
                            'timestamp' => date('Y-m-d H:i:s', $currentTime)
                        ]);
                    }

                    $lastWriteTime = $currentTime;
                    return $originalFputcsv($stream, $fields);
                };

                // 执行原回调逻辑
                $originalCallback();

                // 恢复原函数
                $GLOBALS['fputcsv'] = $originalFputcsv;
            };

            $response->setCallback($wrappedCallback);
        }

        return $response;
    }
}
  1. 注册全局中间件
    在app/Http/Kernel.php的$middleware数组中添加:
\App\Http\Middleware\MonitorStreamResponse::class,

关键注意事项

  • 日志中务必记录请求路径,方便快速定位具体端点
  • 可扩展监控逻辑,添加数据块大小、总耗时等维度的记录
  • 高流量场景建议添加日志采样机制,避免日志量过大
  • 测试时可在chunk回调中加入sleep(61)模拟慢查询,验证监控是否生效

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 00:50:12