Laravel PHP上传录制视频至AWS S3速度慢且易失败问题咨询
视频上传优化方案(Laravel + AWS S3)
问题根源分析
当前代码存在几个核心问题导致上传慢、易失败:
- 转码和上传同步执行,单个请求耗时过长,容易触发超时
- 用
file_get_contents把整个视频加载到内存,大文件会耗尽服务器内存导致上传失败 - 未利用S3的分块上传和传输加速能力
一、AWS Transfer Acceleration(传输加速)
需要AWS端配置 + 代码配合:
- AWS控制台配置:进入目标S3桶的「属性」页面,找到「传输加速」选项,开启该功能(按指引完成域名所有权验证即可)
- Laravel代码配置:修改
config/filesystems.php中的S3驱动配置,添加加速端点:
's3' => [ // 保留原有key/secret/region/bucket等配置 'endpoint' => 'https://你的桶名.s3-accelerate.amazonaws.com', 'use_path_style_endpoint' => false, ],
开启后,S3会通过CloudFront全球边缘节点加速上传,减少跨区域传输延迟。
二、Multipart Upload(分块上传)
Laravel原生支持自动分块,只需调整代码写法:
当前代码用file_get_contents('output.mp4')读取整个文件,改成用文件流或直接传入文件路径,Laravel的S3驱动会自动对10MB以上的文件进行分块上传(可通过multipart_threshold参数调整阈值):
优化上传代码片段
// 替换原来的上传代码 $file_folder = 'uploads/video/'; // 用putFileAs直接传入文件路径,自动处理分块 $store = Storage::disk('s3')->putFileAs( $file_folder, public_path('output.mp4'), $file_name );
这种方式不会把整个文件加载到内存,降低服务器内存占用,同时分块上传失败后可以重试单个块,提升成功率。
三、核心流程优化(解决超时问题)
把转码+上传放到Laravel队列异步处理,避免单个请求长时间阻塞:
1. 创建队列任务
执行命令生成任务类:
php artisan make:job ProcessVideoUpload
2. 编写任务逻辑
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Storage; use FFMpeg\FFMpeg; use FFMpeg\Format\Video\X264; class ProcessVideoUpload implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $tempFilePath; protected $oldFile; protected $fileName; public function __construct($tempFilePath, $oldFile = null) { $this->tempFilePath = $tempFilePath; $this->oldFile = $oldFile; $this->fileName = str_replace('/', '', \Hash::make(time())) . '.mp4'; } public function handle() { try { // FFmpeg转码(可选:添加硬件加速参数加快转码) $ffmpeg = FFMpeg::create(); $video = $ffmpeg->open($this->tempFilePath); $format = new X264(); // 服务器有NVIDIA显卡的话,启用硬件加速大幅提升转码速度 // $format->setAdditionalParameters(['-c:v', 'h264_nvenc']); $outputPath = storage_path('app/temp/' . $this->fileName); $video->save($format, $outputPath); // 分块上传到S3 $fileFolder = 'uploads/video/'; Storage::disk('s3')->putFileAs($fileFolder, $outputPath, $this->fileName); // 清理临时文件 unlink($outputPath); unlink($this->tempFilePath); // 删除旧文件(如果存在) if ($this->oldFile && Storage::disk('s3')->exists($fileFolder . basename($this->oldFile))) { Storage::disk('s3')->delete($fileFolder . basename($this->oldFile)); } // 这里可以添加通知逻辑(比如用websocket告知前端上传完成) } catch (\Exception $e) { \Log::error('视频处理失败: ' . $e->getMessage() . ' 行号: ' . $e->getLine()); // 清理临时文件 if (file_exists($this->tempFilePath)) unlink($this->tempFilePath); if (isset($outputPath) && file_exists($outputPath)) unlink($outputPath); throw $e; // 让队列自动重试或标记失败 } } }
3. 修改控制器方法
public function video_upload(Request $request) { $response = [ 'status' => false, 'data' => [] ]; $responseCode = 200; $validator = Validator::make($request->all(), [ 'file' => 'required|file|mimes:mp4,webm,ogg', ]); if ($validator->fails()) { $response['data']['validator'] = $validator->errors(); return response()->json($response, $responseCode); } try { // 先把前端上传的文件存到本地临时目录 $tempFile = $request->file('file'); $tempFilePath = $tempFile->store('temp', 'local'); $fullTempPath = storage_path('app/' . $tempFilePath); // 分发队列任务,异步处理转码和上传 ProcessVideoUpload::dispatch($fullTempPath, $request->old_file); $response['status'] = true; $response['data'] = '视频正在后台处理,完成后会通知你'; // 或返回任务ID供前端查询状态 } catch (\Exception $e) { $response['data']['message'] = $e->getMessage() . ' 行号: ' . $e->getLine(); $responseCode = 400; } return response()->json($response, $responseCode); }
四、额外优化建议
- FFmpeg转码优化:调整CRF值(比如设为28,平衡画质和速度)、启用硬件加速(如NVENC/QSV),大幅减少转码时间
- 前端预处理:录制时直接生成MP4格式,或前端用JS压缩视频后再上传,减少后端压力
- 日志完善:添加详细的转码、上传日志,方便排查失败原因
内容的提问来源于stack exchange,提问作者Sara
相关产品推荐
相关产品推荐

