ZFS管理手册:第四章ARC
我们在ZFS管理系列中继续介绍另一个zpool VDEV,称为Adjustable Replacement Cache
,简称ARC。我们在上一篇文章中讨论了ZFS Intent Log(ZIL)和单独的Intent Logging Device(SLOG)。
传统缓存
Linux和其他操作系统上的缓存机制为最近最少使用Least Recently Used
的缓存算法。LRU算法的工作方式是,当应用程序读取数据块时,它们被放入缓存中。随着越来越多的数据被读取并放入高速缓存,高速缓存将被填满。但是,缓存是FIFO(先进先出)算法。因此,当高速缓存已满时,较旧的页面将被从cache中清除。即使那些较旧的页面被更频繁地访问。把整个过程想象成一条传送带。数据块被放入缓存中最近使用的位置。随着读取更多的数据块,会将较旧的数据块推送到cache中最近最少使用的位置上,直到它们从传送带上掉下来,或者换句话说,它们会被从缓冲中清理出去。
上图显示了传统的LRU缓存方案。
当从磁盘读取大量顺序读取并将其放入缓存时,即使这些数据只需要一次,但也有从缓存中清除更为频繁请求的内容的趋势。因此,从缓存的角度来看,它最终会得到许多不再需要的毫无价值的无用数据。当然,当请求更新的数据块时,它最终会被替换。
还存在最不常用least frequently used
(LFU)缓存。但是,它们有一个问题,即如果读取较新的数据不够频繁,可能会将其从缓存中逐出。因此,有大量的磁盘请求,这从一开始就违背了缓存的目的。因此,显而易见的方法似乎是以某种方式将两者结合起来–同时拥有一个LRU和一个LFU。
ZFS ARC
ZFS adjustable replacement cache (ARC)是这样一种高速缓存机制,其高速缓存最近的块请求以及频繁的块请求。它是获得专利的IBM adaptive replacement cache的实现,并进行了一些修改和扩展。
术语
- Adjustable Replacement Cache(ARC): 驻留在物理RAM中的缓存。它使用两个缓存组成–最常用的缓存和最近使用的缓存。高速缓存目录对缓存的指针进行索引,包括指向频繁使用的高速缓存和最近使用的高速缓存的磁盘的指针。
- 缓存目录: 目录内保存MRU、MFU、Ghost MRU和Ghost MFU缓存的指针索引。
- Most Recently Used(MRU)缓存: ARC最近使用的缓存。最近从文件系统请求的数据块缓存在此处。
- Most Frequently Used(MFU)缓存: ARC最常用的缓存。文件系统中请求最频繁的数据块缓存在此处。
- Ghost MRU: 将页面从MRU缓存收回到磁盘,以节省MRU中的空间。指针仍然跟踪被逐出的页面在磁盘上的位置。
- Ghost MFU: 将页面从MFU缓存收回到磁盘,以节省MFU中的空间。指针仍然跟踪被逐出的页面在磁盘上的位置。
- Level 2 Adjustable Replacement Cache(L2ARC): 驻留在物理内存外部的缓存,通常位于FAST SSD上。它是RAM ARC的物理扩展。
ARC算法
这是IBM ARC工作原理的简化版本,但它应该帮助您理解如何将优先级放在MRU和MFU上。首先,让我们假设您的缓存中有八个页面。缓存中的四个页面将用于MRU,四个页面将用于MFU。此外,还将有四个指针为幽灵MRU和四个指针为幽灵MFU。因此,缓存目录将引用16页实时缓存或收回的缓存。
- 正如预期的那样,当从文件系统读取块A时,它将被缓存在MRU中。缓存目录中的索引指针将引用MRU页。
- 假设现在从文件系统读取了一个不同的块(块B)。它也将被缓存在MRU中,缓存目录中的索引指针将引用第二个MRU页。由于块B的读取时间比块A晚,因此它在MRU缓存中的优先级高于块A。现在,MRU缓存中有两个页面。
- 现在,假设再次从文件系统读取块A。这将是块A的第二次读取。A被频繁地读取,因此它将被存储在MFU中。数据块必须至少读取两次才能存储在此处。此外,这也是最近的一个请求。所以该块不仅缓存在MFU中,而且还在缓存目录的MRU中被引用。因此,尽管两个页面驻留在缓存中,但缓存目录中有三个指针指向缓存中的两个块。
- 最终,缓存被上述步骤填满,并且我们在缓存目录的MRU和MFU的指针也被设置满。
- 这就是事情变得有趣的地方。假设我们现在需要从未缓存的文件系统中读取新的块。我们要缓存的页面比我们可以存储的页面多。因此,我们将需要从缓存中逐出一个页面。MRU中最旧的页面(称为最近最少使用的LRU)收到驱逐通知,并被Ghost MRU引用。新的页面现在将在MRU中可用于新读取的块。
- 如预期的那样,在从文件系统读取新读取的块之后,它被存储在MRU中并相应地被引用。因此,我们有一个Ghost MRU页面引用和一个已填满的缓存。
- 让我们假设从文件系统中重新读取最近被逐出的页面,这会给整个过程带来麻烦。因为Ghost MRU知道它最近被从缓存中逐出,所以我们将其称为“Ghost缓存命中”。因为ZFS知道它最近被缓存了,所以我们需要把它放回MRU缓存中;而不是MFU缓存中,因为它没有被Ghost MFU所引用。
- 遗憾的是,我们的缓存太小,无法存储页面。因此,我们必须将MRU增加一页来存储新的幻影命中。然而,我们的缓存只有这么大,所以我们必须对MFU的大小进行调整,缩小一页,以便为MRU腾出空间。当然,该算法在MFU和 Ghost MFU上以类似的方式进行处理。Ghost MFU的幻影点击量将扩大MFU,缩小MRU,为新页面腾出空间。
ZFS ARC扩展
前面的算法是IBM设计的ARC算法的简化版本。ZFS已经做了一些扩展,我将在这里说明一下:
- ZFS ARC将占用可用RAM的1/2。然而,这并不是固定的。如果您的服务器中有32 GB的RAM,这并不意味着缓存大小固定为16 GB。相反,总缓存将根据内核决策调整其大小。如果内核需要更多的RAM来执行调度进程,则ZFS ARC将进行调整,以便为内核所需处理的任何内容腾出空间。然而,如果有足够的RAM,ZFS ARC则可以占用的这部份的空间。
- ZFS ARC可以处理多种块大小,而IBM的实现只可以使用静态块大小。
- 内存页可以锁定在MRU或MFU中,以防止被逐出。IBM实现没有此功能。因此,在选择何时从cache中清除内存页时,ZFS ARC算法稍微复杂一些。
L2ARC
Level 2 ARC 或者 L2ARC 应该部署在一个读写速度很快的硬盘上。正如我在上一篇关于ZIL的文章中提到的,L2ARC 应该也是部署在DRAM DIMM(不一定是电池供电的)、快速SSD,或者10k以上的企业级SAS或FC磁盘。如果您决定为您的ZI和您的L2ARC使用相同的设备(这当然是可以接受的),那么您应该对其进行分区,以便ZIL占用非常少的空间,比如512MB或1 GB,并将其余的空间以 RAID-0 L2ARC提供添加到池里。因为 L2ARC 中的数据不需要持久保存,因为它启动时缓存将被擦除。
L2ARC是ARC在RAM中的扩展,当存在L2ARC时,以前的算法保持不变。这意味着随着MRU或MFU的增长,它们不会同时共享RAM中的ARC和SSD上的L2ARC,这将对性能产生巨大影响。取而代之的是,当页面即将被逐出时,遍历算法将把MRU和MFU页面逐出到8MB缓冲区中,该缓冲区稍后被设置为对L2ARC的原子写事务。这里的明显优势是从缓存中收回页面的延迟不会受到影响。此外,如果将大量数据块读取发送到高速缓存,则这些块在L2ARC遍历之前被逐出,而不是发送到L2ARC。这最大限度地减少了大规模顺序读取对L2ARC的污染。填充L2ARC也可能非常慢,也可能非常快,这取决于对数据的访问。
增加L2ARC
警告:某些主板在重新启动后会以不同方式向Linux内核注册磁盘。因此,在一次引导时标识为/dev/sda的磁盘在下一次引导时可能是/dev/sdb。对于存储数据的主池,这不是问题,因为ZFS可以根据元数据拓扑信息重建VDEV。然而,对于您的L2ARC和SLOG设备,不存在这样的元数据。所以与其通过它们的/dev/sdX将它们添加到池中,更应该使用/dev/disk/by-id/里的设备(实际上这是一个符号连接,指向/dev/sdX) ,把它添加到对应的Zpool中。如果您不注意此警告,您的slog设备可能根本没有添加到您的混合池中,您只能在后续把设备重新加入到池中。是否存在性能很好的SLOG,将很大程度的影响到应用程序的性能。
您可以使用“cache” VDEV添加L2ARC。建议您在L2ARC创建再RAID 0设备上,以便使用最多的空间和最快的速度。L2ARC不需要持久数据,因此它可以是易失性DIMM。假设我的池中有4个磁盘,以及两个60 GB的OCZ RevoDrive SSD。我将对SSD上的驱动器进行分区,将4 GB分配给zil,其余的分配给L2ARC。这就是如何将L2ARC添加到池中。在这里,我使用GNU parted首先创建分区,然后添加SSD。/dev/disk/by-id/中的设备指向/dev/sda和/dev/sdb。
# 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的大小:
#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中保存了大约5 GB的缓存数据(请记住,它是基于RAID 0的),但还有足够的缓存空间可用。事实上,上面的粘贴来自一个拥有32 GB DDR2内存的实时运行系统,该系统目前托管着这个博客。L2ARC在一周前进行了修改和重新添加。这向您显示了我在系统上填充L2ARC的速度。你的情况可能有所不同,但这并不令人惊讶。Ben Rockwood有一个Perl脚本,它可以根据MRU、mfu、ghost以及其他统计数据来分解ARC和L2ARC。在http://www.cuddletech.com/blog/pivot/entry.php?id=979上查看(我没有任何使用该脚本的经验)。
总结
ZFS ARC 改进了IBM最初的 ARC,同时保持了IBM的设计。然而,与Linux内核和其他操作系统部署的传统LRU和LFU缓存相比,ZFS ARC具有巨大的优势。而且,通过在快速固态硬盘或磁盘上添加L2ARC,我们可以快速检索大量数据,同时仍允许主机内核根据需要调整内存要求。