Laravel 8中长时间运行Ajax调用导致站点阻塞问题求助
问题核心原因
你遇到的同端口(localhost:8000)页面全部冻结、session()->save() 无效的问题,本质是两个原因叠加:
localhost:8000是执行php artisan serve启动的PHP内置开发服务器,该服务器为单线程阻塞设计,同一时间只能处理一个请求,只要有一个请求未执行结束,同端口下所有其他请求都会排队等待,和session锁完全无关,这也是你提前关闭session依然无效的核心原因。- 你将耗时可达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处理:
生产环境建议用Supervisor配置队列进程守护,避免进程意外退出导致任务中断。php artisan queue:work
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
相关产品推荐
相关产品推荐

