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

如何在香港服务器运行 Debian 时,启用与调优 ZFS ARC,加速直播回放文件读取

发布人:Minchunlin 发布时间:2025-09-08 10:22 阅读量:904


那天是周六的凌晨两点,香港机房的告警把我从打盹里拎起来——用户在看直播回放时频繁缓冲。Nginx 的日志像瀑布一样刷屏,边缘节点到源站的带宽明明还有余量,磁盘 I/O 却顶到了 90% 以上。zpool iostat 显示随机读抖动严重,ARC 命中率 50% 出头。很明显:热段没被好好留在内存,回放的热点切片在机械盘上来回抽签。

那一夜我做了两件事:把 ZFS 的 ARC“胃口”放大到合适的比例,再用 NVMe 做 L2ARC 与 metadata 特殊 vdev,把热门数据和元数据“揣兜里”。第二天午后回放的 95 分位时延就下来了,命中率稳定 85%+。这篇文章,就是把我当晚的全部实操细节掏出来,不管你是新手还是老手,都能照着上手。

1. 现场环境与访问画像

1.1 物理与系统参数

型号/版本 备注
机架 1U Supermicro 港区机房
CPU Intel Xeon Silver 4214(12C/24T)×1 支持 AES-NI
内存 128 GB DDR4 ECC NUMA 单路
系统盘 SATA SSD 480 GB ×1 Debian 系统
数据盘 12 TB SATA HDD ×8 组 RAIDZ2
NVMe1 3.84 TB U.2 企业盘 L2ARC / special vdev
NVMe2 3.84 TB U.2 企业盘 L2ARC / special vdev(镜像)
网卡 25 GbE SFP28 上联交换机万兆/25G
OS Debian 12 (bookworm) 内核 6.x
业务 Nginx/HLS、MP4 回放 每晚回放峰值 5–8k 并发

1.2 访问模式(决定调优方向)

  • HLS 切片:2–4 MB .ts/.m4s,短时间集中被反复读取(同一热门赛事回放,重复率高)。
  • MP4 进度条拖动:偏随机读,跨度几十 MB。
  • 写入:回放生成阶段写入量远小于读取;我们的目标是优化读路径。

结论:

  • ZFS 的 ARC 要能容纳“最近 30–60 分钟的热门切片”。
  • 开启/保留 prefetch,recordsize 适合视频(大块),primarycache=all(缓存数据+元数据),secondarycache=all(启用 L2ARC)。
  • 将 元数据与小文件放入 special vdev(NVMe 镜像),显著降低 HDD 寻道。

2. 在 Debian 上安装 ZFS(DKMS 方案)

如果你已经在用 ZFS,可跳到下一节“池与数据集设计”。

# 1) 确保 contrib 仓库启用
sudo sed -i 's/main$/main contrib non-free-firmware/' /etc/apt/sources.list
sudo apt update

# 2) DKMS 需要内核头文件
sudo apt install -y linux-headers-$(uname -r) build-essential

# 3) 安装 ZFS
sudo apt install -y zfs-dkms zfsutils-linux

# 4) 加载模块并校验
sudo modprobe zfs
zfs version

坑 1:DKMS 编译失败

多半是缺 linux-headers-$(uname -r),或你装了“新内核但没重启”。装头文件&重启后再装 ZFS,一次过。

3. 池与数据集:为回放读优化的磁盘布局

3.1 创建池(HDD 走 RAIDZ2,NVMe 镜像做 special)

special vdev 用来存 元数据与小文件,强烈建议用 镜像(mirror)——special 掉了,pool 直接“黑屏”。

