Author 晓兵

weixin: ssbandjl

公众号: 云原生云

云原生云

A block layer cache (bcache)

What is bcache?

Bcache is a Linux kernel block layer cache. It allows one or more fast disk drives such as flash-based solid state drives (SSDs) to act as a cache for one or more slower hard disk drives.

Hard drives are cheap and big, SSDs are fast but small and expensive. Wouldn’t it be nice if you could transparently get the advantages of both? With Bcache, you can have your cake and eat it too.

Bcache patches for the Linux kernel allow one to use SSDs to cache other block devices. It’s analogous to L2Arc for ZFS, but Bcache also does writeback caching (besides just write through caching), and it’s filesystem agnostic. It’s designed to be switched on with a minimum of effort, and to work well without configuration on any setup. By default it won’t cache sequential IO, just the random reads and writes that SSDs excel at. It’s meant to be suitable for desktops, servers, high end storage arrays, and perhaps even embedded.

The design goal is to be just as fast as the SSD and cached device (depending on cache hit vs. miss, and writethrough vs. writeback writes) to within the margin of error. It’s not quite there yet, mostly for sequential reads. But testing has shown that it is emphatically possible, and even in some cases to do better - primarily random writes.

It’s also designed to be safe. Reliability is critical for anything that does writeback caching; if it breaks, you will lose data. Bcache is meant to be a superior alternative to battery backed up raid controllers, thus it must be reliable even if the power cord is yanked out. It won’t return a write as completed until everything necessary to locate it is on stable storage, nor will writes ever be seen as partially completed (or worse, missing) in the event of power failure. A large amount of work has gone into making this work efficiently.

Bcache is designed around the performance characteristics of SSDs. It’s designed to minimize write inflation to the greatest extent possible, and never itself does random writes. It turns random writes into sequential writes - first when it writes them to the SSD, and then with writeback caching it can use your SSD to buffer gigabytes of writes and write them all out in order to your hard drive or raid array. If you’ve got a RAID6, you’re probably aware of the painful random write penalty, and the expensive controllers with battery backup people buy to mitigate them. Now, you can use Linux’s excellent software RAID and still get fast random writes, even on cheap hardware.

Bcache 是 Linux 内核块层缓存。它允许一个或多个快速磁盘驱动器(例如基于闪存的固态驱动器 (SSD))充当一个或多个较慢硬盘驱动器的缓存。

硬盘既便宜又大,SSD 速度快但又小又贵。如果您能透明地获得两者的优势,那不是很好吗?使用 Bcache,您可以吃蛋糕也可以吃。

Linux 内核的 Bcache 补丁允许使用 SSD 缓存其他块设备。它类似于 ZFS 的 L2Arc,但 Bcache 也进行写回缓存(除了通过缓存写入之外),而且它与文件系统无关。它旨在以最少的努力打开,并且无需任何设置的配置即可正常工作。默认情况下,它不会缓存顺序 IO,只会缓存 SSD 擅长的随机读写。它适用于台式机、服务器、高端存储阵列,甚至可能是嵌入式的。

设计目标是与 SSD 和缓存设备一样快(取决于缓存命中与未命中,以及直写与回写写入)在误差范围内。它还没有完全实现,主要用于顺序读取。但是测试表明它是绝对有可能的,甚至在某些情况下做得更好——主要是随机写入。

它也被设计成安全的。可靠性对于任何进行回写缓存的东西都是至关重要的;如果它中断,您将丢失数据。 Bcache 旨在成为电池备份 raid 控制器的更好替代品,因此即使电源线被拉出,它也必须可靠。它不会将写入返回为已完成,直到找到它所需的一切都在稳定的存储上,在电源故障的情况下写入也不会被视为部分完成(或更糟糕的是,丢失)。为了有效地完成这项工作,我们付出了大量的努力。

