上一篇 下一篇 分享链接 返回 返回顶部

为什么香港服务器(i9-11900K、64GB内存、1TB NVMe SSD)在高并发短视频平台中会面临“读写操作与编码流程之间的I/O瓶颈”导致的性能急剧下降?

发布人:Minchunlin 发布时间:2025-11-18 10:12 阅读量:646


我是负责一家香港服务器租用托管公司旗下、为跨境短视频/直播平台服务的运维工程师。客户在香港机房租用了一台物理服务器(Intel i9‑11900K,8核16线程,主频最高 5.2GHz;64 GB DDR4 内存;1 TB 企业级 NVMe SSD;网络为 BGP 多线 + CN2 优化,目标是为短视频平台提供本地化 “快速启动、低延迟、可弹性扩展” 的服务)。

业务场景如下:平台承载“香港及东南亚用户访问,短视频上传/转码/浏览 + 短直播回放”场景,典型峰值为每日 PV ~20 万+、日活用户 ~5万,短视频上传量约每日 1000 本、回放并发用户在促销期间可达 2000+,峰值时短视频服务器在短时间内承受了大量 编码任务(转码、封面截帧)+ 存储读写(写入上传的视频、读取转码结果、用户浏览缓存) 的复合负载。

一、项目背景 & 故障触发

在一次促销活动期间,客户报告:“视频上传后转码速度忽然变慢,平台用户浏览短片卡顿、延时高、偶发错误”。现场我登陆监控面板发现:服务器 CPU 使用率还剩余约 40%,内存使用未满,网络带宽也有富余,但 磁盘 I/O 延迟飙升,大量短片转码任务在等待写操作或读取临时文件,导致整体性能急剧下降。

这个现象说明:虽然主机硬件看上去足够(i9 + NVMe + 64GB 内存),但在编码流程 + 存储读写混合场景下,“读/写操作与编码流程之间”发生了 I/O 流量竞赛或队列深度饱和,形成了 I/O 瓶颈,反而制约了整体系统性能。

原因大致如下:

  • 短视频上传触发写入,随后转码流程大量读取原始视频,再写入转码结果、再读取供用户浏览,读写交替频繁。
  • 编码流程本质是 CPU +内存 + I/O混合型任务,若 I/O 子系统跟不上,则 CPU 空闲但等待 I/O,从而整体吞吐下降。
  • NVMe 虽快,但仍有队列深度、调度器、文件系统配置、读取模式、并发冲突、缓存失效等因素影响。
  • 在 CentOS 8 默认配置下,I/O 调度、挂载选项、缓存机制、NUMA/中断/队列亲和性等并未优化。
  • 在高并发场景下,多个任务同时对 SSD 提交大块顺序写、小块随机读、并发写临时文件、用户浏览文件读混合,造成 I/O 延迟上升、带宽饱和、队列瓶颈。

因此,我决定按照以下流程进行诊断 & 调优。

二、性能分析流程

我首先从现场监控 +日志 +工具入手,逐步确认瓶颈是否真的属于 I/O 子系统,并进一步定位是哪一种 I/O(读取、写入、随机、小块、队列深度、缓存未命中等)。具体流程如下:

2.1 收集基础指标

我在服务器上运行以下命令收集关键指标:

# 安装基本工具
yum install -y sysstat iostat dstat nvme-cli fio

# 采样 I/O 情况
iostat -dx 5 10
vmstat 5 10
dstat -tdlmn 5 10

# 查看 NVMe 设备情况
nvme list
nvme smart-log /dev/nvme0n1   # 假设为该 SSD

通过 iostat -dx 我观察到 NVMe 设备 /dev/nvme0n1 的如下异常现象(伪代表):

设备 rrqm/s wrqm/s r/s w/s rMB/s wMB/s avgrq‑sz avgqu‑sz await(ms) svctm(ms) util(%)
nvme0n1 0.00 0.00 1500 8000 60 320 42.5 25.3 12.8 2.1 98.5
  • util(%) 几近 100%,说明设备几近饱和。
  • avgqu‑sz 达 25.3,说明 I/O 请求队列堆积。
  • await(ms) 达 12.8 ms,这在 NVMe 环境下属于偏高(正常应该在子毫秒或 1 ms 级别)。
  • CPU 仍有大量空闲,但 I/O 阻塞严重。

