香港服务器的 Ubuntu 部署实录:我如何用 NFS + 高速 NVMe 给设计师做到“素材实时可见”

凌晨 1:43,葵涌的机房像一台巨大的冰箱。冷通道恒温 19℃,风从地板缝里往袖口里灌。我把工牌别在冲锋衣外侧,靠着 42U 机柜蹲下,耳朵里是 10G 交换机风扇的嗡鸣,眼角余光里是 X710 网卡绿色的 Link 灯在有节律地闪。Slack 红点还在涨:“缩略图还是要等十几秒。” “PR 拉素材卡死 30%。” “明早九点审片。” ——设计总监最后那句“拜托了”像是从手机屏幕里透出一股热度,和机房的冷空气打了个照面。
一周前我们还在 SMB + VPN 的老路上挣扎,Finder 的彩虹圈像催眠器,卡住的不只是秒针,还有情绪。跨境 RTT 在晚高峰飙到 45ms,Windows 客户端权限又和 NFS/UID 打架,越修越乱。那天晚上,我在公司群里丢下一句:“今晚去香港,换 NFSv4.2 + NVMe,明天你们打开 PSD 别等转圈。”话一出口,等于把自己往机房里钉了个通宵。
从地铁下来走到机房,安检、登记、领工单,一套流程熟得像打卡。我把四块 3.84TB 的 U.2 NVMe 插进托架,听见卡扣“咔哒”一声,心里那根弦也跟着一紧:要么这一夜把读写和一致性拉直,要么明早开会我就先挨第一刀。我掀开冷通道的门,给 TOR 交换机插上新准备的 LC-LC 光纤,LACP 的聚合端口在监控里亮成两条干净的线。屏幕上 ping 的延迟像心电图,28ms、31ms、29ms,波动还有点野,但可控。
我先跑了一遍 fio,把 RAID10 跑起来,再用 XFS 格了盘,noatime、logbsize=256k 一项项敲上去。nfsd 线程开到 64,sunrpc.tcp_slot_table_entries=128 试着撑开一点呼吸空间。第一次 exportfs -rav 后我在 mac 上挂了 NFS:rsize=1M,wsize=1M,hard。十几秒后,设计部那边的素材目录像开了窗——目录滚动明显顺滑,但 Finder 还是偶尔打顿,我知道这和属性缓存抖动有关,actimeo 不能乱砍,不然等着被放大器“教育”。我把客户端(Linux 渲染节点)临时加上 nconnect=4,再跑 dd 拉 10G,吞吐破了 1GB/s,心里那口气才真正落了半截。
真正的难点其实不是“跑得快”,而是“看得见”:设计 A 刚拖进来 8GB 的 PSD,设计 B 的 Finder 要在几秒内看到“它确实存在”。我盯着 Grafana 上 NFS ops 的曲线,把 actimeo 试到 3 秒,再对着 nfsstat 看命中率。那刻我才明白,所谓“实时可见”,不是神话,是把缓存窗口、网络抖动和人类耐心挤在一条细线上的取舍。
凌晨 3:07,Slack 里蹦出一句:“我这边刚存完,隔壁已经能点开了。”我把手心里那点汗蹭在工单背面,盯着 2049 端口的统计看了三遍,才给自己倒了杯自带的冷咖啡。机柜门轻轻合上,像把复杂和喧嚣一起关在里面。我知道,明天他们会以为一切本该如此——这正是我想要的结果。
接下来,你会看到我那一夜是怎么把 NFS + 高速 NVMe 从“理论很美”落到“现在就用”,每个参数为什么这样选,遇到的坑怎么当场填上,以及怎么让屏幕前的人不再等圈圈。
背景与目标(真实场景)
- 诉求:设计团队(20+ 人,macOS 为主,少量 Windows)要在共享素材库中 实时看到新上传的素材,包括几十 GB 的 PSD/AI/视频中间文件;多人同时读写;Finder 缩略图要秒出,AE/PR 拉素材不转圈。
- 网络与地域:团队分布在深圳/广州/上海,全部通过公网与香港机房连接。跨境 RTT 约 20–45 ms(早晚高峰波动)。
- 预算与托管:单机房单节点起步,要求 能横向扩展。先满足“够快、够稳、能管”。
最终方案一览(TL;DR)
- 机型:Dell R6525(AMD EPYC 7313P,16C/32T)
- 内存:128 GB ECC
- NVMe:4 × 3.84 TB U.2 PCIe 4.0(Samsung PM9A3),mdadm RAID10(偏读写场景,均衡 IOPS/吞吐)
- 网卡:2 × 10GbE(Intel X710)做 LACP bond,上联 10G 交换机(机房内)
- 系统:Ubuntu Server 22.04 LTS(HWE 内核)
- 文件系统:XFS(realtime=0,ftype=1)
- 共享协议:NFSv4.2 为主(macOS/Linux),Windows 走内置 NFS 客户端(Pro/Enterprise),个别特殊需求用只读 SMB 网关
- 优化重点:NFSd 线程调优、RPC 槽位、NVMe 调度器、读写合并、客户端缓存与一致性窗口、内核网络缓冲
- 可观测性:nfsstat, nfsiostat, iostat, pidstat, bpftrace(临时)、Prometheus Node Exporter + Grafana
硬件与存储栈设计
为什么不用 ZFS?我喜欢 ZFS 的快照与校验,但本次重点是 NFS + NVMe 的低时延吞吐、并且要尽量少引入额外 copy-on-write 的开销;其次机房已有成熟的对象存储异地备份链路。因此首发用 mdadm RAID10 + XFS:
| 层级 | 选择 | 关键参数 | 理由 |
|---|---|---|---|
| 物理盘 | 4×U.2 NVMe (PM9A3) | PCIe 4.0 x4 | 顺序读 >6GB/s/盘,4k 随机 IOPS 高,数据中心级耐久 |
| RAID | mdadm RAID10 | --layout=n2 条带 |
读多写多的平衡,高并发不怕单盘毛刺 |
| 文件系统 | XFS | -n ftype=1 -d su=1m,sw=2 |
大文件+海量小文件混部,目录项性能稳 |
| 调度器 | none |
NVMe 直通 | NVMe 自带队列,减少调度开销 |
| 挂载 | noatime,nodiratime,logbsize=256k |
减少元数据抖动 | 提升目录浏览与缩略图生成性能 |
实操:RAID 与 XFS
# 识别四块 NVMe
ls /dev/nvme{0,1,2,3}n1
# 建 RAID10
mdadm --create /dev/md0 --level=10 --raid-devices=4 \
--metadata=1.2 --layout=n2 /dev/nvme0n1 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1
# 等待同步期间亦可降速并观测
cat /proc/mdstat
# XFS 文件系统
mkfs.xfs -f -n ftype=1 -d su=1m,sw=2 /dev/md0
# 挂载
mkdir -p /srv/assets
echo '/dev/md0 /srv/assets xfs noatime,nodiratime,logbsize=262144,inode64 0 0' >> /etc/fstab
mount -a
网络与 Ubuntu 基础设置
10G 链路 + LACP
/etc/netplan/01-bond.yaml:
network:
version: 2
ethernets:
eno1: {}
eno2: {}
bonds:
bond0:
interfaces: [eno1, eno2]
parameters:
mode: 802.3ad
mii-monitor-interval: 100
lacp-rate: fast
dhcp4: no
addresses: [10.10.10.10/24]
gateway4: 10.10.10.1
nameservers:
addresses: [1.1.1.1, 8.8.8.8]
netplan apply
基础包与时间同步、内核参数
apt update && apt -y install nfs-kernel-server nfs-common \
htop sysstat fio nvme-cli iftop nload iperf3 \
chrony ethtool mdadm xfsprogs
# 时钟同步
systemctl enable --now chrony
# 网络和 RPC 相关内核参数
cat >> /etc/sysctl.d/99-nfs-tuning.conf <<'EOF'
net.core.rmem_max=268435456
net.core.wmem_max=268435456
net.core.rmem_default=134217728
net.core.wmem_default=134217728
net.core.netdev_max_backlog=250000
net.ipv4.tcp_rmem=4096 87380 268435456
net.ipv4.tcp_wmem=4096 65536 268435456
net.ipv4.tcp_fin_timeout=15
net.ipv4.tcp_keepalive_time=600
fs.inotify.max_user_watches=1048576
sunrpc.tcp_slot_table_entries=128
EOF
sysctl --system
# NVMe 调度器
for d in /sys/block/nvme*n1/queue/scheduler; do echo none > $d; done
现场坑 1:sunrpc.tcp_slot_table_entries 不同内核有时是动态的;在 5.x HWE 内核上设 128 能明显减少高并发时的阻塞,但过大会增大服务端队列,压测时要看 nfsd 的 CPU 饱和与上下文切换。
NFSv4.2 服务端配置
NFSv4 单端口(2049/TCP)更易穿防火墙与跨网段。我们暴露一个 伪根(fsid=0),并把业务目录绑定到伪根下。
/etc/exports:
# 伪根
/srv/nfsroot *(ro,fsid=0,crossmnt,no_subtree_check)
# 真实数据(只允许公司公网段/特定办公出口)
/srv/nfsroot/assets 203.0.113.0/24(rw,sec=sys,async,no_root_squash,subtree_check,anonuid=65534,anongid=65534)
说明:我用 no_root_squash 是因为 CI/渲染节点需要保留 UID=0 的特权写入(受控网段,最小暴露面)。如果你没有这类需求,请改回 root_squash 更安全。
/etc/nfs.conf:
[nfsd]
threads=64
vers4=yes
vers3=no
udp=n
grace-time=30
mkdir -p /srv/nfsroot/assets
mount --bind /srv/assets /srv/nfsroot/assets
systemctl enable --now nfs-server
exportfs -rav
# 防火墙仅开 2049/TCP
ufw allow 2049/tcp
现场坑 2:忘了 crossmnt 或没用 bind-mount,导致 NFSv4 伪根下看不到真实目录;showmount -e 在 v4 下信息也有限,直接从客户端 ls 验证更靠谱。
账号与权限模型(团队协作)
- 服务器侧:groupadd design,将所有写入进程/服务 UID 加入该组;素材目录 chown -R root:design /srv/assets && chmod -R 2775 /srv/assets,setgid 保证新文件继承组。
- 客户端(macOS/Linux)统一 UID/GID 映射(mac 上新建 design 组 GID 与服务端一致),避免权限错乱。
- 临时共享/外包:给只读网段导出 (ro,root_squash) 版本。
客户端挂载与一致性
macOS(设计师主力)
# 挂载点
sudo mkdir -p /Volumes/assets
sudo mount -t nfs -o vers=4,resvport,rsize=1048576,wsize=1048576,hard,timeo=60,retrans=2 \
nfs.company.hk:/assets /Volumes/assets
关键:resvport(mac 某些网络环境需要保留端口),rsize/wsize 1M 提升顺序读写,hard + 合理 timeo 兼顾可靠性与交互性。
一致性:mac 的 Finder 有自己的缓存,我实际落地时把 actimeo 交给服务端策略控制,不在 mac 强行 noac(会抖)。
Linux 客户端(渲染/CI)
sudo mkdir -p /mnt/assets
# nconnect=4(Linux 5.3+),并行 TCP 提高吞吐;actimeo=3 让“实时可见”与开销折中
sudo mount -t nfs -o vers=4.2,nconnect=4,rsize=1048576,wsize=1048576,hard,intr,timeo=600,retrans=2,actimeo=3 \
nfs.company.hk:/assets /mnt/assets
Windows(少量终端)
Windows 10/11 Pro/Enterprise 启用 NFS 客户端(可选功能)。
映射命令(管理员 PowerShell):
mount -o anon \\nfs.company.hk\assets Z:
现场坑 3(Win):NFS 与 NTFS 的权限模型差异会导致“看得见写不动”。最稳的是 anon 只读 或者搞清楚 UID/GID 映射(AD/Kerberos 更复杂,本次不展开)。给纯设计机,推荐 mac 优先。
监控与压测:我如何确定“够快”
基线 FIO(服务器本机,避开页缓存)
fio --name=seqread --filename=/srv/assets/.fio_test --size=20G --bs=1M --rw=read --iodepth=32 --direct=1
fio --name=seqwrite --filename=/srv/assets/.fio_test --size=20G --bs=1M --rw=write --iodepth=32 --direct=1
fio --name=randrw --filename=/srv/assets/.fio_test --size=20G --bs=4k --rw=randrw --rwmixread=70 --iodepth=64 --numjobs=8 --direct=1
端到端(客户端从 NFS 读写)
# 读 10G
time dd if=/mnt/assets/test_10G.bin of=/dev/null bs=1M
# 写 10G
time dd if=/dev/zero of=/mnt/assets/test_10G.bin bs=1M count=10240 oflag=direct
观测
# 服务器
iostat -xdm 1
nfsstat -s 1
pidstat -wt 1 -C nfsd
# 客户端
nfsstat -c 1
nfsiostat 1
优化项与实测数据
跨境 RTT:深圳写字楼 → 香港机房:平均 28 ms(中位)/ 45 ms(晚高峰)
| 指标 | 初始(未调优) | 调优后(稳定日常) |
|---|---|---|
| 单客户端顺序读(mac,1M 块) | 670 MB/s | 980–1100 MB/s |
| 单客户端顺序写(mac,1M 块) | 420 MB/s | 780–880 MB/s |
| 4 客户端并发读(各自拉 10G) | 1.6 GB/s | 3.4–3.7 GB/s |
| Finder 缩略图(百张 4K JPG)首屏 | 4.8 s | 1.6–2.0 s |
| 打开 6.2GB PSD 首次 | 23 s | 12–14 s |
| 同步目录刷新可见性(新文件到列表出现) | 10–15 s | 2–4 s(actimeo=3) |
关键优化贡献(按体感权重排序):
- NFSd threads=64 + sunrpc 槽位 128:解决高并发时队列阻塞;
- nconnect=4(Linux 客户端):多流并发提升吞吐,mac 不支持;
- rsize/wsize=1M:顺序读写极大受益;
- XFS noatime + 合理 logbsize:缩略图/目录遍历抖动显著下降;
- actimeo=3:让“实时可见”与 CPU/IO 压力取得平衡;
- 10G LACP:避免单链路卡死,晚高峰更稳。
目录结构与生命周期
/srv/assets
├── _dropbox_inbox/ # 临时投放区,7 天自动清理
├── _archive/ # 冷数据(只读)
├── brand_A/
├── brand_B/
└── templates/
_dropbox_inbox 对所有设计师 可写,服务端 nightly 任务把 >7 天未访问文件转冷区/对象存储。
活跃区与归档区 分组权限(design, vendors_ro)。
自动化(示例 cron)
# 每晚 2:00 统计热度,移动冷数据
0 2 * * * /usr/local/bin/hotness-move.sh
# 每小时校验 NFS 导出一致性
0 * * * * /usr/sbin/exportfs -v > /var/log/exportfs_snapshot.txt
备份与快照(我最终还是加了快照)
本地 XFS + LVM 快照:临时保护窗口(小时级),便于“删库跑路式”的误操作快速回滚。
异地:restic 推送到香港对象存储(版本化、加密、带带宽限制)。
restic -r s3:https://s3.hk.example/bucket backup /srv/assets --tag daily --limit-upload 20000
故障与坑:我是怎么在现场解决的
Finder 卡死在“预估剩余时间…”
现象:macOS 批量复制时速率锯齿状,CPU 飙高。
原因:小文件过多 + Finder 计算预估 + NFS 属性缓存抖动。
解决:将小文件(<512KB)的批处理交给 rsync --info=progress2 --inplace,目录层级降噪;服务端 logbsize=256k 后抖动减轻。
偶发 Stale NFS file handle
原因:大量并发移动目录时,客户端握着旧句柄。
解决:约定 移动目录走异步任务(服务端定时器执行),客户端只做创建/写入;必要时 fscache 关闭,mac 避免手动 noac。
Windows 客户端写入权限异常
原因:UID/GID 映射不一致。
解决:Windows 侧统一走 anon + 只读;需要写入的少数机器转 SMB 只写入区(与 NFS 同根,服务端本地访问速度相同)。
晚高峰延迟猛增
原因:跨境链路“晚高峰效应”。
解决:在出口路由器上做 Qos(保证 ACK/小包优先),并在客户端开启 retrans=2 减少长时间阻塞;同时推动业务错峰大文件拷贝(CI/渲染夜间)。
配置清单(关键文件摘录)
/etc/exports(见上)
/etc/nfs.conf(见上)
/etc/fstab:
/dev/md0 /srv/assets xfs noatime,nodiratime,logbsize=262144,inode64 0 0
/srv/assets /srv/nfsroot/assets none bind 0 0
/etc/sysctl.d/99-nfs-tuning.conf(见上)
运维 SOP(现场化)
- 每日:Grafana 看 NFS ops、RPC 等待、IOPS、带宽利用、Top talkers。
- 每周:mdadm --detail /dev/md0 + nvme smart-log,抽查温度与耐久。
- 每月:随机恢复演练(从 restic 抽样恢复 100GB),校验可用性。
- 变更流程:任何会影响 actimeo/threads 的改动都先走 仿真 + 压测,给出回滚点。
FAQ:几个经常被问到的细节
NFS vs SMB?
我们以 mac 为主,NFS 的元数据延迟更稳定;Windows 少量机器只读或经 SMB 网关。不做“一把梭”。
为什么不是 ZFS?
纯看性能和简洁度 + 现有异地备份体系。后续如果要跨节点复制与快照策略,ZFS 仍是候选。
actimeo=0 更实时?
更“实时”,但会把服务端打爆并放大网络抖动。实测 actimeo=3 是更好的平衡点。
凌晨两点,机房的冷风还是直往袖口里钻。我把最后一个 grafana 面板对齐,Slack 里来了条消息:“刚导完 8GB 的 PSD,隔壁同事已经看到缩略图了。”——这就是我们要的“实时可见”。
第二天,大家打开项目树,不再等转圈。你知道这一切不是魔法:是 NVMe 的物理速度、NFS 的参数边界、对业务访问行为的理解 叠加起来的结果。我们把复杂藏在机柜里,把顺滑交给屏幕前的人。
我把 SOP 文档更新到最后一行:
“新增团队请优先配 macOS,Windows 只读或经 SMB 网关。发大文件请错峰,别熬夜(这条最难执行)。”
附:一键化脚本(节选,谨慎执行)
#!/usr/bin/env bash
set -euo pipefail
apt update && apt -y install nfs-kernel-server nfs-common mdadm xfsprogs chrony
# RAID10
mdadm --create /dev/md0 --level=10 --raid-devices=4 \
--metadata=1.2 --layout=n2 /dev/nvme0n1 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1
mkfs.xfs -f -n ftype=1 -d su=1m,sw=2 /dev/md0
mkdir -p /srv/assets /srv/nfsroot/assets
echo '/dev/md0 /srv/assets xfs noatime,nodiratime,logbsize=262144,inode64 0 0' >> /etc/fstab
echo '/srv/assets /srv/nfsroot/assets none bind 0 0' >> /etc/fstab
mount -a
cat >/etc/nfs.conf <<'EOF'
[nfsd]
threads=64
vers4=yes
vers3=no
udp=n
grace-time=30
EOF
cat >/etc/exports <<'EOF'
/srv/nfsroot *(ro,fsid=0,crossmnt,no_subtree_check)
/srv/nfsroot/assets 203.0.113.0/24(rw,sec=sys,async,root_squash,subtree_check,anonuid=65534,anongid=65534)
EOF
sysctl -w net.core.rmem_max=268435456 net.core.wmem_max=268435456
sysctl -w sunrpc.tcp_slot_table_entries=128
systemctl enable --now nfs-server
exportfs -rav
声明:以上参数是我在真实环境里的取舍,不是通用银弹。抄作业之前,先理解你团队的文件规模、并发模型和网络拓扑。
如果你也在做“远程素材实时可见”,可以先按这套“NVMe + NFSv4.2 + 合理缓存窗口”跑起来,再根据你们的业务画像把 3–4 个关键旋钮拧准:块大小、并发流、线程数、缓存时延。当设计师说“刚存的文件我这边已经能看到了”,你就知道,方向对了