缓存

简介

dm-cache 是由 Joe Thornber、Heinz Mauelshagen 和 Mike Snitzer 编写的一个设备映射器目标。

它旨在通过将块设备(例如,传统硬盘)的一些数据动态迁移到更快、更小的设备(例如,SSD)来提高其性能。

这种设备映射器解决方案允许我们在 dm 堆栈的不同级别插入此缓存,例如在精简配置池的数据设备之上。与虚拟内存系统更紧密集成的缓存解决方案应该提供更好的性能。

该目标重用了精简配置库中使用的元数据库。

关于何时迁移以及迁移哪些数据的决定留给了插件策略模块。我们已经编写了其中几个用于实验,我们希望其他人能为特定的 I/O 场景(例如虚拟机镜像服务器)贡献更多。

术语表

迁移

将逻辑块的主副本从一个设备移动到另一个设备。

提升

从慢速设备到快速设备的迁移。

降级

从快速设备到慢速设备的迁移。

原始设备总是包含逻辑块的副本,该副本可能过时或与缓存设备上的副本保持同步(取决于策略)。

设计

子设备

该目标通过向其传递三个设备(以及稍后详述的其他参数)来构建

  1. 原始设备 - 大型、慢速的设备。

  2. 缓存设备 - 小型、快速的设备。

  3. 一个小的元数据设备 - 记录哪些块在缓存中、哪些是脏的,以及供策略对象使用的额外提示。这些信息可以放在缓存设备上,但将其分离允许卷管理器以不同方式配置它,例如作为镜像以增加鲁棒性。此元数据设备只能由一个缓存设备使用。

固定块大小

原始设备被划分为固定大小的块。此块大小在您首次创建缓存时可配置。通常我们使用的块大小为 256KB - 1024KB。块大小必须在 64 个扇区(32KB)到 2097152 个扇区(1GB)之间,并且是 64 个扇区(32KB)的倍数。

固定块大小大大简化了目标。但这也是一种折衷。例如,一个块的一小部分可能被频繁访问,但整个块都将被提升到缓存中。因此,大块大小不好,因为它们浪费缓存空间。而小块大小也不好,因为它们增加了元数据量(包括在内存中和磁盘上)。

缓存操作模式

缓存有三种操作模式:回写(writeback)、直写(writethrough)和直通(passthrough)。

如果选择默认的回写模式,则对已缓存块的写入将仅写入缓存,并且该块在元数据中将被标记为脏。

如果选择直写模式,则对已缓存块的写入只有在同时写入原始设备和缓存设备后才会完成。干净的块应保持干净。

如果选择直通模式(在缓存内容与原始设备不同步时很有用),则所有读取都从原始设备提供(所有读取都未命中缓存),并且所有写入都转发到原始设备;此外,写入命中会导致缓存块失效。要启用直通模式,缓存必须是干净的。直通模式允许激活缓存设备而无需担心一致性。现有的一致性会得到维护,尽管随着写入的进行,缓存会逐渐“冷却”。如果以后可以通过验证或使用“invalidate_cblocks”消息来建立缓存的一致性,则缓存设备可以在仍然“热”的情况下转换为直写或回写模式。否则,可以在转换为所需操作模式之前丢弃缓存内容。

提供了一个简单的清理策略,它将清理(回写)缓存中所有脏块。这在停用缓存或缩小缓存时很有用。缩小缓存的快速设备要求所有被移除区域中的缓存块都是干净的。如果从缓存中移除的区域仍然包含脏块,则调整大小将失败。必须注意,在缓存干净之前,绝不能减小用于缓存快速设备的卷。如果使用回写模式,这一点尤为重要。直写和直通模式已经保持缓存干净。未来支持在指定阈值之上部分清理缓存,将允许在调整大小时保持缓存“热”并处于回写模式。

迁移限流

在原始设备和缓存设备之间迁移数据会占用带宽。用户可以设置一个限流器,以防止在任何时候发生超过一定量的数据迁移。目前我们没有考虑流向设备的正常 I/O 流量。这里需要做更多工作,以避免在 I/O 峰值时进行迁移。

目前,可以使用消息“migration_threshold <#sectors>”来设置正在迁移的最大扇区数量,默认值为 2048 个扇区(1MB)。

更新磁盘元数据

每当写入 FLUSH 或 FUA bio 时,磁盘元数据就会被提交。如果没有此类请求,则每秒都会发生提交。这意味着缓存的行为类似于具有易失性写入缓存的物理磁盘。如果断电,您可能会丢失一些最近的写入。无论发生任何崩溃,元数据都应始终保持一致。

缓存块的“脏”状态变化过于频繁,我们无法实时更新它。因此我们将其视为一个提示。在正常操作中,当 dm 设备挂起时,它将被写入。如果系统崩溃,所有缓存块在重新启动时都将被假定为脏块。

每块策略提示

策略插件可以为每个缓存块存储一段数据。这段数据的大小由策略决定,但应保持较小。与脏位标志一样,如果发生崩溃,这些数据会丢失,因此应该始终能够有一个安全的后备值。

策略提示影响性能,而非正确性。

策略消息

策略将有不同的可调参数,每个策略都有其特定参数,因此我们需要一种通用的方式来获取和设置这些参数。设备映射器消息用于此目的。请参考策略编写指南

丢弃位集分辨率

如果知道块已被丢弃,我们可以在迁移过程中避免复制数据。一个典型的例子是 mkfs 丢弃整个块设备时。我们存储一个位集来跟踪块的丢弃状态。但是,我们允许这个位集具有与缓存块不同的块大小。这是因为我们需要跟踪所有原始设备的丢弃状态(与仅适用于较小缓存设备的脏位集相比)。

