ZFS系列(四)The Adjustable Replacement Cache

本文继续ZFS管理系列文章,这里将介绍另一个zpool VDEV,叫做可调整可替换缓存(简称ARC,当然也有翻译为自适应缓存的)。

传统缓存

Linux和其他操作系统上的缓存机制使用的是最近最少使用的缓存算法。LRU算法的工作方式是,当应用程序读取数据块时,它们被放到缓存中。当越来越多的数据被读取并放入缓存时,缓存将被填满。然而,缓存是先进先出(FIFO)算法。因此,当缓存已满时,较旧的页面将被推出缓存。即使那些旧页面被更频繁地访问。把整个过程想象成一条传送带。块被放在缓存中最近使用的部分。当读取更多的块时,将旧块推到缓存中最近使用最少的部分,直到它们从传送带上脱落,或者换句话说被逐出。

当从磁盘读取较大的顺序读并将其放入缓存时,它倾向于从缓存中逐出更频繁请求的页面。即使这些新读取的数据只需要一次。因此,从缓存的角度来看,它最终会产生大量无用的、不再需要的数据。当然,当请求更新的数据块时,它最终会被替换。

当然除了LRU,还有最少使用频率(LFU)缓存。但是,LFU会遇到一个问题,即如果较新的数据读取的频次不够多,很可能会从缓存中淘汰。因此,如果还要读取这些新数据将会有大量的磁盘请求,这就违背了使用缓存的初衷。因此,似乎最明显的方法就是以某种方式将两者结合起来——同时拥有一个LRU和一个LFU。

ZFS ARC

ZFS可调替换缓存(ARC)就是这样一种缓存机制,它既缓存最近的块请求,也缓存频繁的块请求。它是IBM专利“adaptive replacement cache”的实现,经过一些修改和扩展。

在开始之前,我应该提一下,我从http://www.c0t0d0s0.org/archives/5329-Some-insight-into-the-read-cache-of-ZFS-or-The-ARC.html
学到了很多关于ZFS ARC的知识。下面是我在这里重复使用的那篇文章的图片。谢谢Joerg的精彩帖子。

术语

  • Adjustable Replacement Cache(ARC) -驻留在物理RAM中的缓存。它是使用两个缓存构建的——最常用的缓存和最近使用的缓存。缓存目录索引指向缓存的指针,包括指向称为ghost常用缓存和ghost最近使用缓存的磁盘的指针。
  • 缓存目录-组成MRU, MFU,ghost MRU和ghost MFU缓存的指针的一个索引目录。
  • MRU缓存- ARC最近使用的缓存。文件系统中最近请求的块被缓存在这里。
  • MFU缓存- ARC中最频繁使用的缓存。文件系统中最频繁请求的块都缓存在这里。
  • Ghost MRU-将MRU缓存中的页面移回磁盘,以节省MRU的空间。指针仍然跟踪被逐出的页面在磁盘上的位置。
  • Ghost MFU-将页面从MFU缓存中驱逐回磁盘,以节省MFU中的空间。指针仍然跟踪被逐出的页面在磁盘上的位置。
  • 2级Adjustable Replacement Cache(L2ARC)——位于物理内存之外的缓存,通常位于快速SSD上。它是RAM ARC字面上的、物理上的扩展。

ARC算法

这是IBM ARC工作方式的简化版本,但是它可以帮助您理解MRU和MFU的优先级是如何分配的。首先,让我们假设缓存中有8个页面。缓存中的四页将用于MRU,另外四页用于MFU。此外,还将有四个指针的ghost MRU和四个指针的ghost MFU。因此,缓存目录将引用16页的活动缓存或被淘汰的缓存。

  1. 正如预期的那样,当从文件系统中读取块A时,它将被缓存到MRU中。缓存目录中的一个索引指针将引用该MRU页面。

  1. 现在假设从文件系统中读取了一个不同的块(块B)。它也将被缓存到MRU中,并且缓存目录中的索引指针将引用第二个MRU页面。因为块B比块A读得更晚,所以它在MRU缓存中比块A有更高的优先级。现在MRU缓存中有两个页面。

  1. 现在假设块A再次从文件系统中读取。这将是对块A的第二次读取。结果,它被频繁地读取,所以它将被存储在MFU中。一个块必须被至少读两次才能存储在这里。此外,这也是最近的请求。因此,块不仅缓存在MFU中,它还在缓存目录的MRU中被引用。因此,尽管缓存中有两个页面,但缓存目录中有三个指针指向缓存中的两个块。

  1. 最终,缓存被上述类似的步骤填满,我们在MRU和缓存目录的MFU中都有指针。

  1. 这就是有趣的地方。假设我们现在需要从没有缓存的文件系统中读取一个新的块。由于鸽笼原理,我们要缓存的页面比存储的页面要多。因此,我们需要从缓存中取出一个页面。MRU中最老的页面(被称为最近最少使用的- LRU)会得到驱逐通知,并被ghost MRU引用。MRU中将有一个新的页面用于新读块。

  1. 当新的读块被从文件系统中读取后,如预期的那样,它被存储在MRU中并被引用。这样,我们就有了一个ghost MRU页面引用和一个已填满的缓存。

  1. 为了搅乱整个过程,让我们假设从文件系统中重新读取了最近被逐出的页面。因为ghost MRU知道它最近被从缓存中移除,我们称之为“幽灵缓存命中”。因为ZFS知道它最近被缓存了,所以我们需要将它带回MRU缓存中;而不是MFU缓存,因为它没有被MFU ghost引用。

  1. 不幸的是,我们的缓存太小,无法存储该页面。所以,我们必须把MRU增加一页来存储这个新的“幽灵命中的页面”。但是,我们的缓存只有这么大,所以我们必须将MFU的大小减少1,以便为MRU腾出空间。当然,该算法以类似的方式工作在MFU和ghost MFU。ghost MFU的“幽灵命中”会增大MFU,当然同时需要缩小MRU来为新页面腾出空间。

