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

如何在香港服务器的 Debian 11 上启用 ZFS 压缩与快照功能,降低存储成本并提升读写效率?

发布人:Minchunlin 发布时间:2025-08-28 10:06 阅读量:875


香港将军澳机房的客户一个月前把日志平台和几个高 I/O 的服务都堆在了一台 Debian 11 的物理服务器上,原计划是“快上——先顶住流量”,结果一上就是一个月,磁盘占用节节攀升,读写延迟也开始拖慢接口。
我看着监控屏上那条逐渐“肥胖”的磁盘使用曲线,决定把这台机器的存储栈重构到 ZFS:用压缩“省”容量、用快照控版本、再用合适的记录大小和缓存把读写效率拉回来。

下面,就是那一夜我实际做过、后来在更多节点复用过的完整流程与经验。

一、现场硬件与目标

硬件清单(本次部署的真实参数)

角色 型号/规格 数量 备注
机型 1U 单路 AMD EPYC 7313P 1 16C/32T,主频稳
内存 ECC RDIMM 32GB × 4 128GB 更大的 ARC 空间更香
系统盘 Intel P4511 NVMe 1TB 1 仅装 Debian 11(ext4),数据盘分离
数据盘 12TB 7200RPM 企业级 HDD 8 RAIDZ2
SLOG Intel P4801X U.2 100GB 1(可镜像为2) 小延迟、寿命高(同步写密集时收益明显)
L2ARC Samsung PM983 1.92TB 1 可选,用于冷热分层
网卡 2 × 25GbE - 复制/同步更顺滑

达成的目标:

1)在不更换盘组的前提下,把有效容量“变多”(压缩);

2)把可恢复点细化到小时级(快照),同时控制快照膨胀;

3)对日志/OLTP/对象存储各自调整 recordsize 与 压缩算法,读写延迟下降且吞吐不受伤。

二、在 Debian 11 安装并准备好 ZFS

Debian 11(bullseye)建议开启 contrib non-free 源并安装 DKMS 版本(适配内核升级)。

开启仓库

编辑 /etc/apt/sources.list,将每行 main 后加上 contrib non-free,例如:

deb http://deb.debian.org/debian bullseye main contrib non-free
deb http://security.debian.org/debian-security bullseye-security main contrib non-free
deb http://deb.debian.org/debian bullseye-updates main contrib non-free

安装依赖与 ZFS

apt update
apt install -y ca-certificates gnupg dkms \
  linux-headers-$(uname -r) zfs-dkms zfsutils-linux

加载模块与自启动

modprobe zfs
echo zfs >> /etc/modules

坑 1:DKMS 编译失败

多半是缺少内核头文件或头文件与当前内核不匹配。先 apt install linux-headers-$(uname -r),若仍不行,升级内核与头文件保持一致,再 dkms autoinstall。

三、建池前的“必做功课”:识别盘与 4K 对齐

ZFS 的 ashift 一旦确定就不可更改。对 4K 物理扇区盘请用 ashift=12(2^12=4096)。

确认盘信息

lsblk -o NAME,SIZE,MODEL,ROTA,PHY-SEC,LOG-SEC

如果 PHY-SEC 显示 4096,或硬盘是新款企业盘/叠瓦排除后,稳妥起见直接 ashift=12。

强烈建议使用 by-id 路径创建池,避免 /dev/sdX 漂移:

ls -l /dev/disk/by-id/ | grep -i "ata\|nvme"

四、创建池:一次把关键属性选对

本例我们将 8 块 12TB 盘做 RAIDZ2(允许任意两盘故障),开启 SSD/HDD 的 autotrim、关闭 atime,默认压缩 lz4,并指定 SLOG、L2ARC。

# 1) 主池
zpool create -f \
  -o ashift=12 \
  -o autotrim=on \
  -O compression=lz4 \
  -O atime=off \
  -O xattr=sa \
  -O acltype=posixacl \
  tank \
  raidz2 \
  /dev/disk/by-id/ata-...A \
  /dev/disk/by-id/ata-...B \
  /dev/disk/by-id/ata-...C \
  /dev/disk/by-id/ata-...D \
  /dev/disk/by-id/ata-...E \
  /dev/disk/by-id/ata-...F \
  /dev/disk/by-id/ata-...G \
  /dev/disk/by-id/ata-...H

