ZFS源代码之旅——ARC模块分析

来源:互联网 发布:软件冲突蓝屏怎么办 编辑:程序博客网 时间:2024/05/17 22:49

0. 背景介绍

ARC(Adaptive Replacement Cache)是ZFS的磁盘缓存,它位于DMU模块和SPA层之间,是一个虚拟块层,这种层次可以让一个文件系统和它的快照与克隆共同使用缓存的数据。从下图我们可以看出ARC在整个ZFS文件系统结构中的位置。

本文接下来的内容首先会介绍磁盘缓存在整个文件系统中的作用,接着简单的介绍采用LRU策略缓存的实现,最后,再详细介绍ZFS使用的ARC缓存原理以及它的具体实现。


1. 磁盘缓存

当两个不同速度的设备需要协同工作时,总会出现速度较快的设备等待速度较慢的设备的情况。此时,两个设备的总吞吐量便由较慢的设备决定,即为慢速设备的吞吐量。为了解决这个问题,系统设计者通常在两个设备之间加入一个缓存设备(cache),来减少访问慢速设备的代价。

缓存设备通过提供对慢速设备上的数据的快速访问来减少访问慢速设备的时间,通常缓存的速度和快速设备相当,而容量远小于慢速设备,因此在一个时刻,缓存中只能保存部分慢速设备中的数据,这就需要我们来决定该将哪些数据保存于缓存设备上。

对于计算机系统来说,由于磁盘的速度相对于计算机其他组件的速度太慢,所以很多时候它都是计算机的瓶颈。为了解决该问题,通常的做法是在内存中分配一块空间,用来做磁盘设备的缓存。该缓存中会存放系统中近期使用的数据块,即,缓存中的数据是磁盘上热数据的一份拷贝。根据计算机程序的局部性原理,再加上预读、延迟写等一系列技术,可以大大提高系统访问磁盘数据的性能。

磁盘缓存对于上层来说是透明的。上层的读写请求会转化为对磁盘块的读写请求,这是,当要读写的磁盘块在磁盘缓存中时,则直接对磁盘缓存进行操作,这样可以大大加快程序的响应时间。而若要读写的磁盘块不在磁盘缓存中时,系统会去磁盘上读取数据,再返回用户的响应,同时将数据放入磁盘缓存中,期望很快程序会再次读写该数据块。当上层请求的数据在缓存中时,成为缓存命中,缓存的命中率也是一个缓存实现的重要指标。

管理缓存最主要的工作有两个:

1、应该将哪些数据块放在缓存之中;

2、当缓存已满时,应该将哪些块替换出去。


2. LRU缓存替换算法

在了解了磁盘缓存的相关概念之后,我们来介绍磁盘缓存的一种简单有效的实现——LRU缓存替换算法。

一个磁盘缓存主要需要完成两个目标:一是给定一个磁盘块号,可以快速找到保存该磁盘块数据的缓存块;二是当缓存已满而有新的数据的请求时,要决定将那个缓存块中的数据换出,以存放新的磁盘块。

这两个目标需要我们采取两种方式对缓存块进行管理,为了实现快速访问,最直观的办法是通过Hash表;而为了实现数据替换,我们也需要另一种结构来组织缓存块,对于LRU缓存替换算法来说,采用一个LRU链表来管理缓存块。

LRU缓存的缓存块的组织如下图所示:


可以看到,可以通过块号的Hash值来快速找到其对应的缓存块。而缓存块通过一个双向链表链接。MRU(Most Recently Used)表示最近使用的最频繁的缓存块,而LRU(Least Recently Used)表示最近最少使用的缓存块。

因此,当我们需要对缓存块进行替换时,只要将LRU所指向的缓存块替换出去即可。而当一个缓存块被访问时,会将该缓存块移至MRU端,表示它刚刚被访问过,这样在接下来的替换过程中它最不容易被换出。而一个缓存长期没有被访问,会逐渐移至LRU端,并在某个时刻被换出。

LRU替换算法的思想是通过磁盘块过去访问的情况来预测未来,认为一个磁盘块刚被访问,那它很有可能在接下来也会被访问,这是符合计算机程序的局部性原理的。

磁盘缓存还有一些其它一些优化策略,下面做简要介绍:

1. 预读。很多时候,程序会连续的对磁盘块进行读取操作,比如当一个人看电影时。磁盘缓存会通过预读的方式,即在上层请求读取N号磁盘块时,磁盘缓存会将N、N+1、N+2、…、N+M个磁盘块也读入缓存中,并期望这些缓存会在接下来依次命中。预读的大小和时机都属于预读策略的问题,现在已经有很多预读算法,可能会根据上层读取磁盘块的方式来进行预读,而不是简单的连续读取后面的磁盘块。(感兴趣的同学们可以去看相关资料)