另外,用 dstat -tdlmn 观察了读写请求模式:在转码高峰时段,写入速度波动在 300‑400 MB/s,读取速度不稳定,有大量小块读操作,随机 I/O 增多。结合应用日志发现:转码完成后会将输出写入同一 SSD,然后浏览访问又读取这些转码结果,这就造成写 → 读 → 写 → 读的交替负载。

2.2 分析瓶颈类型

根据收集的指标以及场景特点,我判断瓶颈可能由以下因素导致:

  • 队列深度饱和:avgqu‑sz 25.3 表示 SSD 前端 I/O 请求正在排队。
  • I/O 调度器不适合 NVMe:默认在 CentOS 8 上可能为 mq-deadline 或 cfq,未针对 NVMe 最优。 
  • 文件系统 & 挂载选项未优化:例如 noatime、commit=、inode64、barrier=0 等挂载参数未设。 
  • CPU/内存与 I/O 未协同:虽然 CPU 有余,但 I/O 读写延迟高,说明编码 flow 等任务在等待 I/O,整体吞吐降低。
  • 多任务并发竞争 SSD资源:上传、编码、用户访问同时作用于同一物理 SSD,混合了顺序大块写、小块随机读,SSD 内部可能发生写放大、GC、队列资源争抢。
  • 缓存、预取机制 不充分:可能读请求无法命中 page cache 或者写延迟未被缓冲,造成同步写等待。参考 Linux page cache 机制说明。 
  • NUMA/中断亲和性 不佳:i9‑11900K 虽为单 socket,但仍有 PCIe 通道、中断处理、队列亲和性的问题,在高并发 I/O 时若未优化,也会有影响。

因此,问题基本明确:核心瓶颈在于 NVMe SSD 的 I/O 子系统,而不是 CPU/内存/网络。下一步就是“对症下药”——基于这个指标制定调优方案。

三、调优方案设计与执行(六步法)

下面按我在现场执行的六个关键步骤列出,配合代码示例、配置清单、执行细则。每一步都是“我怎么做”+“为什么这么做”+“效果如何”这种运维现场风格。

步骤 1:选择合适的文件系统 + 挂载选项

文件系统和挂载参数会影响 I/O 性能。对于高并发读写 +混合型负载,合理选择可减少元数据操作、减少缓存失效、减少写放大、优化读操作。参考 Linux 调优指南:建议使用 XFS 或 ext4,挂载时加 noatime、nodiratime、调整 commit 间隔等。 

我在现场的做法

确认当前文件系统情况:

df -Th /mnt/media
blkid /dev/nvme0n1p1
mount | grep "/mnt/media"

我选择将存储挂载为 XFS(因为预计是大块写 +读取场景,XFS 在大文件和并发场景表现较好):

mkfs.xfs -f /dev/nvme0n1p1
mkdir /mnt/media

挂载时加挂载选项(在 /etc/fstab 中加入):

/dev/nvme0n1p1  /mnt/media  xfs  defaults,noatime,nodiratime,attr2,inode64,logbufs=8,logbsize=256k  0 0

说明:

noatime,nodiratime:避免每次访问文件都更新访问时间 metadata。

attr2,inode64:增强 inode 支持大文件/大容量。

logbufs=8,logbsize=256k:调整 XFS 日志缓冲,提高并发写日志能力。

重启挂载或使用 mount -o remount,... 立即生效:

mount -o remount,noatime,nodiratime /mnt/media

执行后观察

挂载变更后,在低峰期我用了 fio 做一个简单读写测试,命令如下:

fio --name=seqwrite --filename=/mnt/media/testfile --bs=1m --iodepth=32 --size=10G --rw=write --direct=1 --numjobs=4
fio --name=randread --filename=/mnt/media/testfile --bs=4k --iodepth=64 --size=10G --rw=randread --direct=1 --numjobs=8

结果显示:顺序写吞吐提升约 +10%,随机读延迟降低约 20%。虽然不是业务真实状态,但体现了优化方向是正确的。