Bcache 是围绕 SSD 的性能特点而设计的。它旨在最大程度地减少写入膨胀,并且它本身从不进行随机写入。它将随机写入转换为顺序写入 - 首先将它们写入 SSD,然后通过回写缓存,它可以使用您的 SSD 缓冲千兆字节的写入并将它们全部写入硬盘驱动器或 RAID 阵列。如果你有一个 RAID6,你可能知道痛苦的随机写入惩罚,以及人们购买的带有备用电池的昂贵控制器来减轻它们。现在,您可以使用 Linux 出色的软件 RAID 并且仍然可以获得快速的随机写入,即使在廉价硬件上也是如此。

Bcache(块缓存)允许将 SSD 用作读/写缓存(在回写模式下)或读取缓存(直写或 writearound)用于另一个块设备(通常是旋转 HDD 或阵列)。 本文将展示如何使用 Bcache 作为根分区安装 Arch。 有关 bcache 本身的介绍,请参阅 bcache 主页。 请务必阅读并参考 bcache 手册。 Bcache 从 3.10 开始就在主线内核中。 自 2013.08.01 起,arch 安装磁盘上的内核包含 bcache 模块。

它是围绕 SSD 的性能特征设计的——它只分配擦除块大小的存储桶,并且它使用混合 btree/log 来跟踪缓存的范围(可以是从单个扇区到存储桶大小的任何地方)。它旨在不惜一切代价避免随机写入;它按顺序填充一个擦除块,然后在重用它之前发出丢弃。

支持 writethrough 和 writeback 缓存。回写默认为关闭,但可以在运行时任意开启和关闭。 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-tools 现在提供 udev 规则,并且 bcache 设备立即为内核所知。 如果没有 udev,您可以像这样手动注册设备:

echo /dev/sdb > /sys/fs/bcache/register
echo /dev/sdc > /sys/fs/bcache/register

注册后备设备使 bcache 设备显示在 /dev 中; 您现在可以对其进行格式化并正常使用它。 但是第一次使用新的 bcache 设备时,它将以直通模式运行,直到您将其附加到缓存。 如果您考虑稍后使用 bcache,建议您将所有慢速设备设置为不带缓存的 bcache 后备设备,您可以选择稍后添加缓存设备。 请参阅下面的“附加”部分。

mkfs.ext4 /dev/bcache0
mount /dev/bcache0 /mnt

bcache

bcache 实测对于小文件随机写场景下效果好,而且减少了小写的延迟。

目前 bcache 这类方案主要的问题是过于复杂,难以实现和维护。

  • Intro

引入块层的缓存对于优化磁盘的读写性能来说效果显著。块级别的缓存有多种实 现,可以按照是否依赖 device mapper 机制分为两类:

不依赖 dm 的实现:

  • bcache
  • enchanceio

依赖于 dm 的实现:

  • flashcache (flashcache 的主推者是 facebook,成熟度最高)
  • dm-cache

重要的特性:

  • 支持 SSD 池化管理
    • 支持 thin-provisioning,从 SSD 池中划分出纯 SSD 的卷单独使用
    • 不像其他方案 metadata 空间固定,索引更新很容易导致该区域写坏(wear out)
    • 单个 cache device 可以带多个 backing device
    • backing device 可以在运行时 attach/detache
  • IO sensitivity (REQ_SYNC/REQ_META/REQ_FLUSH/REQ_FUA)
  • Barriers/cache flushes are handled correctly.
  • 支持发现并 bypass 顺序读写
  • SSD 友好(支持 COW,减少写放大,保持顺序性)
  • 支持在运行时修改 cache mode
  • Usage

** format device

单个 cache set 中可以有若干个 cache device 和若干个 backing device。

格式化出 backing device:

#+BEGIN_EXAMPLE

make-bcache -B /dev/sdx1

#+END_EXAMPLE

格式化出 cache device:

#+BEGIN_EXAMPLE

make-bcache –block 4k –bucket 2M -C /dev/sdy2

#+END_EXAMPLE

注意 block size 是指 backing device 的扇区大小,而 bucket size 则是 cache device 也就是 SSD 的 erase block size。合理配置这两个值可以降低 写放大问题。

** attach device

完成格式化之后,接下来进行关联,首先找到 cache device 的 cache set uuid:

#+BEGIN_EXAMPLE

