去年有台跑数据库的机器,硬盘 SMART 报告全是 healthy,dmesg 里干干净净,RAID 卡也没报过任何错误。但三个月后业务反馈:某个订单表里有一段 JSON 莫名其妙多了一个逗号,历史记录对不上账。查了整整一天,最后是把那块盘单独拆下来 dd 一遍,才在坏道区域里读出乱七八糟的数据硬盘自己一直认为它写成功了。
这就是所谓「静默数据损坏」(silent data corruption,业内也常叫 bit rot):磁盘读回来的数据跟当初写进去的不一样,但没有任何一层报错。普通文件系统只记元数据,不给数据本身算指纹,所以它无从发现。而 ZFS 从设计上就奔着这件事去的每个数据块都带校验和,池子里还有冗余副本,于是问题从「三个月后业务炸掉」变成了「scrub 跑完当场告诉你哪块盘哪一段坏了,并且自动把它修好」。
如果你也在给服务器配存储池、担心硬盘盘龄和静默损坏,可以先看看这几家的裸金属与托管机器规格再动手:fiberstate(企业级裸机与一级数据中心托管);Abelohost(离岸托管与数据保护)
一、ext4 为什么不抓得住这类问题
ext4/xfs 这类传统文件系统,块层面做的事情是:写到哪里去、 inode 指向哪个块、目录项怎么排。这些元数据有 journal(ext4)或者日志(xfs)保护,出错能回滚。但正文的用户数据块本身,通常不存任何校验信息。RAID 1/RAID 5 卡的冗余也只保证「一份坏了我还有一份」,前提是你去读它如果数据本身在写入路径上就已经错了(比如盘的固件 bug、虚机迁移时的位翻转、内存 ECC 之外的问题),副本写过去的是同一个错数据,多个副本一起错,RAID 也救不回来。
更麻烦的是这种错误一旦写进去就是「稳定」的。你重启一次不会好,fsck 也不会报,文件大小校验和 md5 校验(如果当初存了)能发现,但多数业务根本没存。所以业界的共识很朴素:把校验和当成数据的一部分,让文件系统自己负责。ZFS 就是这么干的。
二、ZFS 的校验和到底算什么
ZFS 每个数据块(block)写盘时会同时写一份 256 位的校验和,默认算法是 fletcher4,也可以显式换成 sha256。校验和是对「块内容 + 块编号的父节点编号 + 块出生时间」一起算的,所以它还带了防伪改属性你不用 root 权限去改文件内容再重算校验和,改了就是校验不通过。读取时 ZFS 重算一遍指纹跟盘上存的对比,对不上就判定这块数据不可信。
指纹对不上之后呢?关键在 zpool 的冗余模型:
mirror(镜像):每份数据在 N 块盘上各存一份。任意一块坏了,从另一份读,自动修。适合数据库、虚机镜像这类不能丢的 IO。
raidz1 / raidz2 / raidz3:类似硬件 RAID5/6,但把校验位也用校验和保护(parity)。raidz1 相当于 RAID5,单盘故障可恢复;raidz2 相当于 RAID6,能扛两块盘同时坏;raidz3 多一层校验,代价是要更多盘才能起池。追求容量就用 raidz1 起,之后还能用 raidz2 升级(zpool upgrade)。

三、scrub:定期把所有块重算一遍指纹
光有校验和不够,因为校验和只在「你读这块数据」的时候才被验证。很多坏块躺在那儿没人读,错误就永远发现不了。ZFS 的解法是 scrub:定期把整个池子的每一个数据块完整读一遍,重新算校验和,跟盘上存的比。这就是为什么它能提前三个月抓到问题哪怕那段数据三个月没人碰过。
命令就一行:
zpool scrub tank
跑完看结果:zpool status -v tank。如果一切正常,你会看到 scrub repaired 0B,也就是「检查了这么多字节,修复了 0 字节」。如果有问题,这一行会显示修复了多少字节,以及对应盘的 errors 计数。scrub 的重点不是修数据,是确认「冗余还在」一旦你在一块盘上再坏一次,就该考虑换盘了。
我一般把 scrub 排在周末凌晨,用 systemd timer 或者直接写进 cron:
# 每月第一个周日 3 点巡检整个池子
0 3 1-7 * 0 root zpool scrub tank
注意 zpool 有个保护机制:同一时刻一个池子只能有一个 scrub 在跑,你手动再敲一次会直接被拒,不会叠加 IO 把机器拖垮。另外 raidz2 这种双校验布局,scrub 的读取量大概是全池容量的两倍多,数据量大的时候留意一下磁盘带宽。

四、真出错了:它是怎么自己把数据救回来的
我那台机器的实际情况是:scrub 报 checksum errors: 1.2K in 3 ranges on tank,涉及 sdb。zpool status 会把这几段直接标出来,同时告诉你 errors: No known data errors意思是所有坏块都已经用冗余副本补回来了,用户数据是完整的。这时你有几个选择:
1)热替换(recommended 路径):把坏盘换掉,用 zpool replace tank sdb /dev/sdc,系统会自动 resilver,把新盘上缺失的数据重新写一遍。这期间池子是降级状态(mirror 少一面、raidz2 少一盘),速度大概是平时的两三倍,raidz2 下 resilver 期间不要同时做大流量 IO,容易把降级窗口拉长。
2)软替换:盘还在线但已经不可信了,可以 zpool replace tank sdb 让系统挑一块健康的盘顶上,再 zpool detach tank sdb 把坏盘摘出去、zpool scrub tank 重扫一遍确认。注意 detach 只是摘掉不是修复,物理上还是要换新盘。
3)别忘了两件事:一是 zpool status 里的 errors 计数清零只说明这一轮没再出错,不代表盘没问题;二是换盘之后一定要再跑一次完整 scrub 确认。

五、顺手把快照和 zfs send 用起来
ZFS 的快照是写时复制(copy-on-write),几乎不额外占空间,但会钉住旧版本的数据,导致「你删了大文件,df 却没释放」。这是 ZFS 新手最常撞的坑之一其实是老版本还挂着,不是删除失败。
两个日常命令值得记住:zfs list -t snapshot 看所有快照,zfs destroy tank/data@old-snap 删掉不用的。另一个是 zpool trim tank,把 SSD 上已删除块的映射通知底层释放,配合 ashift=12 设置(建池时就要定)能让回收更及时。
另外 zfs send 可以把快照增量同步到另一台机器,做真正的异地容灾:zfs send tank/data@backup | zfs recv backup/data。快照只在本地池子里,机器整个被格式化就没了;只有 send/receive 出去的副本,才算备份。
六、什么时候值得上 ZFS,什么时候不值
值得上:数据贵、不能丢、且你有冗余余量(至少两块盘做 mirror,或者三块起 raidz1);机器内存别太抠(ZFS 的 ARC 缓存默认吃内存的一半,2G 内存的小机器建议手动限制);你能接受文件系统这套额外的抽象层。
不太值得:只有一块盘(没有冗余,ZFS 的自愈能力直接归零,反而不如 ext4 简单);1G 内存的迷你机(ARC 和元数据开销会让你很难受);只是想装个面板跑网站(宝塔那套 ext4 方案更省心)。
最后提醒一句:ZFS 的校验和能保证「你读到的数据是对的」,但保证不了「数据是最新的」。它防的是静默损坏,不是逻辑错误,也不是误删删了就是删了,快照只在池子还活着的时候有用。所以校验和 + 定期快照 + 异地 send + 真正的备份策略,这四件事是一套,缺一不可。



喜欢
顶
难过
囧
围观
无聊





