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

Laravel 8中长时间运行Ajax调用导致站点阻塞问题求助

问题核心原因

你遇到的同端口(localhost:8000)页面全部冻结、session()->save() 无效的问题,本质是两个原因叠加:

  1. localhost:8000 是执行 php artisan serve 启动的PHP内置开发服务器,该服务器为单线程阻塞设计,同一时间只能处理一个请求,只要有一个请求未执行结束,同端口下所有其他请求都会排队等待,和session锁完全无关,这也是你提前关闭session依然无效的核心原因。
  2. 你将耗时可达10分钟的ffmpeg处理逻辑直接放在HTTP请求内同步执行,即使换用Nginx+PHP-FPM的多进程环境,这种写法也会占用PHP-FPM工作进程,并发量高时会直接耗尽进程池导致所有请求卡住,同时进程和HTTP会话绑定,用户关闭浏览器时进程可能收到SIGHUP信号被中断。
可落地的完整解决方案

1. 替换本地测试运行环境

立刻停止使用 php artisan serve 测试长耗时任务,本地搭建Nginx+PHP-FPM环境(或用Laravel Valet、Docker等多进程运行环境),从环境层面解决单请求阻塞所有页面的问题。

2. 用Laravel队列解耦HTTP请求和视频处理逻辑

这是生产环境最稳定的实现方式,完全满足「后台不中断运行、前端不阻塞」的需求:

  • 首先修改.env配置,将队列驱动改为非同步类型:
    QUEUE_CONNECTION=database
    # 性能要求高可以替换为redis
    
  • 执行队列相关初始化命令,生成队列表并执行迁移:
    php artisan queue:table
    php artisan migrate
    
  • 生成视频处理任务类:
    php artisan make:job ProcessVideo
    
  • 在任务类中编写ffmpeg处理逻辑,确保进程完全脱离当前会话:
    // app/Jobs/ProcessVideo.php
    <?php
    
    namespace App\Jobs;
    
    use App\Models\Video;
    use Illuminate\Bus\Queueable;
    use Illuminate\Contracts\Queue\ShouldQueue;
    use Illuminate\Foundation\Bus\Dispatchable;
    use Illuminate\Queue\InteractsWithQueue;
    use Illuminate\Queue\SerializesModels;
    
    class ProcessVideo implements ShouldQueue
    {
        use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
    
        public $timeout = 1200; // 超时设为20分钟,覆盖最长视频处理时长
    
        public function __construct(public int $videoId)
        {}
    
        public function handle()
        {
            $video = Video::find($this->videoId);
            // 拼接ffmpeg命令,重定向输出、后台运行,返回进程PID
            $cmd = sprintf(
                'ffmpeg -i %s %s > /dev/null 2>&1 & echo $!',
                escapeshellarg($video->source_path),
                escapeshellarg($video->processed_path)
            );
            $pid = trim(shell_exec($cmd));
            
            // 轮询检查进程是否运行结束
            while (file_exists('/proc/' . $pid)) {
                sleep(2);
            }
    
            // 更新视频处理状态
            $video->update(['status' => 'processed']);
        }
    }
    
  • 改造原Ajax调用的路由逻辑,只负责派发任务、立即返回响应,不执行耗时操作:
    Route::post('/video/process', function (\Illuminate\Http\Request $request) {
        $videoId = $request->input('video_id');
        // 派发队列任务,立即返回不阻塞
        \App\Jobs\ProcessVideo::dispatch($videoId);
        // 先把视频状态更新为处理中
        \App\Models\Video::where('id', $videoId)->update(['status' => 'processing']);
        return response()->json(['code' => 0, 'video_id' => $videoId]);
    });
    
  • 启动队列工作进程,该进程为独立CLI进程,和Web服务、用户浏览器会话完全无关,关闭浏览器、用户断开连接都不会中断ffmpeg处理:
    php artisan queue:work
    
    生产环境建议用Supervisor配置队列进程守护,避免进程意外退出导致任务中断。

3. 实现前端处理完成自动重载视频

先写一个轻量轮询接口,只返回视频处理状态,无任何耗时逻辑:

Route::get('/video/status/{id}', function (int $id) {
    return response()->json([
        'status' => \App\Models\Video::where('id', $id)->value('status')
    ]);
});

前端发起处理请求拿到video_id后,每隔3秒轮询一次该接口,当返回status=processed时,直接替换视频标签的src地址加载处理后的视频即可,不需要整页刷新。

临时兼容方案(不推荐生产使用)

如果你暂时不想接入队列,在Nginx+PHP-FPM环境下,可以在调用shell_exec前执行以下操作,避免请求阻塞:

public function processVideo(\Illuminate\Http\Request $request)
{
    // 提前关闭session写锁
    session()->save();
    // 关闭当前HTTP连接,提前发送响应给客户端
    fastcgi_finish_request();
    // 之后再执行ffmpeg逻辑,注意命令必须加后台运行、输出重定向参数
    shell_exec('ffmpeg ... > /dev/null 2>&1 &');
}

该方案稳定性远低于队列实现,高并发下依然会出现资源占用问题,仅适合临时测试使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:51:46