# 假设 HDD 为 /dev/sd[b-i],两块 NVMe 为 /dev/nvme0n1 /dev/nvme1n1
# 注意:生产环境根据你的设备名调整
sudo zpool create -o ashift=12 \
  -o autotrim=on \
  -O compression=lz4 \
  -O atime=off \
  -O xattr=sa -O acltype=posixacl \
  tank \
  raidz2 /dev/sdb /dev/sdc /dev/sdd /dev/sde /dev/sdf /dev/sdg /dev/sdh /dev/sdi \
  special mirror /dev/nvme0n1 /dev/nvme1n1
  • ashift=12:对 4K 物理扇区友好。
  • atime=off:避免每次读都写 atime。
  • xattr=sa/acltype=posixacl:减少元数据额外开销。
  • special mirror nvme:把 dnode/indirect blocks/元数据放 NVMe,回放的索引查找飞起来。

3.2 添加 L2ARC(可晚点加)

L2ARC 是 只读二级缓存(典型放 NVMe),命中率提升明显,但要先把 ARC 调好。
我通常 先跑一晚只靠 ARC,再加 L2ARC 观察命中率是否上升。

# 添加 NVMe 作为二级缓存(如果 special 已用两块 NVMe,考虑再加一对做 L2ARC)
sudo zpool add tank cache /dev/nvme2n1
# 验证
zpool status tank

4. ARC 容量规划与持久化配置

4.1 先算“能给 ARC 多大内存”

经验值(直播回放负载):

物理内存 预留给系统/进程 ARC 可用上限(zfs_arc_max)
64 GB 12–16 GB 36–44 GB
128 GB 20–28 GB 80–96 GB
256 GB 40–56 GB 180–210 GB

我这台 128 GB,Nginx+FFmpeg 这类进程常驻 ~10–12 GB,再给监控、日志和 page cache 留冗余,把 ARC 设到 88 GB 比较稳。

4.2 临时调整(热排错用)

# 查看当前
cat /sys/module/zfs/parameters/zfs_arc_max
cat /proc/spl/kstat/zfs/arcstats | egrep "size|c_max|c_min|hits|misses|l2_hits|l2_misses"

# 临时设定(单位字节),88G:
echo $((88*1024*1024*1024)) | sudo tee /sys/module/zfs/parameters/zfs_arc_max
# 同步设个 zfs_arc_min(比如上限的 1/4~1/3)
echo $((24*1024*1024*1024)) | sudo tee /sys/module/zfs/parameters/zfs_arc_min

注意:这种写 /sys 的方式重启就没了,用于当晚观察。

4.3 持久化(开机生效)

编辑 /etc/modprobe.d/zfs.conf:

# /etc/modprobe.d/zfs.conf
options zfs zfs_arc_max=94489280512 zfs_arc_min=25769803776
# 88G / 24G
# 可按你的内存与业务进程占用调整

更新 initramfs(ZFS 模块一般随内核早期加载):

sudo update-initramfs -u -k all
# 下次重启生效;若当前要立即生效,用 4.2 的临时方法先试跑

坑 2:设了不生效

模块早已加载,modinfo zfs 可见;要么重启,要么临时写 /sys。如果是 root on ZFS 的系统,别强行 rmmod。

5. 数据集参数(让回放文件“更合胃口”)

我把用于回放的目录单独建为一个 dataset,专治视频大文件/切片:

sudo zfs create tank/replay
sudo zfs set mountpoint=/data/replay tank/replay

# 关键参数(结合访问画像)
sudo zfs set recordsize=1M tank/replay        # 视频块大
sudo zfs set primarycache=all tank/replay     # 数据+元数据都进 ARC
sudo zfs set secondarycache=all tank/replay   # 允许进 L2ARC
sudo zfs set compression=lz4 tank/replay      # 绝大多数视频已压,LZ4 主要压元数据,几乎无负担
sudo zfs set atime=off tank/replay            # 减少无谓写
sudo zfs set logbias=throughput tank/replay   # 偏吞吐(写入不敏感)

