Nginx 伺服大体积 mp4 文件效率问题排查
Category(分类): Backend Status: 已读
原文:NGINX Serving Large mp4 Files extremely inefficiently(ServerFault 问答,2015)。 问答内容经 StackExchange API 核实整理。
问题背景
环境:
- nginx/1.0.15,CentOS 6.6
- Intel Atom C2750 @ 2.40GHz(8 核)、32GB 内存、5 × 6TB 7200RPM(RAID 10)
- 服务器为 1Gbit/s 带宽,专门用于伺服静态 mp4 文件
现象:
- 带宽跑满 400-500 mbit/s 后就到瓶颈,始终用不满 1Gbit/s;
- 约 100 个并发连接后服务质量开始下降,速度急剧变慢(此时带宽仍有 50% 富余);
- 单个 mp4 文件通常 400-1200MB(平均 700MB);
- 服务器负载从未超过 0.3——CPU 很闲,问题出在 I/O 模型上。
提问者当时的配置(有问题的版本):
# nginx.conf 关键部分
aio on;
sendfile off;
tcp_nopush off;
tcp_nodelay on;
output_buffers 1 3m;
open_file_cache max=10000 inactive=5m;
open_file_cache_valid 2m;
open_file_cache_min_uses 1;
open_file_cache_errors on;
# server 配置
server {
listen 80 default_server sndbuf=32k;
location /Videos/ {
root /home;
gzip off;
gzip_static off;
mp4;
mp4_max_buffer_size 300m; # 注意:过大
}
}
回答一(6 赞):sendfile + sendfile_max_chunk + 线程池
推荐的起步配置:
events {
accept_mutex off;
}
access_log off;
sendfile on;
sendfile_max_chunk 512k;
- 关闭日志和 accept_mutex;
- 开启 sendfile,并设置
sendfile_max_chunk 512k,防止单个连接长时间占用 worker; - 提问者恰恰是
sendfile off+aio on的组合,在这个场景下是反效果。
更重要的:Nginx 1.7.11+ 的线程池特性对该场景非常有效:
location / {
root /home;
aio threads;
mp4;
}
官方测试样例中,线程池把带宽从 1Gbps 提升到了 9Gbps——九倍!你只有 1Gbps,但它能让你的带宽被完全利用。
详见官方博客:Thread Pools Boost Performance 9x!
原理:没有线程池时,nginx 的事件循环 worker 会被慢速磁盘读取阻塞——一个 worker 等着读盘时,它处理的所有其他连接都得排队。Atom CPU + 7200RPM 机械盘的场景下,这正好解释了"负载很低但吞吐上不去、连接一多速度雪崩"的现象。线程池把阻塞读交给独立线程,worker 继续处理事件。
注意:提问者使用的 nginx 1.0.15(2012 年)远早于 1.7.11(2015 年发布线程池),升级版本是前提。
回答二(5 赞):先优化 mp4 文件本身
在折腾 Nginx/Apache 调参之前,先看看 mp4 文件本身——那里通常有巨大的优化空间。将视频分为两类:
- cinematic(影视类):每一帧变化都必要,不能降帧率;
- non-cinematic(非影视类):屏幕录制、网课、讲座等。
针对非影视类的压缩手段:
- 立体声转单声道:语音为主的音频,用 ffmpeg 转码时复制视频流 + 立体声转 mono。非影视类 mp4 约 1/3 体积是视频、1/3 左声道、1/3 右声道,转 mono 能显著减小体积;
- 用 FDK-AAC 重编码音频:比其他 AAC 编码器产出小得多。FDK 的最佳效果需要立体声轨(立体声压得比 mono 还小),用它就保持立体声;
- 降低音频比特率/采样率:一般用
-ar 44100,纯语音低保真场景可降到 22050; - 尽量降低视频帧率:屏幕录制中一帧可能 10-60 秒才变一次,用
-r $fps从 30-60fps 降到 1-5fps,质量不变而体积骤降; - 确保 faststart(moov atom 前置):让文件可以被流式播放而不是必须整体下载。
非影视类文件常常可以从 1GB 压到 10-20MB。
FDK 音频参数参考:
-c:a libfdk_aac -profile:a aac_he_v2 -afterburner 1 -signaling explicit_sbr -vbr 5 -ac 2 -ar 44100
回答三(2 赞):multi_accept
events {
worker_connections 768;
multi_accept on;
}
该回答者开启 multi_accept 后解决了视频播放到一半停止、访客无法看完后半段的问题。
整理者注
结合本站另一篇《Nginx 伺服 mp4 视频流的配置参考》中的现代配置,本案例的要点可以归纳为:
| 问题配置 | 建议配置 | 说明 |
|---|---|---|
sendfile off | sendfile on + sendfile_max_chunk 512k | 静态大文件首选内核态传输,限制分片防独占 |
aio on(nginx 1.0/1.1 语义) | aio threads;(≥1.7.11) | 线程池避免读盘阻塞事件循环 |
mp4_max_buffer_size 300m | 1M-3M 量级 | 300M 远超需要,徒增内存压力 |
| 未使用缓存/分片 | slice 1m + proxy_cache + Range | 大文件按分片缓存,拖进度条友好 |
| — | 先压缩源文件(回答二) | 源文件小了,一切瓶颈都缓解 |
核心教训:"CPU 负载极低但吞吐上不去"是典型的 I/O 阻塞特征——不是加机器/加带宽能解决的,要从 I/O 模型(sendfile、aio threads)和源文件体积入手。