# 2) 添加低延迟 SLOG(同步写优化)
zpool add tank log /dev/disk/by-id/nvme-INTEL_P4801X...

# 3) 可选:添加 L2ARC(读缓存二级层)
zpool add tank cache /dev/disk/by-id/nvme-SAMSUNG_PM983...

什么时候 SLOG 有用?

只有 同步写(sync write) 场景(如:数据库、NFS sync、消息队列落盘策略严格)才显著受益。普通文件写大多是异步写,SLOG 不会提升速度。

五、用数据集(dataset)做“业务分仓”,分别选压缩与记录大小

把不同业务分成不同 dataset,用属性“就地生效”而不相互牵制。

# 日志(文本、JSON),高压缩比,写多读多
zfs create tank/logs
zfs set compression=zstd-3 tank/logs
zfs set recordsize=64K    tank/logs

# OLTP 数据库(以 MySQL/InnoDB 为例),随机 IO,行页 16K 更合适
zfs create tank/db
zfs set compression=lz4   tank/db          # CPU 成本低,延迟稳
zfs set recordsize=16K    tank/db
zfs set atime=off         tank/db
# 可选:更激进的日志落盘一致性
# zfs set logbias=latency tank/db

# 对象/大文件(备份仓/镜像等),顺序 IO,大块读写
zfs create tank/objects
zfs set compression=lz4   tank/objects     # 大文件常已压缩,lz4 成本最低
zfs set recordsize=1M     tank/objects

# 关键小文件集(配置/脚本等),额外副本提升读稳健
zfs create tank/configs
zfs set compression=zstd-3 tank/configs
zfs set recordsize=16K     tank/configs
zfs set copies=2           tank/configs

为什么 zstd 不“全局上”?

lz4:吞吐高、CPU 开销极小,默认首选;

zstd:压缩更高,但 CPU 更忙。适合 日志/文本、可忍受少量 CPU 时间换容量的场景。常用档位:zstd-1 ~ zstd-5,我偏爱 zstd-3 平衡点。

六、开启快照与保留策略:两种方案

方案 A:开箱即用的 zfs-auto-snapshot

apt install -y zfs-auto-snapshot
# 默认会装好 cron.hourly/daily/weekly/monthly 的策略
# 默认保留数量可通过环境变量或参数调整

优点是简单,适合先“立刻有快照”;缺点是保留策略不够精细。

方案 B:用 sanoid 精细编排(推荐)

apt install -y sanoid  # Debian 11 可用
# 编辑 /etc/sanoid/sanoid.conf

示例配置(按业务分 Dataset):

[tank/logs]
use_template=hourly_daily_weekly
recursive=no
process_children_only=no
compression=zstd-3

[tank/db]
use_template=hourly_daily_weekly
recursive=no
process_children_only=no

[tank/objects]
use_template=daily_weekly
recursive=no
process_children_only=no

# 模板
[template_hourly_daily_weekly]
hourly=24          # 最近 24 小时,每小时一份
daily=7            # 最近 7 天,每天一份
weekly=4           # 最近 4 周,每周一份
autosnap=yes
autoprune=yes

[template_daily_weekly]
daily=7
weekly=8
autosnap=yes
autoprune=yes

启用定时:

systemctl enable --now sanoid.timer

快照可见与挂载

为了手工检查/回滚更直观:

  • zfs set snapdir=visible tank 以后,.zfs/snapshot 会在挂载点里可见。

七、回滚与恢复的日常操作清单

列快照

zfs list -t snapshot

单文件回滚(从快照目录拷贝)

cp /tank/logs/.zfs/snapshot/auto-2025-08-01-1200/app.log /tank/logs/

整集回滚(危险操作,慎用)

zfs rollback tank/db@auto-2025-08-01-1200

创建/删除手工快照

zfs snapshot tank/db@pre_migration
zfs destroy  tank/db@pre_migration

八、压缩带来的“真金白银”——容量与性能评估

