技术知识文章集合TECHNICAL ARCHIVE · 807 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBE

Nginx 伺服大体积 mp4 文件效率问题排查

所属分类
Backend
文件格式
Markdown
原始路径
Backend/Nginx 伺服大体积 mp4 文件效率问题排查
本文目录5 个章节
  1. 问题背景
  2. 回答一(6 赞):sendfile + sendfile_max_chunk + 线程池
  3. 回答二(5 赞):先优化 mp4 文件本身
  4. 回答三(2 赞):multi_accept
  5. 整理者注

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(非影视类):屏幕录制、网课、讲座等。

针对非影视类的压缩手段:

  1. 立体声转单声道:语音为主的音频,用 ffmpeg 转码时复制视频流 + 立体声转 mono。非影视类 mp4 约 1/3 体积是视频、1/3 左声道、1/3 右声道,转 mono 能显著减小体积;
  2. 用 FDK-AAC 重编码音频:比其他 AAC 编码器产出小得多。FDK 的最佳效果需要立体声轨(立体声压得比 mono 还小),用它就保持立体声;
  3. 降低音频比特率/采样率:一般用 -ar 44100,纯语音低保真场景可降到 22050;
  4. 尽量降低视频帧率:屏幕录制中一帧可能 10-60 秒才变一次,用 -r $fps 从 30-60fps 降到 1-5fps,质量不变而体积骤降;
  5. 确保 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 offsendfile on + sendfile_max_chunk 512k静态大文件首选内核态传输,限制分片防独占
aio on(nginx 1.0/1.1 语义)aio threads;(≥1.7.11)线程池避免读盘阻塞事件循环
mp4_max_buffer_size 300m1M-3M 量级300M 远超需要,徒增内存压力
未使用缓存/分片slice 1m + proxy_cache + Range大文件按分片缓存,拖进度条友好
—先压缩源文件(回答二)源文件小了,一切瓶颈都缓解

核心教训:"CPU 负载极低但吞吐上不去"是典型的 I/O 阻塞特征——不是加机器/加带宽能解决的,要从 I/O 模型(sendfile、aio threads)和源文件体积入手。

807 DOCUMENTS · 12 COLLECTIONS
ARCHIVE SEARCH807 篇文章