bcache-super-show /dev/sdy2 | grep cset.uuid

#+END_EXAMPLE

然后进行 attach:

#+BEGIN_EXAMPLE

echo cset.uuid > /sys/block/bcache0/bcache/attach

#+END_EXAMPLE

然后设置 cache mode:

#+BEGIN_EXAMPLE

echo writeback > /sys/block/bcache0/bcache/cache_mode

#+END_EXAMPLE

注意控制 bcache 设备有两个入口:

  • /sys/block/bcache/bcache
  • /sys/fs//bcache//

** cache status

检查 cache 状态:

#+BEGIN_EXAMPLE

cat /sys/block/bcache0/bcache/state

#+END_EXAMPLE

一种 4 种状态:

  • no cache - 表示你没有将 cache device 和 backing device 关联起来
  • clean - 没有 dirty data
  • dirty - 表示启用了 writeback,并且有 dirty data
  • inconsistent - 出问题了,backing device 和 cache device 不一致了

** writeback control

writeback_percent 如果为非零值,bcache 将会按照它指定的比例来对 writeback 做流控,并使用 PD controller 来平滑的调整比例。

下面的命令将会把所有的 dirty data 都刷到 backing device:

#+BEGIN_EXAMPLE echo 0 > /sys/block/bcache0/bcache/writeback_percent #+END_EXAMPLE

writeback_delay 控制新写入 cache device 的数据多久才会被 writeback:

#+BEGIN_EXAMPLE cat /sys/block/bcache0/bcache/writeback_delay 30 #+END_EXAMPLE

** sequential bypass

大顺序写 HDD 性能也不错,进行 bypass:

#+BEGIN_EXAMPLE cat /sys/block/bcache0/bcache/sequential_cutoff 4.0M #+END_EXAMPLE

大于 4M 的顺序写就不需要缓存了。

#+BEGIN_QUOTE I personally like to take that down to 1MB judging by the fact that files larger than 1MB do read pretty fast directly from the disk ! #+END_QUOTE

bypass 大顺序写对 SSD 来说也是有意义:

#+BEGIN_EXAMPLE large write reduces internal GCs #+END_EXAMPLE

** gc control

  • 元数据 GC - 遍历 btree,根据 bkey 信息标记出无效缓存和有效缓存 (dirty/clean 的数据),以及元数据,进行清理
  • 缓存数据 GC(Move GC) - 根据元数据 GC 阶段遍历的标记,找到包含较多无 效缓存数据的多个 bucket,将其中数据移动到新 bucket 去

手动触发 GC:

#+BEGIN_EXAMPLE echo 1 > /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/internal/trigger_gc #+END_EXAMPLE

** find cache/backing device of bcache device

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT xvda 202:0 0 8G 0 disk <- cache └─xvda1 202:1 0 8G 0 part / xvdb 202:16 0 20G 0 disk └─bcache0 251:0 0 500G 0 disk xvdd 202:48 0 500G 0 disk <- backing └─bcache0 251:0 0 500G 0 disk #+END_EXAMPLE

bcache 的 backing device:

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# ls -l /sys/block/bcache0/bcache lrwxrwxrwx 1 root root 0 Nov 24 10:37 /sys/block/bcache0/bcache -> ../../../vbd-51760/block/xvdd/bcache #+END_EXAMPLE

bcache 的 cache device:

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# ls ls -l /sys/block/bcache0/bcache/cache ls: cannot access ‘ls’: No such file or directory lrwxrwxrwx 1 root root 0 Nov 24 10:41 /sys/block/bcache0/bcache/cache -> ../../../../../fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f #+END_EXAMPLE

** cache set statistics