以下为我在这台机器上的一组参考数据(fio 与生产导出的脱敏样本),不同环境会有波动,重点看趋势与方法。

1) 压缩比(zfs get compressratio)

Dataset 压缩算法 业务类型 原始数据量 压缩后占用 压缩比
tank/logs zstd-3 文本/JSON 8.1 TB 3.2 TB 2.53×
tank/db lz4 InnoDB 页 4.4 TB 3.7 TB 1.19×
tank/objects lz4 大文件/镜像 12.0 TB 11.7 TB 1.03×

空间账本(粗算):

8×12TB RAIDZ2 原始约 96TB(十进制),剔除两盘冗余约 72TB。

以上表加权,有效空间相当于 ~92–115TB(不同业务占比下浮动),等价于少买一组盘。

2) 性能(fio,读写混合 / 可压缩与不可压缩对比)

128K 顺序写(16 并发)

压缩 测试数据 吞吐
关闭 不可压缩 1.18 GB/s
lz4 可压缩 50% 1.31 GB/s(写入字节更少)
zstd-3 可压缩 50% 1.24 GB/s(CPU 稍高但仍有净增)

4K 随机写(QD=64)

压缩 测试数据 IOPS 99线时延
关闭 不可压缩 45.2k 7.8 ms
lz4 可压缩 50% 51.7k 6.1 ms
zstd-3 可压缩 50% 49.8k 6.5 ms

解读:可压缩场景下,写更少的字节意味着更少的落盘与寻道,时延与 IOPS 都受益。CPU 开销则由 lz4 < zstd,因此我用 lz4 做通用、zstd 定点投给文本类。

九、运行期调优:把“尺子”放在业务上

ARC 大小(内存缓存)

Debian 11 上可用 /etc/modprobe.d/zfs.conf 指定:

# 例如限制 ARC 最大到 64GB(67108864 KB)
echo "options zfs zfs_arc_max=68719476736" > /etc/modprobe.d/zfs.conf
update-initramfs -u
reboot

初次上线观测 free -h 与 arc_summary.py,避免把系统挤到 swap。

recordsize 的“极简原则”

数据库:16K(贴近页大小);

日志:64K;

大文件:1M;

不要频繁改,尤其在已有大量数据后再改,收益与成本需要评估(新写入才会用新 recordsize)。

同步写策略

默认 sync=standard;

谨慎使用 sync=disabled(仅限临时批处理/临时落盘、可接受断电数据丢失);

数据库/NFS 服务搭配 SLOG 才是正道。

避免开启在线去重(dedup)

内存与 CPU 成本极高,不建议除非你有明确的去重收益模型与足够内存(动辄数百 GB)。

autotrim 与填充分配

SSD 盘池 autotrim=on;HDD 影响小但可保持开启;

尽量保持池使用率 < 80%,ZFS 空间紧张时性能明显下滑。

十、监控与日常巡检:发现问题要靠“眼缘”+“证据”

健康与冗余

zpool status
zpool status -x    # 是否一切正常

实时 IO

zpool iostat -v 1

压缩收益

zfs get compressratio,compression tank tank/logs tank/db tank/objects

ARC/L2ARC 概览

apt install -y python3-pip && pip3 install arcstat
arcstat.py 1
# 或 arc_summary.py 查看概览

快照占用

zfs list -t snapshot -o name,used,creation | sort

十一、我踩过的坑 & 机房当场修法

池重启后未自动导入

现象:开机后业务挂载点空空如也;

原因:zpool.cache 未更新到 initramfs;

修法:

zpool set cachefile=/etc/zfs/zpool.cache tank
update-initramfs -u
reboot

SLOG 用了普通 NVMe,延迟没改善反而抖

原因:SLOG 需要极低写延迟且高耐久(如 Optane/P4801X/P5800X),普通 NVMe 的 FTL 抖动会把最坏时延放大;

修法:换真正的低延迟小盘做 SLOG,或取消 SLOG 保持 sync=standard。

ashift 选错

现象:写入抖动、盘噪声大;

结论:ashift=12 是对 4K 物理扇区的保险选择。错了只能重建池,所以上线前确认一次。

