不能直接在php上传后立即转码,因为ffmpeg转码是cpu密集型操作,耗时长且不可控,易导致php超时、nginx 504、浏览器断连;必须解耦上传与转码,通过redis队列+常驻worker实现异步处理,并辅以文件校验、超时限制、进程守护与监控机制保障稳定性。

为什么不能直接在PHP上传后立即转码
因为音频转码(比如用 ffmpeg)是CPU密集型操作,耗时长、不可控。用户上传一个100MB的WAV文件,exec('ffmpeg -i ...') 可能卡住30秒以上,PHP脚本超时、Nginx 504、浏览器断连全都会发生。必须把“接收上传”和“处理音频”彻底拆开。
用Redis + PHP Worker实现轻量队列
不用引入RabbitMQ或Supervisor等重型组件,小中项目用Redis List就能稳住。核心逻辑:上传接口只存文件、推任务ID进队列;后台常驻Worker拉取并执行转码。
- 上传成功后,生成唯一任务ID(如
uniqid('audio_')),把原始文件存到/upload/raw/,元信息(路径、格式、用户ID)写入json_encode()存进 Redis 的audio:queue列表 - Worker脚本用
while(true)循环,每次调用$redis->lPop('audio:queue'),若返回false则sleep(1)避免空轮询 - 转码前先检查文件是否存在、是否被篡改(
file_exists()+hash_file('sha256', $path)对比上传时记录的哈希) - 调用
ffmpeg时务必加超时和资源限制:exec("timeout 120 ffmpeg -i '$input' -acodec libmp3lame -b:a 128k '$output' 2>&1", $output, $return_code),$return_code !== 0就记日志、发告警
上传接口要防重复、防越界、防伪造
用户可能并发上传同一文件,或修改前端form的 name 字段绕过校验。光靠JS校验没用,后端必须重做。
- 用
$_FILES['audio']['tmp_name']获取临时路径,立刻计算md5_file()作为文件指纹,查库判断是否已存在——存在就跳过转码,直接返回已有MP3地址 - 限制
$_FILES['audio']['size']不超过200MB(在php.ini中设upload_max_filesize = 200M和post_max_size = 200M,否则PHP直接拒收) - 用
finfo_open(FILEINFO_MIME_TYPE)检查真实MIME,拒绝application/octet-stream或text/plain等非音频类型,不信任$_FILES['audio']['type'] - 保存路径拼接必须用
dirname(__FILE__) . '/upload/raw/' . $task_id . '_' . basename($_FILES['audio']['name']),禁止直接拼接用户传的完整文件名
Worker进程怎么不死、怎么不堆积、怎么可监控
没人守着终端跑 php worker.php,它得自己活下来,还要让人知道它卡在哪了。
- 启动时用
pcntl_fork()或简单点:用nohup php worker.php > /var/log/audio_worker.log 2>&1 &启动,再用ps aux | grep worker.php查进程 - 每处理完一个任务,往Redis写个计数器:
$redis->incr('audio:processed'),同时用$redis->llen('audio:queue')记录剩余长度,这两个值可通过HTTP接口暴露给运维看板 - 加心跳机制:Worker每5分钟写一次
$redis->setex('audio:worker:heartbeat', 600, time()),外部脚本定期检查这个key是否存在,不存在就自动拉起新进程 - 不要在Worker里用
die()或未捕获异常退出,所有异常都try/catch并写入日志,确保循环继续
真正麻烦的不是写队列逻辑,而是文件权限、磁盘空间预警、ffmpeg版本差异导致的编码失败、以及多个Worker实例争抢同一个任务——这些不会报错,但会让队列越积越深,得靠日志+监控提前嗅到异常。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











