php上传音频后不能直接用exec()转码,因为web服务器超时限制会导致请求卡死或504错误;正确做法是上传后立即返回task_id,用redis队列+cli worker异步处理,确保秒级响应、幂等性与失败重试。

PHP上传音频后为什么不能直接用exec()转码
因为Web服务器(如Apache或Nginx)对PHP进程有超时限制(默认30秒),而音频转码(比如ffmpeg处理长MP3)常耗时几十秒甚至几分钟,直接在请求中调用exec('ffmpeg -i ...')会导致请求卡死、504 Gateway Timeout或用户看到白屏。
真正可行的路径是:上传立即返回响应,把转码任务“扔给后台”,由独立进程持续处理。
- 别在
$_POST接收后立刻跑shell_exec()——这是最常见卡顿源头 - 不要依赖
set_time_limit(0)硬扛——它治标不治本,还可能拖垮整个PHP-FPM worker - 上传接口必须做到「秒级响应」,哪怕只是返回
{"task_id":"abc123"}
用Redis+CLI脚本实现轻量级异步队列
不用引入RabbitMQ或Supervisor,用PHP原生能搞定:上传端写任务到Redis,后台用php /path/to/worker.php轮询执行。关键在于解耦和幂等性。
示例流程:
- 前端上传
audio.mp3→ PHP保存到/tmp/uploads/xxx.mp3→ 写入Redis:LPUSH audio_tasks {"file":"/tmp/uploads/xxx.mp3","format":"wav","id":"t_7f2a"} -
worker.php每2秒BRPOP audio_tasks 1取任务,执行exec("ffmpeg -i {$task['file']} -ar 16000 {$task['file']}.wav 2>&1", $output, $return_code) - 转码成功后,改名、清理临时文件、更新数据库状态字段
status = 'done'
注意:exec()必须捕获$return_code——ffmpeg失败时返回非0值,不检查就认为成功,会导致静音文件被当作正常结果。
如何防止同一音频被重复处理或丢失
Redis队列本身不保证消息不丢,但加两层防护就能稳住:
- 上传时生成唯一
task_id(用uniqid('', true)而非time()),并先SETNX task:t_7f2a processing,避免并发上传同文件触发多次任务 -
worker.php拿到任务后,立刻SETEX task:t_7f2a 300 "running"(5分钟过期),防止worker崩溃导致任务卡死 - 转码完成后
DEL task:t_7f2a,并用HMSET task_result:t_7f2a status done path /out/xxx.wav存结果,供API查询
漏掉SETEX超时保护,某个worker挂掉后任务就永远没人处理;漏掉SETNX,用户狂点上传按钮会塞进一堆重复任务。
前端怎么知道转码好了
别让前端轮询/api/task-status?id=t_7f2a查数据库——高并发下DB压力大。直接查Redis更轻量:
// api/task-status.php
$task_id = $_GET['id'] ?? '';
$result = $redis->hGetAll('task_result:' . $task_id);
if (empty($result)) {
http_response_code(202); // Accepted,还没好
echo json_encode(['status' => 'processing']);
} else {
echo json_encode($result); // {"status":"done","path":"/out/xxx.wav"}
}
这里hGetAll()比SELECT * FROM tasks WHERE id = ?快一个数量级,且无锁表风险。如果业务要求强一致性(比如转码后必须立刻同步到CDN),那就在worker.php里补一句exec('aws s3 cp ' . $wav_path . ' s3://bucket/...'),别指望前端自己去拉。
真正的难点不在“怎么启动异步”,而在“怎么确保每个环节都可追溯、可中断、可重试”——比如ffmpeg因内存不足崩溃,$return_code是137,你得把它记进日志并触发告警,而不是当成普通失败默默吞掉。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











