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

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

发布人:Minchunlin 发布时间:2025-09-21 11:41 阅读量:862


凌晨 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 个关键旋钮拧准:块大小、并发流、线程数、缓存时延。当设计师说“刚存的文件我这边已经能看到了”,你就知道,方向对了

目录结构
全文