所以,想象一下两种截然相反的工作f负载。第一种工作负载从磁盘读取大量随机数据,几乎没有重复。MRU可能会占缓存的大部分,而MFU只占很小的一部分。缓存已经根据系统所承受的负载调整了自己。但是,考虑第二种工作负载,它不断地反复读取相同的数据,只有很少的新读取数据。在这种情况下,MFU可能会占据大部分缓存,而MRU不会。因此,缓存被调整来适应系统所承受的负载。

还记得我们前面提到的传统缓存方案吗?Linux内核使用LRU方案将缓存的页面交换到磁盘。因此,它总是倾向于最近的缓存命中,而不是频繁的缓存命中。如果您只需要一次读取大量块,那么这可能会产生严重的后果。您将把频繁缓存的页面交换到磁盘,即使新读取的数据只需要一次。因此,您可能会以THE SWAP OF DEATH结束,因为频繁请求的页面必须从交换区域返回磁盘。使用ARC,我们修改缓存以适应这种负载(因此,它被称为“Adaptive Read cache”)。

ZFS ARC扩展

前面的算法是IBM设计的ARC算法的简化版本。ZFS做了如下扩展:

  • ZFS ARC将占用1/2的可用RAM。然而,这并不是静态的。如果您的服务器中有32GB的RAM,这并不意味着缓存总是16GB。相反,总缓存将根据内核的决定调整其大小。如果内核需要为调度的进程提供更多的RAM,则会调整ZFS ARC,以便为内核需要的任何内容腾出空间。但是,如果有ZFS ARC可以占用的空间,它就会占用它。
  • ZFS ARC可以使用多种块大小,而IBM实现使用静态块大小。
  • 页面可以锁定在MRU或MFU,以防止驱逐。IBM实现没有这个特性。因此,在选择页面何时从缓存中被移除时,ZFS ARC算法会稍微复杂一些。

L2ARC

2级ARC或L2ARC应该使用快速磁盘(比如SSD)。正如我在之前关于ZIL的文章中提到的,ZIL应该使用DRAM内存(不一定需要电池支持),快速SSD,或10k以上的企业SAS或FC硬盘。如果您决定为您的ZIL和L2ARC使用相同的设备(这当然是可以接受的),那么您应该对它进行分区,使ZIL占用很少的空间,比如512 MB或1 GB,并将其余的作为条带(RAID-0) L2ARC给池。在L2ARC中不需要持久化,因为缓存将在引导时被擦除。

L2ARC是ARC在RAM中的扩展,当有L2ARC存在时,之前的算法保持不变。这意味着随着MRU或MFU的增长,它们不会同时共享RAM中的ARC和SSD中的L2ARC。这将对性能产生巨大的影响。相反,当一个页面即将被逐出时,一个遍历算法将MRU和MFU页面逐出到一个8 MB的缓冲区中,这个缓冲区稍后被设置为一个到L2ARC的原子写事务。这里的明显优势是,从缓存中删除页面的延迟不会受到影响。此外,如果大量的数据块被读到缓存中,这些块在L2ARC遍历之前被驱逐,而不是被发送到L2ARC。这将最大限度地减少大量顺序读取对L2ARC的污染。根据对数据的访问情况,填充L2ARC可能会非常慢,也可能非常快。

添加一个L2ARC