2. 延迟写与写合并。当上层修改了缓存块中的数据时,会出现缓存中的数据与磁盘上的数据不一致的情况。这时我们应该将修改过的缓存块写回磁盘,以防止突然断电造成的数据丢失。但是,如果我们写磁盘过于频繁,会极大影响系统的性能。因此通常的做法不是马上写入,而是有一个线程定期(如30秒)将缓存中的“脏”数据写回磁盘。这种延迟写还有一个作用就是可以做写合并。即写入线程可以在写入磁盘时将多个连续的磁盘块一次性写入,这样可以减少磁盘的寻道次数,提高磁盘的吞吐量。在每一个缓存块结构中,都会有一个DIRTY的标记位,表示该缓存块是否被修改。当一个缓存块要被换出时,需要检查该标记位,对于修改过的缓存块,在换出时要将其写回磁盘。

3. 绕过写(bypass write)。有时候,程序可能会一次写大量的数据到磁盘上(远大于磁盘缓存大小),此时的做法是绕过缓存直接将数据写到磁盘上。这样做的目的是减少数据拷贝次数(从应用程序缓冲区到磁盘缓存,从磁盘缓存到磁盘变为从应用程序缓冲区直接到磁盘),提高写入性能。

关于LRU的更多介绍大家可以看《Practical File System Design with the Be File System》的第8章及其代码实现。


3. ARC缓存替换算法

ARC缓存替换算法是IBM两位科学家在《Arc: A Self-tuning, Low Overhead Replacement Cache》在这篇论文中提出的。它的基本思想和LRU缓存替换算法相同,具体描述如下:

1. ARC中缓存数据的单位是页(page,即一个磁盘块,该论文采用术语“页”),ARC中维护两个LRU页队列:L1和L2。其中L1中保存了那些最近只被访问了一次的页,而L2中保存了最近至少被访问了两次的页。由于L1中存储的是最近访问的页,而L2中存放的是最近频繁被访问的页,因此我们可以说L1捕获“最近”,而L2捕获最“频繁”。这种做法又对缓存的页进行了分类。

2. 当一个页第一次被访问时,会将它放入L1队列中。如果此时L1队列已满,则要决定该将哪个页换出。

3. 当一个在L1中的页再一次被访问时,会将它放入L2队列中。如果此时L2队列已满,则要决定该将哪个页换出。

4. 系统会动态维护L1和L2队列的大小,具体的算法大家可以参考论文。本文之后会介绍ZFS的ARC算法中如何对两个队列进行维护。

在论文的最后,有ARC缓存替换算法和LRU缓存替换算法的比较,从数据可以看出ARC比LRU算法有更高的性能。


4. ZFS中ARC缓存替换算法的实现


4.1. 背景介绍

ZFS中的ARC是对论文中算法的实现,当然,它根据ZFS系统的需要对算法进行了一定的修改。下面的内容来自arc.c文件的注释,它解释了ZFS的ARC实现与论文中的异同。

/* * DVA-based Adjustable Replacement Cache * * While much of the theory of operation used here is * based on the self-tuning, low overhead replacement cache * presented by Megiddo and Modha at FAST 2003, there are some * significant differences: * * 1. The Megiddo and Modha model assumes any page is evictable. * Pages in its cache cannot be "locked" into memory.  This makes * the eviction algorithm simple: evict the last page in the list. * This also make the performance characteristics easy to reason * about.  Our cache is not so simple.  At any given moment, some * subset of the blocks in the cache are un-evictable because we * have handed out a reference to them.  Blocks are only evictable * when there are no external references active.  This makes * eviction far more problematic:  we choose to evict the evictable * blocks that are the "lowest" in the list. * * There are times when it is not possible to evict the requested * space.  In these circumstances we are unable to adjust the cache * size.  To prevent the cache growing unbounded at these times we * implement a "cache throttle" that slows the flow of new data * into the cache until we can make space available. * * 2. The Megiddo and Modha model assumes a fixed cache size. * Pages are evicted when the cache is full and there is a cache * miss.  Our model has a variable sized cache.  It grows with * high use, but also tries to react to memory pressure from the * operating system: decreasing its size when system memory is * tight. * * 3. The Megiddo and Modha model assumes a fixed page size. All * elements of the cache are therefor exactly the same size.  So * when adjusting the cache size following a cache miss, its simply * a matter of choosing a single page to evict.  In our model, we * have variable sized cache blocks (rangeing from 512 bytes to * 128K bytes).  We therefor choose a set of blocks to evict to make * space for a cache miss that approximates as closely as possible * the space used by the new block. * * See also:  "ARC: A Self-Tuning, Low Overhead Replacement Cache" * by N. Megiddo & D. Modha, FAST 2003 */

以上内容主要介绍了三个不同之处:

1. 论文中提出的ARC算法认为每个页都是可换出的,而ZFS的ARC实现需要在有些时候将部分缓存块“锁在”内存中,即只能换出那些没有加锁的缓存块;

2. 论文中提出的ARC算法中的每个页大小相等,而ZFS的ARC实现支持可变缓存块大小;

3. 论文中需要换出时,可以确定要换出多少个页,即当需要换入N个页时,只需要换出N个页即可。而ZFS的ARC实现由于采用可变缓存块,因此换出时不确定要换出多少个块,而必须依次遍历,直到换出的块大小总数满足要求为止。

原创粉丝点击