步骤 2:优化 I/O 调度器与队列深度

默认的 I/O 调度器与队列深度未必适合 NVMe。如果继续使用旧的 CFQ 或 deadline,反而在 NVMe 的高并发环境中造成不必要的调度开销。对于 NVMe 一般推荐使用 none 或 mq-deadline 或 bfq,视场景不同。 

同时,队列深度(/sys/block/nvme0n1/queue/nr_requests、read_ahead_kb 等)也应调优。

我在现场的做法

查看当前调度器与队列深度:

cat /sys/block/nvme0n1/queue/scheduler
cat /sys/block/nvme0n1/queue/nr_requests
cat /sys/block/nvme0n1/queue/read_ahead_kb

因为这是纯 NVMe 且大量并发 I/O,对调度器负载要求高,我将调度器切换为 none(代表尽量少调度开销):

echo none > /sys/block/nvme0n1/queue/scheduler

为了重启后生效,我在 /etc/default/grub(CentOS 8中)增加 kernel 参数:

GRUB_CMDLINE_LINUX="elevator=none"

然后 grub2-mkconfig -o /boot/grub2/grub.cfg,然后重启。

调整队列深度及读预取:

echo 512 > /sys/block/nvme0n1/queue/nr_requests
echo 16384 > /sys/block/nvme0n1/queue/read_ahead_kb

说明:增加队列深度允许更多并发 I/O 请求挂起,提高吞吐;增加 read ahead 对顺序读有利。

针对 NUMA & 多核,我也将 nvme 中断绑定至 CPU 核心(下面步骤也包含在下一部分)。

执行后观察

再次使用 iostat -dx 5 10 监控,发现 avgqu‑sz 从 ~25 降到 ~10,await 从 12.8 ms 降到 ~5‑6 ms,util(%) 稍降但仍高,说明压力虽仍存在,但队列饱和情况已有缓解。这也说明调度器与队列调优是关键环节。

步骤 3:NUMA/中断亲和性与 CPU 资源协同

在高吞吐 I/O 环境里,如果中断、I/O 队列挂起在非本地 CPU 上(与内存或 PCIe 通道不在同一 NUMA node)可能造成延迟。还可能编码任务和 I/O 争用 CPU 核心。虽然 i9‑11900K 是单 socket,但依然有 PCIe 通道、中断分布、CPU 核栈分配的问题。我将这个作为 “系统协同” 优化项。

我在现场的做法

使用 lscpu、numactl --hardware 查看 NUMA 架构(在该平台通常只有 NUMA node 0,但仍检查)。

lscpu | grep NUMA
numactl --hardware

查看 NVMe 中断(IRQ)绑定情况:

cat /proc/interrupts | grep nvme0

假设看到 nvme0n1 的中断编号为 40,而中断绑定在 CPU 0 上。

我选择将该中断绑定到专用 CPU 核(例如核 6),避免与编码线程争用:

echo 64 > /proc/irq/40/smp_affinity   # 64→bit mask,对应 CPU6

若重启后生效,还可在 /etc/rc.d/rc.local 里加入相关绑定脚本。

对编码流程(比如 ffmpeg 转码进程)也设定 CPU 亲和性。例如,我配置 encoding.sh 脚本:

#!/bin/bash
taskset -c 0-3 ffmpeg -i input.mp4 -c:v libx264 ... output.mp4

这样编码任务固定在 CPU 核 0‑3,I/O 中断在 CPU6,避免争用。

在系统启动脚本里禁用 CPU 频率调节器进入低 power state(C‑states)以降低延迟(特别在 NVMe 高并发时建议禁用深度 C‑state)。例如,在 BIOS 禁用 C6/C7 或在内核参数里加 intel_idle.max_cstate=1。

执行后观察

监控 CPU 各核负载发现 CPU0‑3 全用于编码,CPU6 中断负载上升但独立于编码,其他核空闲。整体编码延时稍微缩短, I/O 等待时间减少了 ~10%。虽然改进幅度不是爆炸性,但在大规模并发场景中“减少争用”是累积效应。

步骤 4:提升缓存利用 & 减少同步 I/O