recordsize:HLS 切片 2–4 MB,我统一设 1 MB 比较折中。MP4 也受益于大 recordsize 的顺序读。
若你的工作集“超热但稀薄”(同一时间段只少量切片被反复读),也可试 primarycache=metadata 看看内存压力与命中率的权衡——我的场景 all 更优。

6. L2ARC 高阶调优(可选但推荐)

添加 L2ARC 后,观察默认吞吐是否过低,可适度抬高写速(别压垮业务):

# 查看 L2ARC 参数(不同版本名字可能略有差异)
grep . /sys/module/zfs/parameters/l2arc_* 2>/dev/null | sort

# 例如将 L2ARC 写入上限提到 ~64MB/s(按需)
echo $((64*1024*1024)) | sudo tee /sys/module/zfs/parameters/l2arc_write_max

# 降低 prefetch 禁忌(让顺序读也能进 L2ARC),视版本而定
# 0 = 允许预取数据进 L2ARC;1 = 禁止
echo 0 | sudo tee /sys/module/zfs/parameters/l2arc_noprefetch

持久化:同样写到 /etc/modprobe.d/zfs.conf 的 options zfs ... 里;参数名以实际系统为准。
OpenZFS 2.x 的持久化 L2ARC(重启后继续热):大多版本已默认开启,可通过 zpool get feature@persistent_l2arc tank 查看,保持 enabled/active。

7. Nginx 回放与操作系统配合

sendfile on; + ZFS 没冲突,ZFS 会自行管理页面缓存。

open_file_cache 对热点切片加速明显(减少 stat),同时 ARC/Special vdev 让元数据查询更快:

open_file_cache max=200000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

不要在 Nginx 上乱开 aio threads + directio,让 ZFS 自己做预取和缓存更稳。

8. 监控与压测:我当晚用的脚本与流程

8.1 ARC 命中率快速观察脚本

#!/usr/bin/env bash
# arc_hit.sh:每秒打印一次 ARC/L2ARC 命中率
while true; do
  stats=/proc/spl/kstat/zfs/arcstats
  hits=$(awk '/^hits/{print $3}' $stats)
  misses=$(awk '/^misses/{print $3}' $stats)
  l2hits=$(awk '/^l2_hits/{print $3}' $stats)
  l2miss=$(awk '/^l2_misses/{print $3}' $stats)
  ah=$(awk -v h=$hits -v m=$misses 'BEGIN{printf "%.2f", (h/(h+m))*100}')
  lh=$(awk -v h=$l2hits -v m=$l2miss 'BEGIN{printf "%.2f", (h/(h+m))*100}')
  echo "$(date '+%H:%M:%S') ARC_hit=${ah}%  L2ARC_hit=${lh}%"
  sleep 1
done

8.2 回放读压测(两种)

Nginx 静态文件下载(模拟切片分发):

# 100 并发、持续 120s
wrk -t8 -c100 -d120s http://127.0.0.1/replay/hot/seg-00001.ts

fio 顺序/随机混合读(贴近 MP4 拖动):

# fio-replay-read.fio
[replay]
rw=randread
rwmixread=100
bs=1M
iodepth=32
directory=/data/replay
numjobs=4
runtime=120
time_based=1
group_reporting=1

fio fio-replay-read.fio

9. 我这台机器“前后对比”的核心指标

压测时同等并发,同一内容集,先只用 ARC,再加 L2ARC+special vdev。

指标 调优前 仅 ARC 调优 + L2ARC & special
Nginx 静态下载 95p 时延(ms) 180 95 58
ARC 命中率(稳态) 52% 78% 85–88%
L2ARC 命中率(稳态) 20–30%
HDD 平均读队列 1.8 1.1 0.6
CPU iowait 12% 6% 3%

解读:ARC 做到 70%+,已经能“救火”;加了 special vdev(元数据在 NVMe 上)和 L2ARC(冷热交界数据在 NVMe 上),尾延进一步收紧,HDD 几乎只干“冷块吞吐”。

10. 常见坑位与现场补救

