缓存¶
简介¶
dm-cache 是由 Joe Thornber、Heinz Mauelshagen 和 Mike Snitzer 编写的设备映射器目标。
它旨在通过将部分数据动态迁移到更快的、更小的设备(例如,SSD)来提高块设备(例如,主轴)的性能。
这种设备映射器解决方案允许我们在 dm 堆栈的不同级别插入此缓存,例如,在精简配置池的数据设备之上。与虚拟内存系统更紧密地集成的缓存解决方案应该会提供更好的性能。
该目标重用了精简配置库中使用的元数据库。
关于迁移哪些数据以及何时迁移的决定留给插件策略模块。我们已经编写了其中的几个用于实验,我们希望其他人会为特定的 io 场景贡献其他的(例如,虚拟机映像服务器)。
术语表¶
- 迁移
将逻辑块的主副本从一个设备移动到另一个设备。
- 升级
从慢速设备迁移到快速设备。
- 降级
从快速设备迁移到慢速设备。
原始设备始终包含逻辑块的副本,该副本可能是过时的,也可能与缓存设备上的副本保持同步(取决于策略)。
设计¶
子设备¶
该目标是通过将三个设备传递给它来构建的(以及稍后详述的其他参数)
原始设备 - 大而慢的设备。
缓存设备 - 小而快的设备。
一个小的元数据设备 - 记录哪些块在缓存中,哪些块是脏的,以及策略对象使用的额外提示。此信息可以放在缓存设备上,但将其分开可以使卷管理器以不同的方式配置它,例如,作为镜像以获得额外的鲁棒性。此元数据设备只能由单个缓存设备使用。
固定块大小¶
原始设备被分成固定大小的块。当您首次创建缓存时,可以配置此块大小。通常,我们一直使用 256KB - 1024KB 的块大小。块大小必须在 64 个扇区 (32KB) 和 2097152 个扇区 (1GB) 之间,并且是 64 个扇区 (32KB) 的倍数。
拥有固定的块大小大大简化了目标。但这是一种折衷方案。例如,块的一小部分可能被频繁访问,但整个块将被提升到缓存中。因此,大的块大小不好,因为它们会浪费缓存空间。小的块大小不好,因为它们会增加元数据量(在核心中和在磁盘上)。
缓存操作模式¶
缓存有三种操作模式:写回、写通和直通。
如果选择写回(默认),则写入缓存的块将仅写入缓存,并且该块将在元数据中标记为脏。
如果选择写通,则在写入缓存的块完成之前,它必须同时命中原始设备和缓存设备。干净的块应保持干净。
如果选择直通,当缓存内容与原始设备不一致时很有用,则所有读取都从原始设备提供(所有读取都错过缓存),所有写入都转发到原始设备;此外,写入命中会导致缓存块失效。要启用直通模式,缓存必须是干净的。直通模式允许激活缓存设备,而无需担心一致性。存在的一致性会被维护,尽管随着写入的进行,缓存会逐渐冷却。如果稍后可以验证缓存的一致性,或者通过使用 “invalidate_cblocks” 消息建立一致性,则缓存设备可以转换为写通或写回模式,同时仍保持预热状态。否则,可以在转换为所需的操作模式之前丢弃缓存内容。
提供了一个简单的清理器策略,它将清理(写回)缓存中所有脏的块。用于停用缓存或缩小缓存时。缩小缓存的快速设备需要清除要删除的缓存区域中的所有缓存块。如果从缓存中删除的区域仍然包含脏块,则调整大小将失败。必须注意,在缓存清理干净之前,永远不要减少用于缓存的快速设备的卷。如果使用写回模式,这一点尤其重要。写通和直通模式已经维护了一个干净的缓存。未来支持部分清理缓存(高于指定阈值)将允许在调整大小时保持缓存预热并在写回模式下运行。
迁移节流¶
在原始设备和缓存设备之间迁移数据会占用带宽。用户可以设置节流,以防止在任何时候发生超过一定量的迁移。目前,我们没有考虑任何发送到设备的正常 io 流量。在这里需要做更多的工作,以避免在那些峰值 io 时刻进行迁移。
目前,可以使用消息 “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”。有关详细信息,请参阅编写策略指南。
可选的特性参数为
写直通
写直通缓存,禁止缓存块内容与原始块内容不同。如果没有此参数,则默认行为是稍后写回缓存块内容以提高性能,因此它们可能与相应的原始块不同。
透传
一种降级模式,适用于各种缓存一致性情况(例如,回滚底层存储的快照)。读取和写入始终转到原始设备。如果写入转到缓存的原始块,则该缓存块将失效。要启用透传模式,缓存必须是干净的。
元数据2
使用版本2的元数据。这将脏位存储在单独的 btree 中,从而提高了关闭缓存的速度。
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 被映射到原始设备的次数 |
#降级 |
块从缓存中删除的次数 |
#升级 |
块移动到缓存的次数 |
#脏 |
缓存中与原始块不同的块数 |
#特性参数 |
要遵循的特性参数的数量 |
特性参数 |
“writethrough”(可选) |
#核心参数 |
核心参数的数量(必须为偶数) |
核心参数 |
用于调整核心的键/值对,例如 migration_threshold |
策略名称 |
策略的名称 |
#策略参数 |
要遵循的策略参数的数量(必须为偶数) |
策略参数 |
键/值对,例如 sequential_threshold |
缓存元数据模式 |
ro(只读),rw(读写) 在即使只读模式也被认为不安全的情况下,将不允许进一步的 I/O,并且状态将只包含字符串“Fail”。然后应使用用户空间恢复工具。 |
needs_check |
如果设置了“needs_check”,则为“needs_check”,否则为“-”。元数据操作失败,导致在元数据的超级块中设置了 needs_check 标志。必须停用元数据设备并进行检查/修复,然后才能使缓存再次完全运行。“-”表示未设置 needs_check。 |
消息¶
策略会有不同的可调参数,每个策略都有其特定的参数,因此我们需要一种通用的方法来获取和设置这些参数。这里使用设备映射器消息。(也可以使用 sysfs 接口。)
消息格式为
<key> <value>
例如
dmsetup message my_cache 0 sequential_threshold 1024
失效是从缓存中删除条目而不写回。可以使用 invalidate_cblocks 消息使缓存块失效,该消息接受任意数量的 cblock 范围。每个 cblock 范围的结束值是“超出末尾一个值”,这意味着 5-10 表示 5 到 9 的值范围。每个 cblock 都必须表示为十进制值,将来可能需要一个以十六进制表示的 cblock 范围的变体消息,以便更好地支持更大缓存的有效失效。使用 invalidate_cblocks 时,缓存必须处于透传模式
invalidate_cblocks [<cblock>|<cblock begin>-<cblock end>]*
例如
dmsetup message my_cache 0 invalidate_cblocks 2345 3456-4567 5678-6789
示例¶
测试套件可以在这里找到
https://github.com/jthornber/device-mapper-test-suite
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'