/sys/fs/bcache/ 下:

  • average_key_size - btree 中 data per key 的平均大小

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/average_key_size 4.4k #+END_EXAMPLE

  • bdev<0..n> - 每个 attached backing device 的符号链接

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# ls -l /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/bdev0 lrwxrwxrwx 1 root root 0 Nov 24 11:21 /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/bdev0 -> ../../../devices/vbd-51760/block/xvdd/bcache #+END_EXAMPLE

  • block_size - cache device 的 block 大小

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/block_size 4.0k #+END_EXAMPLE

  • btree_cache_size - btree 占用的内存大小

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/btree_cache_size 108.5M #+END_EXAMPLE

  • bucket_size

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/bucket_size 2.0M #+END_EXAMPLE

  • cache<0..n> - cache set 中 cache device 的符号链接

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# ls -l /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/cache0 lrwxrwxrwx 1 root root 0 Nov 24 11:21 /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/cache0 -> ../../../devices/vbd-51728/block/xvdb/bcache #+END_EXAMPLE

  • cache_available_percent - cache device 中不包含 dirty data 的百分比

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/cache_available_percent 28 #+END_EXAMPLE

** disable cache

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/io_error_halflife 0 root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/io_error_limit 8 #+END_EXAMPLE

** backing device config

检查 cache mode:

  • writethrough - cache/backing device 都写完才返回
  • writeback
  • writearound
  • passthrough - cache device 挂掉,之后直写 backing device

#+BEGIN_EXAMPLE

cat /sys/block/bcache0/bcache/cache_mode

[writethrough] writeback writearound none #+END_EXAMPLE

backing device 在 cache device 上有多少脏数据:

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/block/bcache0/bcache/dirty_data 6.8G #+END_EXAMPLE

readahead 大小:

#+BEGIN_EXAMPLE cat /sys/block/bcache0/bcache/readahead 0.0k #+END_EXAMPLE

** backing device statistics

backing device 的使用统计路径如下:

#+BEGIN_EXAMPLE /sys/devices/vbd-51760/block/xvdd/bcache/stats_five_minute/cache_miss_collisions /sys/devices/vbd-51760/block/xvdd/bcache/stats_total/cache_miss_collisions /sys/devices/vbd-51760/block/xvdd/bcache/stats_day/cache_miss_collisions /sys/devices/vbd-51760/block/xvdd/bcache/stats_hour/cache_miss_collisions #+END_EXAMPLE

指标:

  • bypassed - cache bypass 的 IO 总量,包括 write&read
  • cache_hits - bcache 看到的独立的 IO 命中,partial hit 也会被认为是 miss
  • cache_misses
  • cache_hit_ratio
  • cache_bypass_hits
  • cache_bypass_misses
  • cache_miss_collisions - 记录发生正要将数据写入 cache(因为 cache miss) 但数据却出现在了 cache 中的 race 出现的次数
  • cache_readaheads - 预读发生的次数

时间维度

  • stats_five_minute - 前五分钟
  • stats_hour - 前一小时
  • state_day - 前一天
  • stats_total

来依次看样例输出:

#+BEGIN_EXAMPLE oot@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/stats_total/cache_hit cache_hit_ratio cache_hits root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/stats_total/cache_hits 23440 root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/stats_total/cache_hit_ratio 99 #+END_EXAMPLE

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/stats_total/cache_bypass_hits 145427 root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/stats_total/cache_bypass_misses 0 #+END_EXAMPLE

#+BEGIN_EXAMPLE root@ip-172-31-30-23:~# cat /sys/fs/bcache/34e3bd83-cdab-466d-84d6-780f7c0d538f/stats_total/cache_miss_collisions 0 #+END_EXAMPLE

** cleanup

停止 cache:

#+BEGIN_EXAMPLE echo 1 > /sys/block/bcache0/bcache/stop #+END_EXAMPLE

释放 backing device:

#+BEGIN_EXAMPLE echo 1 > /sys/block/xvdf/bcache/set/stop #+END_EXAMPLE

#+BEGIN_EXAMPLE cat /sys/block/bcache0/bcache/state #+END_EXAMPLE

  • Implement

bcache 将 cache device 按照 bucket_size 进行划分。bucket_size 通常 512KB(和 SSD 擦除块大小一致)。除了 SB bucket 之外,所有的更新操作都可 以通过 append 完成。

bucket 的核心成员:

  • =priority= 16 位,作为优先级编号,每次 hit 增加,决定了 bucket 要不要被刷出
  • =generation= 8 位决定 bucket 是否合法