在短视频平台中,上传 → 写入 →转码读取 →写出 → 浏览读取 是典型流程。如果每一步都同步等待磁盘完成写/读,就会大幅增加延迟。因此,合理利用 Linux 的 page‐cache、异步 I/O、减少 metadata 操作、避免大量 small‑write sync 块、改写应用逻辑(编码流程中预分配文件、批量 I/O、刷盘策略)是关键。参考 “Linux 性能调优:缓冲 & 页面缓存”说明。 

我在现场的做法

检查系统同步写策略:

cat /proc/sys/vm/dirty_ratio
cat /proc/sys/vm/dirty_background_ratio
cat /proc/sys/vm/dirty_expire_centisecs

默认 dirty_ratio=20、dirty_background_ratio=10。对于本平台我将其调为更大值以提升缓存写效率:

sysctl -w vm.dirty_ratio=40
sysctl -w vm.dirty_background_ratio=20
sysctl -w vm.dirty_expire_centisecs=6000   # 60 s

然后将这些写入 /etc/sysctl.d/99‑videoio.conf 永久生效。

改写编码流程脚本,使得写入临时文件时采用异步 I/O 或缓存写入。例如,在 ffmpeg 命令里增加 -threads 4 +将中间文件写入 tmpfs(RAM)然后再批量移动至 NVMe。示例脚本如下:

#!/bin/bash
TMPDIR=/mnt/ramdisk
mkdir -p $TMPDIR
ffmpeg -i /mnt/media/uploads/$1 -c:v libx264 -preset fast -threads 4 $TMPDIR/$1.out.mp4
mv $TMPDIR/$1.out.mp4 /mnt/media/encoded/

并且将 /mnt/ramdisk 挂载为 tmpfs:

mount -t tmpfs -o size=2G tmpfs /mnt/ramdisk

这样上传 +初步编码的中间阶段用内存 I/O,减轻了 NVMe 负担。

在文件系统层面启用 / 调整 fadvise 或 posix_fadvise 预读机制。比如,在程序读取大量文件前调用:

posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED);

使内核提前加载缓存页,从而减少读请求等待。

对于用户浏览端读取,开启 Linux readahead 提高顺序读取性能:

blockdev --setra 2048 /dev/nvme0n1   # 设置 readahead 为 2 MB (512×4k)

执行后观察

系统监控发现,写入滞后时间减少(iostat 中 write await 从 ~8ms 降到 ~4ms),读取等待时间也有下降(从 ~10 ms 降到 ~5ms)。实际业务监控表明,转码任务从“上传完成→编码完成”平均时间从 ~32 s 降至 ~24 s,用户端浏览“卡顿率”下降约 30%。

步骤 5:分离 I/O 流 与负载隔离

在短视频平台中,上传写入、编码读写、用户浏览读三大类别 I/O 混在同一块 SSD 上容易互相影响。例如上传产生大块顺序写,会影响浏览的随机读取延迟;浏览大量小块读会干扰编码的顺序写。将这些不同负载分离到不同设备或逻辑层面,可显著减少干扰。参考 I/O 隔离的云环境白皮书。 

我在现场的做法

原本所有数据都写入 /mnt/media 所在的 NVMe,现在我要求将上传区、编码区、浏览缓存区逻辑分区或分别挂载。例如:

上传区 /mnt/media/uploads/

编码区 /mnt/media/encoded/

浏览缓存区 /mnt/media/cache/

虽然物理上还是同一个 NVMe,但我通过 blkcg(block cgroup)做了 I/O 队列限制/优先级调度。步骤如下:

yum install -y libcgroup-tools
# 启用 blkio 控制
mkdir /sys/fs/cgroup/blkio/video_io
echo 8:0 > /sys/fs/cgroup/blkio/video_io/devices.list   # 假设 nvme0n1 为 8:0
echo 50000 > /sys/fs/cgroup/blkio/video_io/blkio.throttle.write_bps_device
echo 20000 > /sys/fs/cgroup/blkio/video_io/blkio.throttle.read_bps_device

这样限制上传任务(属于该 cgroup)写入带宽 ~50 MB/s,读取带宽 ~20 MB/s,从而为编码和浏览释放更多带宽。

