块层缓存 (bcache)¶
假设你有一个大型的、速度慢的 raid 6,以及一到三个 ssd。 如果你能将它们用作缓存,那岂不是很好……因此有了 bcache。
- bcache wiki 可以在以下网址找到:
- 这是 bcache-tools 的 git 仓库
https://git.kernel.org/pub/scm/linux/kernel/git/colyli/bcache-tools.git/
- 最新的 bcache 内核代码可以从主线 Linux 内核中找到
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/
它围绕 SSD 的性能特征而设计 - 它只分配擦除块大小的 bucket,并且它使用混合 btree/日志来跟踪缓存的范围(可以是单个扇区到 bucket 大小的任何位置)。 它的设计目标是不惜一切代价避免随机写入; 它会按顺序填充一个擦除块,然后在重复使用之前发出丢弃命令。
支持直写和回写缓存。 回写默认为关闭,但可以在运行时任意打开和关闭。 Bcache 竭尽全力保护您的数据 - 它可以可靠地处理非正常关机。(它甚至没有正常关机的概念; bcache 只有在写入完成并存储在稳定存储上后才会返回)。
回写缓存可以使用大部分缓存来缓冲写入 - 将脏数据写入到后备设备始终是按顺序完成的,从索引的开始到结束进行扫描。
由于随机 IO 是 SSD 的优势,因此缓存大型顺序 IO 通常不会有太多好处。 Bcache 检测顺序 IO 并跳过它; 它还会保留每个任务的 IO 大小的滚动平均值,只要平均值高于截止值,它就会跳过来自该任务的所有 IO - 而不是在每次寻道后缓存前 512k。 因此,备份和大型文件复制应该完全绕过缓存。
如果闪存上发生数据 IO 错误,它将尝试通过从磁盘读取或使缓存条目无效来恢复。 对于无法恢复的错误(元数据或脏数据),缓存将自动禁用; 如果缓存中存在脏数据,它首先会禁用回写缓存,并等待所有脏数据刷新。
入门: 您需要 bcache-tools 仓库中的 bcache util。 缓存设备和后备设备都必须在使用前格式化
bcache make -B /dev/sdb
bcache make -C /dev/sdc
bcache make 具有同时格式化多个设备的能力 - 如果您同时格式化后备设备和缓存设备,则不必手动附加
bcache make -B /dev/sda /dev/sdb -C /dev/sdc
如果您的 bcache-tools 没有更新到最新版本并且没有统一的 bcache 实用程序,您可以使用旧的 make-bcache 实用程序来格式化具有相同 -B 和 -C 参数的 bcache 设备。
bcache-tools 现在附带 udev 规则,并且 bcache 设备会立即被内核识别。 如果没有 udev,您可以手动注册设备,如下所示
echo /dev/sdb > /sys/fs/bcache/register
echo /dev/sdc > /sys/fs/bcache/register
注册后备设备会使 bcache 设备出现在 /dev 中; 您现在可以格式化它并像往常一样使用它。 但是第一次使用新的 bcache 设备时,它将以透传模式运行,直到您将其附加到缓存。 如果您考虑稍后使用 bcache,建议将所有速度较慢的设备设置为没有缓存的 bcache 后备设备,您可以选择稍后添加缓存设备。 请参阅下面的“附加”部分。
设备显示为
/dev/bcache<N>
以及(使用 udev)
/dev/bcache/by-uuid/<uuid>
/dev/bcache/by-label/<label>
入门
mkfs.ext4 /dev/bcache0
mount /dev/bcache0 /mnt
您可以通过 /sys/block/bcache<N>/bcache 中的 sysfs 控制 bcache 设备。 您还可以通过 /sys/fs//bcache/<cset-uuid>/ 控制它们。
缓存设备作为集合进行管理; 尚不支持每个集合多个缓存,但将来可以镜像元数据和脏数据。 您的新缓存集合显示为 /sys/fs/bcache/<UUID>
附加¶
注册缓存设备和后备设备后,必须将后备设备附加到您的缓存集合才能启用缓存。 将后备设备附加到缓存集合的方式如下,缓存集合的 UUID 位于 /sys/fs/bcache 中
echo <CSET-UUID> > /sys/block/bcache0/bcache/attach
这只需要做一次。 下次重新启动时,只需重新注册所有 bcache 设备即可。 如果后备设备在某个地方的缓存中有数据,则在缓存显示之前不会创建 /dev/bcache<N> 设备 - 如果您启用了回写缓存,这一点尤为重要。
如果您正在启动并且您的缓存设备消失并且永远不会再回来,您可以强制运行后备设备
echo 1 > /sys/block/sdb/bcache/running
(您需要使用 /sys/block/sdb(或您后备设备的名称),而不是 /sys/block/bcache0,因为 bcache0 尚不存在。如果您使用的是分区,则 bcache 目录将位于 /sys/block/sdb/sdb2/bcache)
如果将来出现,后备设备仍将使用该缓存集合,但所有缓存的数据都将失效。 如果缓存中有脏数据,请不要指望文件系统可以恢复 - 您将遇到大规模的文件系统损坏,尽管 ext4 的 fsck 确实创造了奇迹。
错误处理¶
Bcache 尝试透明地处理与缓存设备之间的 IO 错误,而不会影响正常运行; 如果它看到太多错误(阈值是可配置的,默认为 0),它会关闭缓存设备并将所有后备设备切换到透传模式。
对于从缓存的读取,如果发生错误,我们只需从后备设备重试读取。
对于直写写入,如果写入缓存时发生错误,我们只需切换到使缓存中该 lba 处的数据无效(即,我们对绕过缓存的写入执行的操作相同)
对于回写写入,我们目前将该错误传递回文件系统/用户空间。 这可以改进 - 我们可以将其作为跳过缓存的写入重试,这样我们就不必返回写入错误。
当我们分离时,我们首先尝试刷新所有脏数据(如果我们以回写模式运行)。 但是,如果它无法读取某些脏数据,则目前不会执行任何智能操作。
操作方法/食谱¶
使用丢失的缓存设备启动 bcache
如果注册后备设备没有帮助,它已经存在,您只需强制它在没有缓存的情况下运行
host:~# echo /dev/sdb1 > /sys/fs/bcache/register
[ 119.844831] bcache: register_bcache() error opening /dev/sdb1: device already registered
接下来,如果存在缓存设备,请尝试注册它。 但是,如果它不存在,或者由于某种原因注册失败,您仍然可以在没有缓存的情况下启动 bcache,如下所示
host:/sys/block/sdb/sdb1/bcache# echo 1 > running
请注意,如果您以回写模式运行,这可能会导致数据丢失。
Bcache 找不到其缓存
host:/sys/block/md5/bcache# echo 0226553a-37cf-41d5-b3ce-8b1e944543a8 > attach [ 1933.455082] bcache: bch_cached_dev_attach() Couldn't find uuid for md5 in set [ 1933.478179] bcache: __cached_dev_store() Can't attach 0226553a-37cf-41d5-b3ce-8b1e944543a8 [ 1933.478179] : cache set not found
在这种情况下,缓存设备只是未在启动时注册或消失后又重新出现,需要(重新)注册
host:/sys/block/md5/bcache# echo /dev/sdh2 > /sys/fs/bcache/register
损坏的 bcache 在设备注册时导致内核崩溃
这永远不应该发生。 如果确实发生,那么您就发现了一个错误! 请将其报告给 bcache 开发列表:linux-bcache@vger.kernel.org
请务必提供尽可能多的信息,包括内核 dmesg 输出(如果可用),以便我们提供帮助。
在没有 bcache 的情况下恢复数据
如果内核中没有 bcache,则后备设备上的文件系统仍然可在 8KiB 偏移处使用。 因此,可以通过使用 --offset 8K 创建的后备设备的 loopdev,或者在最初使用 bcache make 格式化 bcache 时由 --data-offset 定义的任何值。
例如
losetup -o 8192 /dev/loop0 /dev/your_bcache_backing_dev
这应该在 /dev/loop0 中呈现您未修改的后备设备数据
如果您的缓存处于直写模式,则可以安全地丢弃缓存设备而不会丢失数据。
擦除缓存设备
host:~# wipefs -a /dev/sdh2
16 bytes were erased at offset 0x1018 (bcache)
they were: c6 85 73 f6 4e 1a 45 ca 82 65 f5 7f 48 ba 6d 81
在重新启动并启用 bcache 后,您将重新创建缓存并附加它
host:~# bcache make -C /dev/sdh2
UUID: 7be7e175-8f4c-4f99-94b2-9c904d227045
Set UUID: 5bc072a8-ab17-446d-9744-e247949913c1
version: 0
nbuckets: 106874
block_size: 1
bucket_size: 1024
nr_in_set: 1
nr_this_dev: 0
first_bucket: 1
[ 650.511912] bcache: run_cache_set() invalidating existing data
[ 650.549228] bcache: register_cache() registered cache device sdh2
使用丢失的缓存启动后备设备
host:/sys/block/md5/bcache# echo 1 > running
附加新缓存
host:/sys/block/md5/bcache# echo 5bc072a8-ab17-446d-9744-e247949913c1 > attach
[ 865.276616] bcache: bch_cached_dev_attach() Caching md5 as bcache0 on set 5bc072a8-ab17-446d-9744-e247949913c1
删除或替换缓存设备
host:/sys/block/sda/sda7/bcache# echo 1 > detach [ 695.872542] bcache: cached_dev_detach_finish() Caching disabled for sda7 host:~# wipefs -a /dev/nvme0n1p4 wipefs: error: /dev/nvme0n1p4: probing initialization failed: Device or resource busy Ooops, it's disabled, but not unregistered, so it's still protected
我们需要去注销它
host:/sys/fs/bcache/b7ba27a1-2398-4649-8ae3-0959f57ba128# ls -l cache0
lrwxrwxrwx 1 root root 0 Feb 25 18:33 cache0 -> ../../../devices/pci0000:00/0000:00:1d.0/0000:70:00.0/nvme/nvme0/nvme0n1/nvme0n1p4/bcache/
host:/sys/fs/bcache/b7ba27a1-2398-4649-8ae3-0959f57ba128# echo 1 > stop
kernel: [ 917.041908] bcache: cache_set_free() Cache set b7ba27a1-2398-4649-8ae3-0959f57ba128 unregistered
现在我们可以擦除它
host:~# wipefs -a /dev/nvme0n1p4
/dev/nvme0n1p4: 16 bytes were erased at offset 0x00001018 (bcache): c6 85 73 f6 4e 1a 45 ca 82 65 f5 7f 48 ba 6d 81
dm-crypt 和 bcache
首先设置未加密的 bcache,然后在 /dev/bcache<N> 上安装 dmcrypt。 这将比您对后备设备和缓存设备都进行 dmcrypt,然后在上面安装 bcache 速度更快。[基准测试?]
停止/释放已注册的 bcache 以进行擦除和/或重新创建
假设您需要释放所有 bcache 引用,以便您可以运行 fdisk 并重新注册已更改的分区表,如果其上还有任何活动的后备或缓存设备,这将不起作用
它是否存在于 /dev/bcache* 中? (有时不会)
如果是,那就很容易了
host:/sys/block/bcache0/bcache# echo 1 > stop
但是如果您的后备设备消失了,这将不起作用
host:/sys/block/bcache0# cd bcache bash: cd: bcache: No such file or directory
在这种情况下,您可能需要注销引用此 bcache 的 dmcrypt 块设备才能释放它
host:~# dmsetup remove oldds1 bcache: bcache_device_free() bcache0 stopped bcache: cache_set_free() Cache set 5bc072a8-ab17-446d-9744-e247949913c1 unregistered
这会导致后备 bcache 从 /sys/fs/bcache 中删除,然后可以重复使用它。 这对于任何块设备堆叠都是如此,其中 bcache 是较低的设备。
在其他情况下,您也可以在 /sys/fs/bcache/ 中查找
host:/sys/fs/bcache# ls -l */{cache?,bdev?} lrwxrwxrwx 1 root root 0 Mar 5 09:39 0226553a-37cf-41d5-b3ce-8b1e944543a8/bdev1 -> ../../../devices/virtual/block/dm-1/bcache/ lrwxrwxrwx 1 root root 0 Mar 5 09:39 0226553a-37cf-41d5-b3ce-8b1e944543a8/cache0 -> ../../../devices/virtual/block/dm-4/bcache/ lrwxrwxrwx 1 root root 0 Mar 5 09:39 5bc072a8-ab17-446d-9744-e247949913c1/cache0 -> ../../../devices/pci0000:00/0000:00:01.0/0000:01:00.0/ata10/host9/target9:0:0/9:0:0:0/block/sdl/sdl2/bcache/
设备名称将显示哪个 UUID 相关,cd 进入该目录并停止缓存
host:/sys/fs/bcache/5bc072a8-ab17-446d-9744-e247949913c1# echo 1 > stop
这将释放 bcache 引用,并允许您将该分区用于其他目的。
性能故障排除¶
Bcache 有很多配置选项和可调参数。 默认值旨在对典型的桌面和服务器工作负载是合理的,但它们不是您在进行基准测试时获得最佳数字所需的。
后备设备对齐
bcache 中的默认元数据大小为 8k。 如果您的后备设备基于 RAID,请务必使用 bcache make --data-offset 按步幅宽度的倍数对齐它。 如果您打算将来扩展磁盘阵列,请将一系列素数乘以您的 raid 条带大小,以获得您想要的磁盘倍数。
例如: 如果您的条带大小为 64k,则以下偏移量将为许多常见的 RAID5 数据盘片数提供对齐
64k * 2*2*2*3*3*5*7 bytes = 161280k该空间被浪费了,但仅需 157.5MB,您就可以将 RAID 5 卷增长到以下数据盘片数,而无需重新对齐
3,4,5,6,7,8,9,10,12,14,15,18,20,21 ...写入性能不佳
如果写入性能不符合您的预期,您可能希望以回写模式运行,这不是默认设置(不是因为缺乏成熟度,而是因为在回写模式下,如果您的 SSD 出现问题,您将会丢失数据)
# echo writeback > /sys/block/bcache0/bcache/cache_mode性能不佳,或流量没有按预期流向 SSD
默认情况下,bcache 不会缓存所有内容。 它会尝试跳过顺序 IO - 因为您真正想要缓存的是随机 IO,并且如果您复制一个 10 GB 的文件,您可能不希望它将 10 GB 的随机访问数据从缓存中推出。
但是,如果您想对从缓存的读取进行基准测试,并且您从 fio 写入一个 8 GB 的测试文件开始 - 因此您想禁用它
# echo 0 > /sys/block/bcache0/bcache/sequential_cutoff要将其设置回默认值 (4 mb),请执行
# echo 4M > /sys/block/bcache0/bcache/sequential_cutoff流量仍然流向盘片/仍然遇到缓存未命中
在现实世界中,SSD 并不总是能跟上磁盘 - 尤其是对于速度较慢的 SSD,一个 SSD 缓存多个磁盘,或者主要是顺序 IO。 因此,您想避免受到 SSD 的限制,并使其减慢所有速度。
为避免这种情况,bcache 会跟踪到缓存设备的延迟,如果延迟超过阈值,它会逐渐限制流量(它通过降低顺序绕过来实现这一点)。
如果您需要,可以通过将阈值设置为 0 来禁用此功能
# echo 0 > /sys/fs/bcache/<cache set>/congested_read_threshold_us # echo 0 > /sys/fs/bcache/<cache set>/congested_write_threshold_us读取的默认值为 2000 us(2 毫秒),写入的默认值为 20000。
仍然遇到缓存未命中,但数据相同
有时会让人困惑的最后一个问题实际上是一个旧的错误,这是由于缓存未命中时处理缓存一致性的方式造成的。 如果 btree 节点已满,则缓存未命中将无法插入新数据的密钥,并且数据将不会写入缓存。
实际上,这不是问题,因为一旦有写入发生,就会导致 btree 节点被拆分,并且您几乎不需要写入流量就可以使其不明显(特别是由于 bcache 的 btree 节点很大并且索引了设备的大区域)。 但是,当您进行基准测试时,如果您尝试通过读取大量数据来预热缓存并且没有其他流量 - 这可能是一个问题。
解决方案: 通过执行写入操作来预热缓存,或者使用测试分支(那里有一个针对该问题的修复)。
Sysfs - 后备设备¶
可在 /sys/block/<bdev>/bcache、/sys/block/bcache*/bcache 和(如果已连接)/sys/fs/bcache/<cset-uuid>/bdev* 中找到
- attach
将缓存集合的 UUID 回显到此文件以启用缓存。
- cache_mode
可以是 writethrough、writeback、writearound 或 none 之一。
- clear_stats
写入此文件会重置运行总计统计信息(而不是每天/每小时/每 5 分钟衰减的版本)。
- detach
写入此文件以从缓存集合中分离。 如果缓存中有脏数据,将首先刷新它。
- dirty_data
缓存中此后备设备的脏数据量。 与缓存集合的版本不同,会持续更新,但可能略有偏差。
- label
底层设备的名称。
- readahead
应执行的预读大小。 默认为 0。如果设置为例如 1M,它将向上舍入缓存未命中的读取大小,但不会重叠现有缓存条目。
- running
如果 bcache 正在运行(即 /dev/bcache 设备是否存在,它是处于透传模式还是缓存模式),则为 1。
- sequential_cutoff
一旦超过此阈值,顺序 IO 将绕过缓存; 将跟踪最近的 128 个 IO,因此即使并非一次全部完成,也可以检测到顺序 IO。
- sequential_merge
如果非零,bcache 会保留提交的最后 128 个请求的列表,以与所有新请求进行比较,以确定哪些新请求是先前请求的顺序延续,以确定顺序截止。 如果顺序截止值大于任何单个请求的最大可接受顺序大小,则这是必要的。
- state
后备设备可以处于四种不同的状态之一
no cache: 从未连接到缓存集合。
clean: 缓存集合的一部分,并且没有缓存的脏数据。
dirty: 缓存集合的一部分,并且有缓存的脏数据。
inconsistent: 当有脏数据被缓存但缓存集合不可用时,用户强制运行了后备设备; 后备设备上的任何数据可能都已损坏。
- stop
写入此文件以关闭 bcache 设备并关闭后备设备。
- writeback_delay
当脏数据写入缓存并且之前不包含任何脏数据时,在启动回写之前等待一些秒数。 默认为 30。
- writeback_percent
如果非零,bcache 会尝试通过限制后台回写并使用 PD 控制器平稳地调整速率,来保持大约此百分比的缓存为脏。
- writeback_rate
速率,以每秒扇区数计 - 如果 writeback_percent 为非零,则后台回写将限制为此速率。 由 bcache 持续调整,但也可能由用户设置。
- writeback_running
如果关闭,则根本不会发生脏数据的回写。 脏数据仍将添加到缓存中,直到它几乎已满; 仅用于基准测试。 默认为开启。
Sysfs - 后备设备统计信息¶
这些数字有目录,用于运行总计,以及过去一天、一小时和 5 分钟内衰减的版本; 它们也会在缓存集合目录中进行聚合。
- bypassed
已绕过缓存的 IO 量(读取和写入)
- cache_hits, cache_misses, cache_hit_ratio
命中和未命中是根据 bcache 看到的每个单独 IO 进行计数的; 部分命中被计为未命中。
- cache_bypass_hits, cache_bypass_misses
仍然会计算打算跳过缓存的 IO 的命中和未命中,但在此处将其分解。
- cache_miss_collisions
计算了从缓存未命中将数据插入缓存的情况,但与写入竞争并且数据已经存在的情况(通常为 0,因为缓存未命中的同步已重写)
Sysfs - 缓存集合¶
可在 /sys/fs/bcache/<cset-uuid> 中找到
- average_key_size
btree 中每个密钥的平均数据量。
- bdev<0..n>
指向每个连接的后备设备的符号链接。
- block_size
缓存设备的块大小。
- btree_cache_size
btree 缓存当前使用的内存量
- bucket_size
Bucket 的大小
- cache<0..n>
指向组成此缓存集合的每个缓存设备的符号链接。
- cache_available_percent
不包含脏数据并且可能用于回写的缓存设备百分比。 这并不意味着此空间未用于干净的缓存数据; 未使用的统计信息(在 priority_stats 中)通常低得多。
- clear_stats
清除与此缓存关联的统计信息
- dirty_data
缓存中的脏数据量(在垃圾回收运行时更新)。
- flash_vol_create
将大小回显到此文件(以人类可读的单位,k/M/G)会创建一个由缓存集合支持的精简配置卷。
- io_error_halflife, io_error_limit
这些确定了我们在禁用缓存之前接受的错误数量。 每个错误都会按半衰期(以 # ios 计)衰减。 如果衰减的计数达到 io_error_limit,则会写出脏数据并禁用缓存。
- journal_delay_ms
日志写入将延迟最多这么多的毫秒,除非缓存刷新发生得更快。 默认为 100。
- root_usage_percent
正在使用的根 btree 节点的百分比。 如果此值过高,则节点将拆分,从而增加树的深度。
- stop
写入此文件以关闭缓存集合 - 等待直到所有连接的后备设备都已关闭。
- tree_depth
btree 的深度(单节点 btree 的深度为 0)。
- unregister
分离所有后备设备并关闭缓存设备; 如果存在脏数据,它将禁用回写缓存并等待刷新。
Sysfs - 缓存集合内部¶
此目录还公开了许多内部操作的计时,具有单独的文件用于平均持续时间、平均频率、上次出现时间和最大持续时间:垃圾回收、btree 读取、btree 节点排序和 btree 拆分。
- active_journal_entries
比索引更新的日志条目的数量。
- btree_nodes
btree 中的总节点数。
- btree_used_percent
正在使用的 btree 的平均分数。
- bset_tree_stats
有关辅助搜索树的统计信息
- btree_cache_max_chain
btree 节点缓存的哈希表中最长的链
- cache_read_races
计算了当从缓存中读取数据时,bucket 被重用并失效的实例 - 即,在读取完成后,指针已过时。 发生这种情况时,将从后备设备重新读取数据。
- trigger_gc
写入此文件会强制运行垃圾回收。
Sysfs - 缓存设备¶
可在 /sys/block/<cdev>/bcache 中找到
- block_size
最小写入粒度 - 应与硬件扇区大小匹配。
- btree_written
所有 btree 写入的总和,以(千/兆/吉)字节计
- bucket_size
Bucket 的大小
- cache_replacement_policy
可以是 lru、fifo 或 random 之一。
- discard
布尔值; 如果开启,则在重用每个 bucket 之前,将向其发出丢弃/TRIM 命令。 默认为关闭,因为 SATA TRIM 是一个非排队命令(因此速度较慢)。
- freelist_percent
空闲列表的大小,以 nbuckets 的百分比表示。 可以写入以增加保留在空闲列表上的 bucket 数量,从而让您在运行时人为地减小缓存的大小。 主要用于测试目的(即,测试不同大小的缓存如何影响您的命中率),但由于 bucket 在移动到空闲列表时会被丢弃,因此还可以通过有效地为其提供更多保留空间来简化 SSD 的垃圾回收。
- io_errors
发生的错误数,按 io_error_halflife 衰减。
- metadata_written
所有非数据写入的总和(btree 写入和所有其他元数据)。
- nbuckets
此缓存中的总 bucket 数
- priority_stats
有关缓存中数据最近访问时间的统计信息。 这可以揭示您的工作集大小。 Unused 是不包含任何数据的缓存的百分比。 Metadata 是 bcache 的元数据开销。 Average 是缓存 bucket 的平均优先级。 Next 是一个量化列表,其中包含每个优先级的优先级阈值。
- written
已写入缓存的所有数据的总和; 与 btree_written 进行比较可以得出 bcache 中的写入膨胀量。