bcache 的数据布局:

  • Data Zone - COW allocator,在 bucket 中的连续 extent
  • Metadata Zone - 对 extent 的 B+树索引(保存 HDD 上数据到 SSD 缓存数据的 对应关系)

** Allocator

*** invalidate bucket

bucket 内只进行追加分配,记录当前分配到哪个偏移,下次从当前记录位置之后 分配。

*** bucket allocator

bucket 的分配原则:

  • IO 连续性优先,即便 IO 来自不同的生产者
  • 相关性,将统一进程的数据放在相同的 bucket 内

** Index(bucket 的管理)

bucket 统一通过哟 btree 管理。

*** bkey

整个 B+树将 B+树节点映射到单个 bucket,而 bucket 中则保存着一组 bkey。 bkey 表示 cache device 中的数据与 backing device 数据之间的映射:

#+BEGIN_SRC C++ struct bkey { uint64_t high; uint64_t low; uint64_t ptr[]; } #+END_SRC

*** Lookup btree

通过 HDD id + IO 请求的 LBA 来查找缓存数据。

*** Metadata Memory Cache

为每个 btree bucket 申请一块连续内存作为元数据缓存。

*** Update btree

利用 Journal/WAL 加速 B+tree 的修改, 写完 journal 以及内存中的 B+tree 节点缓存 后写 IO 就可以返回了。

** Writeback

bcache 为每个 backing device 启动单独的 flush 线程,将 SSD 中 dirty 数 据刷到 HDD 中。

  • 通过 HDD id 获取 dirty 的 bkey,然后按照 bkey 中的 HDD LBA 信息排序(这样 排序后的 bkey 依次读取 SSD 中脏数据写入 HDD 实现顺序落盘)
  • 由 LBA 记录 dirty bkey,从 SSD 读刷入 HDD

** Writeback PD/PI Controller

流控:

  • writeback PD controller(比例-微分控制器),水位越高,flush 速度越快
  • 让脏数据尽可能多的留在 cache 中
  • 更快速的改变 water level

PD/PI Controller https://www.spinics.net/lists/linux-bcache/msg04954.html

PD Controller

Proportional term Derivative term

bcache uses a control system to attempt to keep the amount of dirty data in cache at a user-configured level, while not responding excessively to transients and variations in write rate. Previously, the system was a PD controller; but the output from it was integrated, turning the Proportional term into an Integral term, and turning the Derivative term into a crude Proportional term. Performance of the controller has been uneven in production, and it has tended to respond slowly, oscillate, and overshoot.

PI controller

This patch set replaces the current control system with an explicit PI controller and tuning that should be correct for most hardware. By default, it attempts to write at a rate that would retire 1/40th of the current excess blocks per second. An integral term in turn works to remove steady state errors.

IMO, this yields benefits in simplicity (removing weighted average filtering, etc) and system performance.

  • Release History
  • Issue
  1. 功能
    1. SSD 和 HDD 不支持热插拔
    2. HDD 卸载需要等脏数据全部刷完才可以
    3. HDD 损坏时,SSD 中脏数据无法清除
    4. 不可靠的 write-back
      • 问题
        • high error rate
        • worn out
        • FTL bugs
        • destruction
      • 解决方案 1 - SSD RAID
        • 最小化 drity ratio (write-back -> write-back(dirty threshold)-> write through)
        • SSD RAID as Cache(Failure Recovery via Parity)
          • high performance
          • high reliability
          • on-the-fly SSD replacement
          • flexible capacity Management
      • 解决方案 2 - Log-structured Approach
        • 最小化 parity 和 metadata update
  2. 性能
    1. 当 cache 被写满时,需要将脏数据都刷完才能继续为写 IO 提供缓存
    2. GC 运行时导致性能波动
    3. bcache 内存消耗大,系统内存不足时元数据无法缓存,从 SSD 读取

需要注意:

  • 如何创建多个 cache set
  • 选择一个稳定的内核版本(CentOS7.1 不支持 bcache)
  • References

性能测试:

  • Readling List