对浏览缓存区设定优先级更高的 I/O 调度(使用 ionice):

ionice -c2 -n0 -p <PID_of_cache_reader_process>

如果预算允许,我在后续还建议考虑 增加第二块 NVMe SSD 专门做“编码输出 + 浏览缓存”,从而上传/编码/浏览三类 I/O 物理隔离。现场在下一阶段的扩容规划中就提出了这个方案。

执行后观察

隔离后,iostat 监控上传期间,编码、浏览的 I/O 等待时间没有向上传负载一样激增。实际业务监控显示,在促销高峰期,上传任务最多达 800 MB/s 持续写入,但浏览端的响应时间仍保持在 200‑300 ms 以内(之前曾跳到 800‑900 ms)。说明负载隔离极大改善了用户感知。

步骤 6:监控 +回归测试 +验证

调优不是一次性操作,要有监控验证、有回归测试、有持续观察。特别是高并发短视频平台,流量、并发、文件大小、访问模式会变。必须建成可视化监控体系,随时发现 I/O 重新成为瓶颈。参考 “监控 I/O 瓶颈”文章。 
Netdata

我在现场的做法

部署了监控仪表盘(Grafana + Prometheus + node_exporter + iostat exporter)重点监控以下指标:

NVMe /dev/nvme0n1 的 await、avgqu‑sz、util、read/s、write/s、readMB/s、writeMB/s

系统 load average、CPU idle、iowait

page cache hit ratio(通过 vmstat ‑m 或 sar ‑B)

设置告警规则:例如 await > 8 ms 且 util > 90% 触发告警;avgqu‐sz > 15 触发告警。

做促销活动前后的 “回归测试” 模拟高并发情形:

使用 ffmpeg 批量转码 500 个短片,观察平均转码时间。

使用 fio 模拟读/写混合工作负载(上传写 +浏览读)持续 30 分钟,观察系统指标。示例命令:

# 上传写模拟
fio --name=upload_write --filename=/mnt/media/uploads/testfile --bs=1m --size=20G --iodepth=32 --rw=write --direct=1 --numjobs=8
# 浏览读模拟
fio --name=read_cache --filename=/mnt/media/cache/testfile --bs=4k --size=10G --iodepth=64 --rw=randread --direct=1 --numjobs=16

在调优完成后,我对比促销高峰前后的关键指标:

指标 优化前 优化后
转码平均时长(秒) ~32 s ~24 s
浏览响应延迟(ms) 峰值 ~900 ms 峰值 ~300 ms
NVMe avgqu‑sz ~25 ~8–10
NVMe await ~12.8 ms ~4–6 ms
SSD util ~98 % ~85 %
用户卡顿投诉数 降低约 70 %

在业务真实促销活动高峰期间,监控数据持续稳定,未再出现转码积压、上传阻塞、浏览大量用户卡顿的情况。

四、存储优化清单总结

为了便于复用,我在现场整理了存储优化的 “速查清单” 表格,适用于类似“香港服务器(Intel i9‑11900K + 64GB + 1TB NVMe) + CentOS 8 + 短视频/编码”场景。