警告:一些主板在重新启动时不会以一致的方式向Linux内核提供磁盘。因此,在一次引导时标识为/dev/sda的磁盘可能在下一次引导时标识为/dev/sdb。对于存储数据的主池,这不是问题,因为ZFS可以基于元数据几何结构重建VDEVs。然而,对于您的L2ARC和SLOG设备,不存在这样的元数据。那么,与其通过它们的/dev/sd?名字,将它们添加到池中,还不如用/dev/disk/by-id/*名称将其加入池中,因为这些是符号指针,指向不断变化的/dev/sd?文件。如果您没有注意到这个警告,那么您的L2ARC设备可能根本就不会被添加到您的混合池中,您需要稍后重新添加它。这可能会极大地影响应用程序的性能,这取决于是否存在一个快速的L2ARC。

您可以使用“缓存”VDEV添加L2ARC。建议对L2ARC进行分条处理,使大小和速度都最大化。对于L2ARC来说,持久化数据是不必要的,所以它可以是不稳定的内存。假设我的池中有4个盘片磁盘,一个OCZ Revodrive SSD向系统提供两个60GB驱动器。我将在SSD上对驱动器进行分区,将4 GB给ZIL,其余留给L2ARC。这是如何将L2ARC添加到池中。在这里,我使用GNU parted首先创建分区,然后添加ssd。“/dev/disk/by-id/”中的设备分别指向“/dev/sda”和“/dev/sdb”。仅供参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# parted /dev/sda unit s mklabel gpt mkpart primary zfs 2048 4G mkpart primary zfs 4G 109418255
# parted /dev/sdb unit s mklabel gpt mkpart primary zfs 2048 4G mkpart primary zfs 4G 109418255
# zpool add tank cache \
/dev/disk/by-id/ata-OCZ-REVODRIVE_OCZ-33W9WE11E9X73Y41-part2 \
/dev/disk/by-id/ata-OCZ-REVODRIVE_OCZ-X5RG0EIY7MN7676K-part2 \
log mirror \
/dev/disk/by-id/ata-OCZ-REVODRIVE_OCZ-69ZO5475MT43KNTU-part1 \
/dev/disk/by-id/ata-OCZ-REVODRIVE_OCZ-9724MG8BII8G3255-part1
# zpool status tank
pool: tank
state: ONLINE
scan: scrub repaired 0 in 1h8m with 0 errors on Sun Dec 2 01:08:26 2012
config:

NAME STATE READ WRITE CKSUM
tank ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
sdd ONLINE 0 0 0
sde ONLINE 0 0 0
sdf ONLINE 0 0 0
sdg ONLINE 0 0 0
logs
mirror-1 ONLINE 0 0 0
ata-OCZ-REVODRIVE_OCZ-69ZO5475MT43KNTU-part1 ONLINE 0 0 0
ata-OCZ-REVODRIVE_OCZ-9724MG8BII8G3255-part1 ONLINE 0 0 0
cache
ata-OCZ-REVODRIVE_OCZ-69ZO5475MT43KNTU-part2 ONLINE 0 0 0
ata-OCZ-REVODRIVE_OCZ-9724MG8BII8G3255-part2 ONLINE 0 0 0

errors: No known data errors

此外,我可以用如下命令在任何时候检查L2ARC的大小:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#zpool iostat -v
capacity operations bandwidth
pool alloc free read write read write
------------------------------------------------ ----- ----- ----- ----- ----- -----
pool 824G 2.82T 11 60 862K 1.05M
raidz1 824G 2.82T 11 52 862K 972K
sdd - - 5 29 289K 329K
sde - - 5 28 289K 327K
sdf - - 5 29 289K 329K
sdg - - 7 35 289K 326K
logs - - - - - -
mirror 1.38M 3.72G 0 19 0 277K
ata-OCZ-REVODRIVE_OCZ-69ZO5475MT43KNTU-part1 - - 0 19 0 277K
ata-OCZ-REVODRIVE_OCZ-9724MG8BII8G3255-part1 - - 0 19 0 277K
cache - - - - - -
ata-OCZ-REVODRIVE_OCZ-69ZO5475MT43KNTU-part2 2.34G 49.8G 0 0 739 4.32K
ata-OCZ-REVODRIVE_OCZ-9724MG8BII8G3255-part2 2.23G 49.9G 0 0 801 4.11K
------------------------------------------------ ----- ----- ----- ----- ----- -----

在这种情况下,我在L2ARC中使用了大约5GB的缓存数据(记住,它是条带的),有足够的空间。事实上,上面的执行结果来自一个正在运行的32GB DDR2 RAM的系统。L2ARC在一周前修改并重新添加。这显示了我在我的系统上填充L2ARC的速度。你的情况可能有所不同,但花这么长时间也就不足为奇了。Ben Rockwood有一个Perl脚本,可以分解ARC和L2ARC的MRU, MFU,和ghost,以及其他。请访问http://www.cuddletech.com/blog/pivot/entry.php?id=979 (我对这个脚本没有任何经验)。

总结

ZFS可调可替换缓存改进了IBM的原始Adaptive Read Cache,同时保持了IBM设计的原形。然而,与传统的LRU和LFU缓存相比,ZFS ARC有巨大的优势。而且,通过在快速SSD或磁盘上添加L2ARC,我们可以快速检索大量数据,同时仍然允许主机内核根据需要调整内存需求。

参考资料

https://pthree.org/2012/12/07/zfs-administration-part-iv-the-adjustable-replacement-cache/

如果你觉得本文对你有帮助,欢迎打赏