思路:
下面补充一下细节,不想看可以跳过。
探索第一阶段 btrfs scrub 中 self-healing 这个功能的底层实现。
群晖存储系统的结构为: 先用 mdadm 创建存储设备,并映射为以 md 开头的 block device;md device 作为 pv 交由 lvm 管理;lvm 在 pv 上创建 vg ,进而创建 lv;最后把 btrfs 放在 lv 上。即,btrfs over lvm over mdadm。
btrfs 会记录每个 extent 的 checksum 值用于验证这个 extent 中的数据是否完整,命令 btrfs scrub 可以触发 self-healing。在 self-healing 过程中,修复 checksum 有异常的数据需要额外的,具有正常的 checksum 的另一份数据。即,触发 self-healing 需要 btrfs 中的数据有热冗余。
比如,btrfs 在 Linux 上 btrfs 默认存储两份 metadata 用于在 metadata 损坏时修复这些损坏的 metadata,而 data 则只存了一份,所以在这种情况下,如果 data 的 checksum 出现异常则无法修复。在这个例子中,metadata 有热冗余,data 没有,所以 metadata 可以利用 self-healing 修复 corrupted metadata,而 data 不行。
需要注意: 2 盘 raid1 意味着在物理存储层中 metadata 有 4 份,data 有 2 份,但 btrfs 的 self-healing 只能看到 2 份 metadata 和 1 份 data。因为 mdadm+lvm 屏蔽了 btrfs 对于底层存储结构的感知。即使 mdadm 确实有热冗余,但 btrfs 看不到,所以 btrfs 无法使用 mdadm 中的热冗余。btrfs 能看到的、能用的只有 btrfs 存过的东西 (存过 2 份 metadata 和 1 份 data)。
继续探索第二阶段 mdadm scrub 中 mdadm 都做了什么。
我从 raid.wiki.kernel.org 找到:
下面举个例子以证明 mdadm 在 raid1 中检测到数据不一致时,mdadm scrub 无法保证修复错误数据:
下面举个例子以证明 mdadm 在 raid5/6 中检测到数据不一致时,mdadm scrub 无法保证修复错误数据:
以上两个例子都是 mdadm scrub 无法修复数据的情形。
如前文所述,跑在逻辑卷上的 btrfs 的默认配置无法保证 data extent 的完整性;而原版 mdadm 的行为,如文档所说,也不保证数据完整性。
所以结论是,对于使用公版 btrfs over lvm over mdadm 的做法来说,做 fs scrub + raid (mdadm) scrub 的成果是,修复影响 btrfs over lvm over mdadm (over LUKS) 这套组合正常运行的问题,而由于 mdadm 暴露给 btrfs 的,btrfs 能看到的数据没有变化,所以 btrfs 在 data extent (不包括 metadata extent) corruption 出现时,不能修复已损坏的 data extent,最终,用户已损坏的 data extent 保持在已损坏状态,需要用户介入。
我找不到群晖对于 btrfs 的定制部分的代码,这部分没有开源。但通过模拟数据损坏可以确定群晖对 btrfs 有修改。
后文的一些步骤与命令参考了 此文档 中的内容,但这篇文章只测了 SHR-1 (一种群晖魔改的 raid5) 而没有测 raid1,所以我自己测了一下 raid1 相关的行为。
从结果来看,群晖上的 raid1 在对抗静默错误的方面不如 raid5/6 及其变种 (SHR)。
用到的工具:
验证过程中需要注意的地方:
后面说的快照并不是 VMWare 自带的磁盘快照,而是手动复制整个 VM 目录!!!
下面模拟群晖 raid1 上的数据损坏:
dd if=/dev/zero bs=4096 count=262144 | tr '\000' '\377' > FF 生成 1G 大小的名为 FF 的文件 (文件内数据的 16 进制表示也是 'FF') 并计算 sha256sum FFsha256sum FF 可以得到与刚才相同的校验和,刷新 HxD 中硬盘 3 的视图后看到 'FE' 变回 'FF'上述流程执行完后,从 /var/log/messages 里面可以看到这样的日志:
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.533439] BTRFS warning (device dm-4): csum failed ino 262 off 1041494016 csum 3618274576 expected csum 633470483
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.533579] md3: [Self Heal] Retry sector [3182200] round [1/2] start: choose disk [0:sdf3]
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.534225] md3: [Self Heal] Retry sector [3182200] round [1/2] finished: return result to upper layer
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.537723] BTRFS warning (device dm-4): csum failed ino 262 off 1041494016 csum 3618274576 expected csum 633470483
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.537847] md3: [Self Heal] Retry sector [3182200] round [1/2] start: choose disk [0:sdf3]
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.538034] md3: [Self Heal] Retry sector [3182200] round [1/2] finished: get same result, retry next round
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.539506] md3: [Self Heal] Retry sector [3182200] round [2/2] start: choose disk [1:sdg3]
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.541169] md3: [Self Heal] Retry sector [3182200] round [2/2] finished: return result to upper layer
2023-11-05T11:26:22+08:00 tdsm kernel: [ 670.555152] BTRFS: read error corrected: ino 262 off 1041494016 (dev /dev/mapper/cachedev_0 sector 3156472)
可以判断,FF 文件在被读取时检测到损坏,并被修复。
验证结束。下面是补充内容,可以不看。
我从快照 3 开始,尝试了 3 种操作。
第一种,就是上面提到的,通过计算校验和来读取文件。
第二种,手动调用 btrfs scrub 并观察程序的行为。
执行 sudo btrfs scrub start /volume2 开始检查并修复数据,等一会 ssh 终端会回显一行:
WARNING: errors detected during scrubbing, corrected
用 sudo btrfs scrub status /volume2 可以看到有 1 个 error,和 1 个 corrected errors。
然后用 HxD 打开硬盘 3 并定位到模拟数据损坏的位置,可以看到 'FE' 又变回了 'FF'
第三种,从手动执行群晖套件 '存储空间管理' 中的 '数据清理' 功能,并观察日志和文件。结果是,我可以从 /var/log/messages 中看到确实发生了 self heal,但是群晖的日志中心没有任何提示。
此外,我又想到 mdadm raid1 的硬盘分主从,接着我就切到快照 2,在从盘上模拟静默错误,然后就有了下一节的内容。
现在恢复到快照 2 然后:
sha256sum FF 的到了相同的校验和但是可以从 HxD 中看到,硬盘 4 上,刚才被改成 'FE' 的地方还是 'FE',并没有变成 'FF'。并且 /var/log/messages 中也没有任何关于 btrfs self heal 相关的日志。
接着手动执行 sudo btrfs scrub start /volume2 。执行完成后没有从日志中找到 btrfs self heal 相关的日志,HxD 中看到硬盘 4 上的数据没有被自动修复。最后调用群晖套件 '存储空间管理' 中的 '数据清理' 功能,同样地,日志内无相关信息,HxD 中看到硬盘 4 上的数据没有被自动修复。
我推测这可能和 mdadm raid1 的读写策略有关: 让硬盘 I/O 总是尽可能地发生在 raid1 的主盘上。在这个策略下,负载较小时几乎不会有发生在从盘上的 I/O,所以从盘上的数据也就几乎不被读取,因此从盘上的静默错误就不会被发现,也就不会触发 self heal 了。
我尝试用 rsync 从别的机器向 vm 内复制数据以增加硬盘压力,从而触发从盘的 I/O,进而让 btrfs scrub 可以检测到从盘的校验和异常,但是没有成功。
最后,来都来了,模拟一下不能修复的情况吧,看看群晖有什么行为。
现在恢复到快照 1 然后模拟数据无法修复的情况:
dd if=/dev/zero bs=4096 count=262144 | tr '\000' '\364' > F6 创建一个 F4 文件很明显,两个校验和不一样。然后再看 /var/log/messages 里面的日志:
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.847997] checksum error found by scrub at logical 4674465792 on dev /dev/mapper/cachedev_0, mirror = 0, metadata = 0
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.848275] md3: [Self Heal] Retry sector [9696216] round [1/2] start: choose disk [0:sdf3]
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.848824] md3: [Self Heal] Retry sector [9696216] round [1/2] finished: return result to upper layer
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.849202] md3: [Self Heal] Retry sector [9696216] round [1/2] start: choose disk [0:sdf3]
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.849786] md3: [Self Heal] Retry sector [9696216] round [1/2] finished: get same result, retry next round
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.850237] md3: [Self Heal] Retry sector [9696216] round [2/2] start: choose disk [1:sdg3]
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.850878] md3: [Self Heal] Retry sector [9696216] round [2/2] finished: return result to upper layer
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.851330] md3: [Self Heal] Retry sector [9696216] round [2/2] start: choose disk [1:sdg3]
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.851957] md3: [Self Heal] Retry sector [9696216] round [2/2] finished: get same result, retry next round
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.852403] md3: [Self Heal] Retry sector [9696216] round [5/2] error: cannot find a suitable device, bio sector length [8], request_cnt [3]
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.853322] failed to repair csum (scrub) at logical 4674465792 on dev /dev/mapper/cachedev_0, mirror = 0, metadata = 0
2023-11-05T16:23:43+08:00 tdsm kernel: [ 5313.853913] BTRFS: (null) at logical 4674465792 on dev /dev/mapper/cachedev_0, sector 9670488, root 257, inode 267, offset 1073737728, length 4096, links 1 (path: F4)
日志显示无法修复。然后可以从 HxD 里面看到:
群晖套件的日志中心里还可以找到一条日志: Checksum mismatch on file [/volume2/lol/F4].
我在这里的快照 3 之后尝试了 3 种操作。
第一种,就是上面提到的用 sha256sum 去读损坏的文件。
第二种,建立快照后执行 sudo btrfs scrub start <mount-point> 之后,ssh 的回显会输出 ERROR: there are uncorrectable errors 但群晖的日志中心不会看到警告。
但只要调用群晖套件 '存储空间管理' 中的 '数据清理' 功能,群晖的日志中心就会有警告。
'数据清理' 功能启动后不断地用 cat /proc/mdstat 查看 mdadm 的状态,发现 mdadm scrub 始终没有启动。
第三种,多次执行修复操作看会发生什么。如果用户没有手动修复的话,每次执行 btrfs scrub 或 '数据清理' 都会提示 F4 文件损坏。但在修复之前,硬盘 3 4 上面损坏的数据会保持原样。
下面只说我观察到的 DSM 的行为,继续用之前创建的 raid1 存储池来观察。从观察结果来看 mdadm scrub 的行为并没有改变。
切到快照 1 (只建好存储池和共享文件夹) 开始操作:
dd if=/dev/zero bs=4096 count=262144 | tr '\000' '\370' > F8 创建 F8 测试文件cat /sys/block/md3/md/mismatch_cnt 确认不匹配的 block 的数量为 0
echo repair > /sys/block/md3/md/sync_action 再 echo check > /sys/block/md3/md/sync_action 修复问题并刷新 mismatch_cnt 计数,保证 raid 干净之后再做下一步从快照 3 开始 (以下每一点开始时都会恢复到快照 3 初始状态):
cat /proc/mdstat 查看 mdadm 的状态,发现 mdadm scrub 始终没有启动echo repair > /sys/block/md3/md/sync_action
btrfs scrub start /volume2切到快照 1 (只建好存储池和共享文件夹) 开始操作:
dd if=/dev/zero bs=4096 count=262144 | tr '\000' '\370' > F8 创建 F8 测试文件cat /sys/block/md3/md/mismatch_cnt 确认不匹配的 block 的数量为 0
echo repair > /sys/block/md3/md/sync_action 再 echo check > /sys/block/md3/md/sync_action 修复问题并刷新 mismatch_cnt 计数,保证 raid 干净之后再做下一步从快照 3 开始 (以下每一点开始时都会恢复到快照 3 初始状态):
cat /proc/mdstat 发现 mdadm scrub 始终没有启动cat /sys/block/md3/md/mismatch_cnt 始终显示值为 0btrfs scrub start /volume2 && cat /proc/mdstat:
cat /proc/mdstat 查看 mdadm 的状态,发现 mdadm scrub 始终没有启动cat /sys/block/md3/md/mismatch_cnt 始终显示值为 0echo repair > /sys/block/md3/md/sync_action
cat /proc/mdstat 发现 mdadm scrub 启动,等待命令结束cat /sys/block/md3/md/mismatch_cnt 发现其值为 128 (1 page size)根据观察到的现象:
我猜测群晖可能修改了 btrfs scrub 的行为,并在 mdadm 中添加了供 btrfs scrub 调用的接口来实现 btrfs scrub 利用 mdadm 中数据的功能。
结论是,群晖目前使用的存储方案可以保证数据完整性。
(个人认为目前群晖整个硬件上影响数据完整性的,可以控制的因素就在于没有 ecc 内存了)
可以将以下 shell 脚本设置为定时任务:
bond0_hash_policy_is_layer34(){
grep -q 'layer3+4 1' /sys/class/net/bond0/bonding/xmit_hash_policy
return $?
}
set_bond0_hash_policy_layer34(){
echo 'layer3+4 1' > /sys/class/net/bond0/bonding/xmit_hash_policy
bond0_hash_policy_is_layer34
return $?
}
bond0_hash_policy_is_layer34 || set_bond0_hash_policy_layer34
群晖上 Open vSwitch 上 bonding 的链路聚合种类有「平衡-SLB」(balance-slb) 和「平衡-TCP」(balance-tcp) 两种。主备模式不需要交换机参与且过于简单,不展开。
平衡-SLB,不需要交换机参与。
平衡-TCP 需要交换机也对应配置 802.3ad LACP。有些交换机可以修改链路聚合使用的负载均衡算法,所以要让两边使用相同的算法从而提升性能。
平衡-TCP 使用的负载均衡算法会使用 L2 L3 和 L4 的数据,依据是 OVS 的配置文件 vswitch.xml 中对 balance-tcp 的描述:
Balances flows among slaves based on L2, L3, and L4 protocol information such as destination MAC address, IP address, and TCP port.
不过,一份 2017 年的 OVS 开发邮件列表补丁指出,在实际实现中 L2 字段可能并未被使用。该补丁提到:
L2 fields are not used in userspace hash action... In kernel datapath RSS (which is not include L2 by default for most of the NICs) was used from the beginning.
这意味着,尽管文档写的是 L2+L3+L4,但在许多实际场景中,尤其是使用网卡 RSS 卸载时,哈希计算可能主要依赖 L3 和 L4 信息(IP 地址和端口号),L2 的 MAC 地址不参与或很少参与。
无论细节如何,关键点在于 Balance-TCP 的负载均衡使用了 IP 和端口信息(L3/L4)。因此,在交换机上将 transmit-hash-policy 设置为 layer3+4 是完全正确且匹配的做法。这样两端都能基于 IP 和端口来区分不同的数据流,从而让多个连接有机会被分散到不同的物理网口上。
这个功能由文件系统中时间标记 (time flag) 中的 atime 实现。
Unix 文件系统中有三种时间标记:
下面的命令可以方便地查看这三个时间标记:
#!/bin/bash
echo "ctime: $(ls -lc file | awk '{print $6, $7, $8}')"
echo "atime: $(ls -lu file | awk '{print $6, $7, $8}')"
echo "mtime: $(ls -l file | awk '{print $6, $7, $8}')"
exit 0;
这个功能有三个选项可选,这三个选项分别代表三种 atime 的更新频率:
Linux 文件系统没有这三个频率可选,只有开启 (默认) 和关闭 (挂载文件系统时指定 noatime 选项) 两种情况。群晖应该是对文件系统实现做了一些修改才实现了这个根据频率更新 atime 的功能。
经测试,在文件被访问 (即 atime 被更新) 后,重启系统接着立即再次访问该文件,atime 在本轮更新周期结束之前不会再被更改。可见与 atime 更新有关的数据会被持久化到硬盘上。
这个功能开关旁边的注解是: 新的压缩规则将仅应用于新添加的文件,已有文件不会受到影响 。
它意味着,只有在文件压缩功能开启期间被写入的数据 (文件) 才会被压缩。在功能开启之前和关闭之后,被写入的文件不会被压缩。
同时,群晖不会主动对已经存在于存储空间中的数据进行压缩和解压缩。
即,如果功能开启前,存储空间已经有数据了,那么功能开启之后,群晖不会主动对已在存储空间中的、未被压缩的数据 (统称 A) 进行压缩;如果功能被关闭,群晖也不会主动对在功能开启期间被写入并被压缩的数据 (统称 B) 进行解压缩操作。
这意味着,一个共享文件夹内,已被压缩和未被压缩的数据可以同时存在。
但是,如果功能开启期间,用户对 A 有了写操作,那么 A 会被压缩并保存在存储空间中;如果功能关闭之后,用户对 B 有了写操作,那么 B 会被解压缩并保存在存储空间中。
注: 以上列举的操作都发生在同一个共享文件夹中。
群晖的不断电系统使用了 NUT 作为管理工具。它也可以作为 nut-client 连接到其他的 nut-server。
但是需要 nut-server 使用默认的通信端口,将 UPS 名称设置为 UPS ,并新建一个用户名为 monuser 密码为 secret 的用户。
即,在 nut-server 所在机器上的 ups.conf 中有如下配置:
# 注意 UPS 名称必须是 'UPS' 才行
[ups]
driver = "..."
port = "auto"
vendorid = "..."
...
在 nut-server 所在机器上的 upsd.users 文件中有如下用户:
[monuser]
password = secret
upsmon slave
可以用 SSH 连接到群晖之后,执行 sudo upsc ups@<nut-server-ip> 来检查群晖能否正确与 nut-server 通信。
最后在群晖的不断电系统的 UPS 类型 中选择 Synology 不断电系统服务器 ,然后填入 nut-server 的 IP 即可。
该问题通常出现在小屏或高缩放率设备上,比如主菜单中一页只能显示 3 行图标,只有这前 3 行图标可以被正确排列。
在使用一个大的显示器打开 DSM 后,一屏就可以显示所有的内容,此时可以正确拖动所有的图标。
群晖的技术支持说,通过 Ctrl + 鼠标滚轮调整浏览器缩放从而让主菜单的一页可以显示更多图标,然后就可以正确拖动排列了。
存储池有任务在运行时,比如修改 raid 类型,可以删除存储池的缓存,但不可以为存储池配置缓存。