L2ARC 放消费级 NVMe,长时间写入掉速

现象:晚高峰后 l2arc 写入跟不上、命中率波动。

解法:换 企业 NVMe;临时可把 l2arc_write_max 稍降但更稳定,或只先用 ARC+special。

ARC“吃太多”触发 OOM

现象:内存吃紧时 Nginx/FFmpeg 被 OOM。

解法:设置 zfs_arc_max 与 zfs_arc_min,并保守预留给业务进程;必要时将 primarycache=metadata 应用于“低复用度目录”。

special vdev 单盘

现象:单块 NVMe special 掉了,pool 直接不可用。

解法:special 至少做镜像。宁可不加,也别单盘 special。

DKMS 编译/升级踩坑

解法:升级前先 apt install linux-headers-$(uname -r);生产上一律先在灰度机验证。

prefetch 被关

现象:顺序读视频命中率异常低。

解法:检查 /sys/module/zfs/parameters/zfs_prefetch_disable 或版本相关开关,确保预取开启;必要时调大 zfetch_max_streams(不同版本名略有差异,先 grep zfetch 看看)。

11. 运维日常:观察与回滚

  • 观察周期:至少跑满一个“晚高峰周期”。命中率、HDD 队列、Nginx 95/99 分位时延。
  • 慢慢加码:先 ARC,后 L2ARC,再考虑调 l2arc_*。

回滚策略:

  • L2ARC 可随时 zpool remove tank cache /dev/nvme2n1。
  • 数据集参数改动即时生效;若异常,原值改回即可。
  • special vdev 一旦上了,就当“永久部件”(所以要镜像)。

12. 我的最终配置摘录(可作模板)

# /etc/modprobe.d/zfs.conf
options zfs zfs_arc_max=94489280512 zfs_arc_min=25769803776 \
  l2arc_write_max=67108864 l2arc_noprefetch=0

# 池与数据集(示意)
zpool create -o ashift=12 -o autotrim=on -O compression=lz4 -O atime=off \
  -O xattr=sa -O acltype=posixacl tank \
  raidz2 sdb sdc sdd sde sdf sdg sdh sdi \
  special mirror nvme0n1 nvme1n1

zfs create tank/replay
zfs set mountpoint=/data/replay tank/replay
zfs set recordsize=1M primarycache=all secondarycache=all atime=off \
  compression=lz4 logbias=throughput tank/replay

# 需要时添加 L2ARC
zpool add tank cache nvme2n1

13. 收尾:凌晨三点半的机房走廊

当晚三点半,机房走廊的空调风还是一阵阵地吹。ARC 命中率稳在 80% 以上,L2ARC 也接过了那部分“温热数据”,HDD 指示灯从狂闪变得从容。Nginx 的 95p 时延图曲线收紧得像拉好的弓弦。
我把 zfs.conf 的参数持久化,给监控加了几个告警阈值,然后在工单里写上这句话:“回放热点留在内存和 NVMe 上,机械盘只负责大冷块。”
第二天同事问我怎么做到的,我笑着把这套步骤发过去——就像现在写给你的一样。愿你下一个“凌晨两点”的现场,也能把问题按住。

快速清单(方便你照着做)

  • 安装 zfs-dkms / zfsutils-linux,补齐内核头。
  • 池:HDD 组 RAIDZ2;NVMe 镜像做 special vdev。
  • 数据集:recordsize=1M,primarycache=all,secondarycache=all,atime=off。
  • ARC:按照物理内存与进程占用,设 zfs_arc_max/zfs_arc_min;先临时,后持久化。
  • (可选)L2ARC:添加企业 NVMe,必要时调整 l2arc_write_max/l2arc_noprefetch。
  • 监控与压测:arcstats、zpool iostat、wrk/fio,至少跑一个业务周期。
  • 注意坑:special 要镜像;ARC 别吃太多;DKMS 升级先灰度。
目录结构
全文