php 不应直接调用 ffmpeg 处理视频加水印,因其并发下会导致 cpu/内存雪崩、i/o 瓶颈及资源泄漏;正确方案是 php 仅调度任务至 go/rust 编写的专用水印服务,并采用 tmpfs、jpeg 水印图、-threads 1 等优化措施。

PHP 本身不直接处理视频帧,多路并发加水印必然触发 CPU 和内存雪崩——这不是配置调优能解决的问题,而是架构选型错误。
为什么 exec('ffmpeg -i ... -vf "drawtext=..."') 在并发下会失控
每个 exec() 调用都会 fork 一个独立的 FFmpeg 进程,而 FFmpeg 是 CPU 密集型任务,单路 1080p 加水印通常吃满 1 个逻辑核。5 路并发 = 至少 5 核持续满载,若服务器只有 4 核,就会触发严重上下文切换和队列等待。
- PHP 进程本身不释放 CPU,只是等待
exec()返回,期间仍占 FPM worker,阻塞后续请求 - FFmpeg 启动开销大(解复用器初始化、字体渲染上下文加载),每路重复执行,浪费可观资源
- 水印位置/文字若动态生成(如含时间戳),
drawtext需实时计算布局,进一步放大 CPU 压力 - 未设置
-threads 1时,FFmpeg 可能自行启用多线程,导致单路任务抢占更多核
内存泄漏比 CPU 更隐蔽:tmpfile + PHP-FPM 共同作祟
常见写法是先 file_put_contents('/tmp/in.mp4', $raw),再传给 FFmpeg;转码完再 unlink()。问题在于:
-
file_put_contents()写入大视频时,若未流式处理,PHP 会把整个二进制块暂存于内存(尤其当memory_limit被设高后) - FPM worker 生命周期内,临时文件未及时
unlink(),或并发写入同一路径导致覆盖/残留,/tmp 占满后 FFmpeg 报Cannot open file '/tmp/out.mp4': Permission denied - PHP 的
proc_open()若未显式关闭 stdin/stdout/stderr 流,子进程句柄泄露,Linux 的ulimit -n快速耗尽,后续所有exec()直接失败
真正可行的资源隔离方案:别让 PHP 承担转码职责
PHP 唯一该做的事是调度与状态透出。把水印逻辑下沉到专用服务,才能控制资源边界:
- 用 Go 或 Rust 写轻量水印服务,监听 Redis 队列,每实例限定最大并发数(如
max_concurrent: 3),超限自动拒绝 - PHP 仅推送任务 JSON 到
watermark:queue,包含源 S3 URL、水印模板 ID、目标路径,不碰任何视频二进制 - FFmpeg 命令统一预编译:用
-vf "movie=/path/to/logo.png[logo]; [in][logo]overlay=10:10"替代 drawtext,避免字体解析开销 - 强制所有 FFmpeg 实例使用
-threads 1 -preset ultrafast -tune zerolatency,牺牲少量压缩率换取确定性资源占用
监控盲区:你以为在看 CPU,其实瓶颈在磁盘 I/O
当多路 FFmpeg 同时读取不同视频文件、写入不同输出路径时,随机小文件读写会让 SATA SSD 的 IOPS 瞬间打满。现象是:top 显示 CPU 不高,但 iostat -x 1 显示 %util 持续 100%,await > 50ms。
- 规避方式:所有输入视频必须提前预加载到内存盘(
tmpfs),如mount -t tmpfs -o size=16g tmpfs /mnt/ramdisk - 输出也写入
/mnt/ramdisk,完成后由异步脚本rsync --remove-source-files转存到持久存储 - 绝对不要让 FFmpeg 直接读写 NFS 或 CephFS,元数据延迟会放大整体耗时
最常被忽略的一点:水印图若是 PNG 且含 alpha 通道,FFmpeg 默认用软件混合,CPU 占用翻倍;换成无透明度的 JPEG 水印图,同一台机器并发路数可提升 2.3 倍——这种细节,压测时根本不会暴露,只会在凌晨三点的生产环境突然爆发。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