目标接口

构造函数

cache <metadata dev> <cache dev> <origin dev> <block size>
      <#feature args> [<feature arg>]*
      <policy> <#policy args> [policy args]*

元数据设备

存储持久元数据的快速设备

缓存设备

存储缓存数据块的快速设备

原始设备

存储原始数据块的慢速设备

块大小

以扇区为单位的缓存单元大小

#功能参数

传递的功能参数数量

功能参数

直写或直通(默认为回写。)

策略

要使用的替换策略

#策略参数

传递给策略的键/值对对应的偶数个参数

策略参数

传递给策略的键/值对。例如 ‘sequential_threshold 1024’。详情请参阅策略编写指南

可选功能参数有

直写

直写式缓存,禁止缓存块内容与原始块内容不同。如果没有此参数,默认行为是出于性能原因稍后回写缓存块内容,因此它们可能与相应的原始块不同。

直通

一种降级模式,适用于各种缓存一致性情况(例如,回滚底层存储的快照)。读写操作始终访问原始设备。如果写入一个已缓存的原始块,则该缓存块将被失效。要启用直通模式,缓存必须是干净的。

metadata2

使用元数据版本 2。这会将脏位存储在一个单独的 B 树中,从而提高了关闭缓存的速度。

no_discard_passdown

禁用将丢弃操作从缓存传递到原始数据设备。

始终注册一个名为“default”的策略。这是我们目前认为提供最佳综合性能的策略的别名。

由于默认策略在不同内核之间可能有所不同,如果您依赖特定策略的特性,请始终按名称请求它。

状态

<metadata block size> <#used metadata blocks>/<#total metadata blocks>
<cache block size> <#used cache blocks>/<#total cache blocks>
<#read hits> <#read misses> <#write hits> <#write misses>
<#demotions> <#promotions> <#dirty> <#features> <features>*
<#core args> <core args>* <policy name> <#policy args> <policy args>*
<cache metadata mode>

元数据块大小

每个元数据块的固定大小(以扇区为单位)

#已用元数据块

已使用的元数据块数量

#总元数据块

元数据块总数

缓存块大小

缓存设备的可配置块大小(以扇区为单位)

#已用缓存块

缓存中驻留的块数量

#总缓存块

缓存块总数

#读取命中

READ bio 被映射到缓存的次数

#读取未命中

READ bio 被映射到原始设备的次数

#写入命中

WRITE bio 被映射到缓存的次数

#写入未命中

WRITE bio 被映射到原始设备的次数

#降级次数

块从缓存中移除的次数

#提升次数

块被移动到缓存的次数

#脏块

缓存中与原始设备不同的块的数量

#功能参数

后续功能参数数量

功能参数

‘直写’(可选)

#核心参数

核心参数数量(必须是偶数)

核心参数

用于调整核心的键/值对,例如 migration_threshold

策略名称

后续策略参数数量(必须是偶数)

#策略参数

键/值对,例如 sequential_threshold

策略参数

缓存元数据模式

ro 表示只读,rw 表示读写

在严重情况下,即使只读模式被认为不安全,也将不再允许进一步的 I/O 操作,并且状态将只包含字符串“Fail”。此时应使用用户空间恢复工具。

needs_check

如果设置,则为‘needs_check’,否则为‘-’。元数据操作失败,导致元数据超级块中的 needs_check 标志被设置。元数据设备必须在缓存完全恢复运行之前被停用并检查/修复。“-”表示 needs_check 未设置。

消息

策略将有不同的可调参数,每个策略都有其特定参数,因此我们需要一种通用的方式来获取和设置这些参数。设备映射器消息用于此目的。(也可以使用 sysfs 接口。)

消息格式为

例如

<key> <value>

失效是指从缓存中移除一个条目而不将其回写。缓存块可以通过 invalidate_cblocks 消息失效,该消息接受任意数量的 cblock 范围。每个 cblock 范围的结束值是“超出末尾一位”,这意味着 5-10 表示从 5 到 9 的值范围。每个 cblock 必须表示为十进制值,将来可能需要一种以十六进制表示 cblock 范围的变体消息,以更好地支持大缓存的高效失效。使用 invalidate_cblocks 时,缓存必须处于直通模式。

dmsetup message my_cache 0 sequential_threshold 1024

示例

invalidate_cblocks [<cblock>|<cblock begin>-<cblock end>]*

失效是指从缓存中移除一个条目而不将其回写。缓存块可以通过 invalidate_cblocks 消息失效,该消息接受任意数量的 cblock 范围。每个 cblock 范围的结束值是“超出末尾一位”,这意味着 5-10 表示从 5 到 9 的值范围。每个 cblock 必须表示为十进制值,将来可能需要一种以十六进制表示 cblock 范围的变体消息,以更好地支持大缓存的高效失效。使用 invalidate_cblocks 时,缓存必须处于直通模式。

dmsetup message my_cache 0 invalidate_cblocks 2345 3456-4567 5678-6789

测试套件可以在这里找到

https://github.com/jthornber/device-mapper-test-suite

©内核开发社区。 | 由 Sphinx 5.3.0 & Alabaster 0.7.16 提供技术支持 | 页面源

dmsetup create my_cache --table '0 41943040 cache /dev/mapper/metadata \
        /dev/mapper/ssd /dev/mapper/origin 512 1 writeback default 0'
dmsetup create my_cache --table '0 41943040 cache /dev/mapper/metadata \
        /dev/mapper/ssd /dev/mapper/origin 1024 1 writeback \
        mq 4 sequential_threshold 1024 random_threshold 8'