zstd 开太高

现象:CPU 抢占,时延坏尾拉长;

经验:一般不超过 zstd-5,日志类 zstd-3 很稳。

快照“滋生”过多

现象:明明数据删了,空间却回不来;

原因:旧快照还在引用数据块;

修法:sanoid 的 autoprune=yes 必开,周期性审计:

zfs list -t snapshot -o name,used,refer,creation | sort -k2 -h

十二、复制与异地容灾(加分项)

这台香港节点我们做了每小时到“隔壁机柜”的 本地高速复制,每天一次到 不同运营商机房 的“冷备库”。

使用 syncoid(与 sanoid 同源):

apt install -y syncoid
# 首次全量(带压缩流)
syncoid --compress=zstd tank/logs hk2:tankbak/logs
# 周期增量(可写进 systemd timer)
syncoid --compress=zstd tank/db   hk2:tankbak/db

十三、把方法固化成“上线 checklist”

  1. ashift=12、autotrim=on、atime=off、xattr=sa、acltype=posixacl
  2. 数据集按业务拆分:recordsize 与 compression 匹配
  3. SLOG 用真低延迟盘,否则宁缺毋滥
  4. 快照:先 zfs-auto-snapshot 立刻可用,再切 sanoid 精细化
  5. zpool set cachefile + update-initramfs,确保重启自动导入
  6. ARC 边跑边调,保持系统留足余量
  7. 监控压缩收益与快照占用,定期 autoprune

附:如果你想手搓 systemd 快照(不用 sanoid)

服务文件 /etc/systemd/system/zfs-hourly-snap.service:

[Unit]
Description=Create hourly ZFS snapshots

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/zfs-snap-hourly.sh

定时器 /etc/systemd/system/zfs-hourly-snap.timer:

[Unit]
Description=Run hourly ZFS snapshots

[Timer]
OnCalendar=hourly
Persistent=true

[Install]
WantedBy=timers.target

脚本 /usr/local/sbin/zfs-snap-hourly.sh(示例):

#!/usr/bin/env bash
set -euo pipefail
stamp=$(date +'%Y%m%d-%H%M')
for ds in tank/logs tank/db tank/objects; do
  /usr/sbin/zfs snapshot "${ds}@auto-${stamp}"
  # 只保留最近24个小时快照
  /usr/sbin/zfs list -t snapshot -o name,creation -s creation | \
    awk -v ds="${ds}@" '$1 ~ "^"ds {print $1}' | head -n -24 | xargs -r /usr/sbin/zfs destroy
done

启用:

chmod +x /usr/local/sbin/zfs-snap-hourly.sh
systemctl daemon-reload
systemctl enable --now zfs-hourly-snap.timer

收尾:风还在吹,曲线终于瘦了下来

凌晨四点,我合上了那只已经被汗渍浸过边缘的笔记本。监控大屏上,磁盘占用曲线终于不再直线向上,日志数据的压缩比在 2× 徘徊;数据库的 99 线时延回到了我们能接受的红线之下。
ZFS 没有魔法,但它把“用更少的字节表达同样的数据”这件事做到了极致,还顺手给我们了一个优雅的快照时光机。
后来我又在中环和葵涌的几台机器上复刻了同样的策略,流程越来越熟练,甚至把 sanoid 的模板做成了标准件。每次调整完 recordsize 和压缩算法,再看那条“瘦身成功”的曲线,我都能想到将军澳冷通道里的那阵风——安静、有力、可预期。

TL;DR(可以当作你下次上线的最小步骤)

  • 安装:zfs-dkms zfsutils-linux,modprobe zfs
  • 建池:ashift=12 autotrim=on atime=off compression=lz4,RAIDZ2
  • 业务分仓:日志 zstd-3/64K、DB lz4/16K、大文件 lz4/1M
  • 快照:先 zfs-auto-snapshot,再上 sanoid 做保留策略
  • 监控:zpool status、zpool iostat、zfs get compressratio、arcstat.py
  • 优化:SLOG 用真低延迟盘;ARC 合理上限;快照定期 autoprune
目录结构
全文