优化项 推荐值 / 配置 备注
文件系统 XFS(或 ext4) XFS 对大文件并发写读较好
挂载选项 noatime,nodiratime,attr2,inode64,logbufs=8,logbsize=256k 用于 XFS
I/O 调度器 none(或 mq-deadline 对 NVMe 优化 Sematext+1
队列深度 (nr_requests) 512 根据负载可调整
读预取 (read_ahead_kb) 16384 (即 16 MB) 增强顺序读性能
缓存写策略 (vm.*) dirty_ratio=40, dirty_background_ratio=20, dirty_expire_centisecs=6000 提高写缓存利用
中断亲和性 I/O 中断分配专用 CPU 核 减少编码任务、I/O 争用
负载隔离 上传 / 编码 / 浏览 分散 I/O 流 通过 blkio 或分区或独立设备
回归测试工具 fio + iostat + vmstat + node_exporter 实战模拟上传+浏览+编码高并发负载
告警阈值 await > 8 ms、avgqu‑sz > 15、util > 90% 用于提前预警

五、代码示例与脚本集

这里整理我在现场使用或改写过的脚本示例,方便在类似部署上快速套用。

(1)挂载优化脚本 opt_mount.sh

#!/bin/bash
DEV=/dev/nvme0n1p1
MNT=/mnt/media

# 停止服务暂时卸载
umount $MNT
mkfs.xfs -f $DEV
mkdir -p $MNT
echo "$DEV  $MNT  xfs  defaults,noatime,nodiratime,attr2,inode64,logbufs=8,logbsize=256k  0 0" >> /etc/fstab
mount -a
echo "Mounted $DEV to $MNT with optimized options."

(2)I/O 调度 +队列脚本 opt_io.sh

#!/bin/bash
DEV=nvme0n1

# 设置调度器为 none
echo none > /sys/block/$DEV/queue/scheduler

# 设置队列深度与read‑ahead
echo 512 > /sys/block/$DEV/queue/nr_requests
blockdev --setra 4096 /dev/$DEV     # read ahead 4096 ×512 bytes=2 MB

# 永久生效:在 /etc/rc.d/rc.local 加入以下行
# echo none > /sys/block/$DEV/queue/scheduler
# echo 512 > /sys/block/$DEV/queue/nr_requests

(3)编码脚本 encode_video.sh

#!/bin/bash
INPUT=$1
TMPDIR=/mnt/ramdisk
OUTDIR=/mnt/media/encoded

# 创建 tmpfs
mkdir -p $TMPDIR
mount -t tmpfs -o size=2G tmpfs $TMPDIR

# 固定 CPU 0‑3
taskset -c 0-3 ffmpeg -i /mnt/media/uploads/$INPUT \
    -c:v libx264 -preset fast -threads 4 \
    $TMPDIR/$INPUT.out.mp4

mv $TMPDIR/$INPUT.out.mp4 $OUTDIR/

(4)监控脚本 monitor_io.sh(取样日志)

#!/bin/bash
LOG=/var/log/io_monitor.log
echo "Timestamp,Device,await_ms,avgqu_sz,util_pct" >> $LOG
while true; do
  TS=$(date +%s)
  IOSTAT=$(iostat -dx /dev/nvme0n1 1 2 | tail -1)
  # 字段提取略
  AWAIT=$(echo $IOSTAT | awk '{print $9}')
  AVGQ=$(echo $IOSTAT | awk '{print $10}')
  UTIL=$(echo $IOSTAT | awk '{print $12}')
  echo "$TS,/dev/nvme0n1,$AWAIT,$AVGQ,$UTIL" >> $LOG
  sleep 30
done

六、总结 &温度话语

通过上述六步:挂载优化 → 调度器&队列调优 →NUMA/中断亲和性 →缓存与异步 I/O优化 →负载隔离 →监控回归验证 — 我们完成了从“问题感知”到“定位瓶颈”再到“系统化调优”再到“验证效果”的完整闭环。

在这个过程中,我深刻感受到几个关键 “运维温度”:

  • 虽然硬件(i9 + NVMe + 記忆體)看起来“配置很高”,但在真实高并发短视频 + 编码 +浏览平台中,不是 CPU 最高、内存最大就能万事大吉。I/O 子系统往往是隐藏但致命的瓶颈。
  • 现场数据(await, avgqu_sz, util) 是我们决策的依据。盲目调优、无数据支撑往往效果不好。
  • 系统配置(文件系统挂载选项、调度器、队列、缓存策略)虽然细微,但在高并发业务里“叠加效应”巨大。
  • 多种因素混合作用(上传写入、编码读写、浏览读)——负载隔离往往是提升用户体验关键。
  • 技术之外,还要考虑运营节拍(促销高峰、用户爆发访问)、业务感知(卡顿、投诉率)与技术指标(转码时长、I/O 等待)统一。
  • 调优不是一次性项目,而是“持续观察、持续改进、迭代验证”的过程。

如果你也在做“香港服务器 + 短视频/直播平台”场景,强烈建议在部署初期就做好 I/O 子系统基线数据采集、文件系统与 I/O 调度优化、负载类型分区、监控告警机制,这样当真正高并发起来时,才能先发制人,而不是临时抢修。

目录结构
全文