闲话缓存:ZFS 读缓存深入研究-ARC(一)

在Solaris ZFS 中实现的ARC(Adjustable Replacement Cache)读缓存淘汰算法真是很有意义的一块软件代码。它是基于IBM的Megiddo和Modha提出的ARC(Adaptive Replacement Cache)淘汰算法演化而来的。但是ZFS的开发者们对IBM 的ARC算法做了一些扩展,以更适用于ZFS的应用场景。ZFS ARC的最早实现展现在FAST 2003的会议上,并在杂志《;Login:》的一篇文章中被详细描述。

注:关于杂志《;Login:》,可参考这个链接:https://www.usenix.org/publications/login/2003-08/index.html

ZFS ARC真是一个优美的设计。在接下来的描述中,我将尽量简化一些机制,以便于大家更容易理解ZFS ARC的工作原理。关于ZFS ARC的权威描述,可以参考这个链接:http://src.opensolaris.org/source/xref/onnv/onnv-gate/usr/src/uts/common/fs/zfs/arc.c。在接下来的段落中,我将试着给大家深入讲解一下ZFS 读缓存的内部工作原理。我将关注点放在数据如何进入缓存,缓存如何调整它自己以适应I/O模式的变化,以及“Adjustable Replacement Cache”这个名字是如何来的。

缓存

嗯,在一些文件系统缓存中实现的标准的LRU淘汰算法是有一些缺点的。例如,它们对扫描读模式是没有抵抗性的。但你一次顺序读取大量的数据块时,这些数据块就会填满整个缓存空间,即使它们只是被读一次。当缓存空间满了之后,你如果想向缓存放入新的数据,那些最近最少被使用的页面将会被淘汰出去。在这种大量顺序读的情况下,我们的缓存将会只包含这些新读的数据,而不是那些真正被经常使用的数据。在这些顺序读出的数据仅仅只被使用一次的情况下,从缓存的角度来看,它将被这些无用的数据填满。

另外一个挑战是:一个缓存可以根据时间进行优化(缓存那些最近使用的页面),也可以根据频率进行优化(缓存那些最频繁使用的页面)。但是这两种方法都不能适应所有的workload。而一个好的缓存设计是能自动根据workload来调整它的优化策略。

ARC的内部工作原理

在ARC原始的实现(IBM的实现)和ZFS中的扩展实现都解决了这些挑战,或者说现存问题。我将描述由Megiddo和Modha提出的Adaptive Replacement Cache的一些基本概念,ZFS的实现版本作为这个实现机制的一个扩展来介绍。这两种实现(原始的Adaptive Replacement Cache和ZFS Adjustable Replacement Cache)共享一些基本的操作原理,所以我认为这种简化是一种用来解释ZFS ARC切实可行的途径。

首先,假设我们的缓存中有一个固定的页面数量。简单起见,假设我们有一个8个页面大小的缓存。为了是ARC可以工作,在缓存中,它需要一个2倍大小的管理表。

这个管理表分成4个链表。头两个链表是显而易见的:

·         最近最多使用的页面链表 (LRU list)

·         最近最频繁使用的页面链表(LFU list)

另外两个链表在它们的角色上有些奇怪。它们被称作ghost链表。那些最近被淘汰出去的页面信息被存储在这两个链表中:

·         存储那些最近从最近最多使用链表中淘汰的页面信息 (Ghost list for LRU)

·         存储那些最近从最近最频繁使用链表中淘汰的页面信息(Ghost list for LFU)

这两个ghost链表不储存数据(仅仅储存页面信息,比如offset,dev-id),但是在它们之中的命中对ARC缓存工作的行为具有重要的影响,我将在后面介绍。那么在缓存中都发生了什么呢?

假设我们从磁盘上读取一个页面,并把它放入cache中。这个页面会放入LRU 链表中。

接下来我们读取另外一个不同的页面。它也会被放入缓存。显然,他也会被放入LRU 链表的最近最多使用的位置(位置1):

好,现在我们再读一次第一个页面。我们可以看到,这个页面在缓存中将会被移到LFU链表中。所有进入LRU链表中的页面都必须至少被访问两次。无论什么时候,一个已经在LFU链表中的页面被再次访问,它都会被放到LFU链表的开始位置(most  frequently used)。这么做,那些真正被频繁访问的页面将永远呆在缓存中,不经常访问的页面会向链表尾部移动,最终被淘汰出去。

随着时间的推移,这两个链表不断的被填充,缓存也相应的被填充。这时,缓存已经满了,而你读进了一个没有被缓存的页面。所以,我们必须从缓存中淘汰一个页面,为这个新的数据页提供位置。这个数据页可能刚刚才被从缓存中淘汰出去,也就是说它不被缓存中任何的非ghost链表引用着。

假设LRU链表已经满了:

这时在LRU链表中,最近最少使用的页面将会被淘汰出去。这个页面的信息会被放进LRU ghost链表中。

现在这个被淘汰的页面不再被缓存引用,所以我们可以把这个数据页的数据释放掉。新的数据页将会被缓存表引用。

随着更多的页面被淘汰,这个在LRU ghost中的页面信息也会向ghost链表尾部移动。在随后的一个时间点,这个被淘汰页面的信息也会到达链表尾部,LRU链表的下一次的淘汰过程发生之后,这个页面信息也会从LRU ghost链表中移除,那是就再也没有任何对它的引用了。

好的,如果这个页面在被从LRU ghost链表中移除之前,被再一次访问了,将会发生什么?这样的一个读将会引起一次幽灵(phantom)命中。由于这个页面的数据已经从缓存中移除了,所以系统还是必须从后端存储媒介中再读一次,但是由于这个幽灵命中,系统知道,这是一个刚刚淘汰的页面,而不是第一次读取或者说很久之前读取的一个页面。ARC用这个信息来调整它自己,以适应当前的I/O模式(workload)。

很显然,这个迹象说明我们的LRU缓存太小了。在这种情况下,LRU链表的长度将会被增加一。显然,LFU链表的长度将会被减一。

但是同样的机制存在于LFU这边。如果一次命中发生在LFU ghost 链表中,它会减少LRU链表的长度(减一),以此在LFU 链表中加一个可用空间。

利用这种行为,ARC使它自己自适应于工作负载。如果工作负载趋向于访问最近访问过的文件,将会有更多的命中发生在LRU Ghost链表中,也就是说这样会增加LRU的缓存空间。反过来一样,如果工作负载趋向于访问最近频繁访问的文件,更多的命中将会发生在LFU Ghost链表中,这样LFU的缓存空间将会增大。

进一步,这种行为开启了一个灵活的特性:假设你为处理log文件而读取了大量的文件。你只需要每个文件一次。一个LRU 缓存将会把所有的数据缓存住,这样也就把经常访问的数据也淘汰出去了。但是由于你仅仅访问这些文件一次,它们不会为你带来任何价值一旦它们填满了缓存。

一个ARC缓存的行为是不同的。显然这样的工作负载仅仅会很快填满LRU链表空间,而这些页面很快就会被淘汰出去。但是由于每个这样的页面仅仅被访问一次,它们基本不太可能在为最近访问的文件而设计的ghost链表中命中。这样,LRU的缓存空间不会因为这些仅读一次的页面而增加。

假如你把这些log文件与一个大的数据块联系在一起(为了简单起见,我们假设这个数据块没有自己的缓存机制)。数据文件中的数据页应该会被频繁的访问。被LFU ghost链表引用的正在被访问的页面就很有可能大大的高于LRU ghost链表。这样,经常被访问的数据库页面的缓存空间就会增加。最终,我们的缓存机制就会向缓存数据块页面优化,而不是用log文件来污染我们的缓存空间。

闲话缓存:ZFS 读缓存深入研究-ARC(一)

时间: 2024-11-05 02:55:06

闲话缓存:ZFS 读缓存深入研究-ARC(一)的相关文章

闲话缓存:ZFS 读缓存深入研究-ARC(二)

Solaris ZFS ARC的改动(相对于IBM ARC) 如我前面所说,ZFS实现的ARC和IBM提出的ARC淘汰算法并不是完全一致的.在某些方面,它做了一些扩展: ·         ZFS ARC是一个缓存容量可变的缓存算法,它的容量可以根据系统可用内存的状态进行调整.当系统内存比较充裕的时候,它的容量可以自动增加.当系统内存比较紧张(其它事情需要内存)的时候,它的容量可以自动减少. ·         ZFS ARC可以同时支持多种块大小.原始的实现假设所有的块都是相同大小的. ·  

判断是否启用缓存,启用后直读缓存信息

文件/framework/www/post_control.php 26-38行function index_f(){$id = $this->get("id");$pid = $this->get('pid');if(!$id && !$pid){error(P_Lang('未指定项目'),'','error');}$project_rs = $this->call->phpok('_project',array("phpok&quo

优化MySQL,还是使用缓存?读一篇文章有感

今天我想对一个Greenfield项目上可以采用的各种性能优化策略作个对比.换言之,该项目没有之前决策强加给它的各种约束限制,也还没有被优化过. 具体来说,我想比较的两种优化策略是优化MySQL和缓存.提前指出,这些优化是正交的,唯一让你选择其中一者而不是另一者的原因是他们都耗费了资源,即开发时间. 优化MySQL 优化MySQL时,一般会先查看发送给mysql的查询语句,然后运行explain命令.稍加审查后很常见的做法是增加索引或者对模式做一些调整. 优点 1.一个经过优化的查询对于所有使用

[转]缓存、缓存算法和缓存框架简介

以下内容转自:http://www.leexiang.com/cache-algorithm (转载请注明出处)-----------------------------------分割线---------------------------------------------- 引言 我们都听过 cache,当你问他们是什么是缓存的时候,他们会给你一个完美的答案,可是他们不知道缓存是怎么构建的,或者没有告诉你应该采用什么标准去选择缓存框架.在这边文章,我们会去讨论缓存,缓存算法,缓存框架以及哪

缓存、缓存算法和缓存框架

译者:lixiang 译文:http://www.zavakid.com/25 原文:http://www.jtraining.com/component/content/article/35-jtraining-blog/98.html 引言 我们都听过 cache,当你问他们是什么是缓存的时候,他们会给你一个完美的答案,可是他们不知道缓存是怎么构建的,或者没有告诉你应该采用什么标准去选择缓存框架.在这边文章,我们会去讨论缓存,缓存算法,缓存框架以及哪个缓存框架会更好. 面试 “缓存就是存贮数

缓存、缓存算法和缓存框架简介

文章转载于:http://blog.jobbole.com/30940/ 引言 我们都听过 cache,当你问他们是什么是缓存的时候,他们会给你一个完美的答案,可是他们不知道缓存是怎么构建的,或者没有告诉你应该采用什么标准去选择缓存框架.在这边文章,我们会去讨论缓存,缓存算法,缓存框架以及哪个缓存框架会更好. 面试 “缓存就是存贮数据(使用频繁的数据)的临时地方,因为取原始数据的代价太大了,所以我可以取得快一些.” 这就是 programmer one (programmer one 是一个面试

[转]缓存、缓存算法和缓存框架简单介绍

引言 我们都听过 cache,当你问他们是什么是缓存的时候,他们会给你一个完美的答案.可是他们不知道缓存是怎么构建的.或者没有告诉你应该採用什么标准去选择缓存框架. 在这边文章,我们会去讨论缓存.缓存算法.缓存框架以及哪个缓存框架会更好. 面试 "缓存就是存贮数据(使用频繁的数据)的暂时地方,由于取原始数据的代价太大了.所以我能够取得快一些." 这就是 programmer one (programmer one 是一个面试者)在面试中的回答(一个月前,他向公司提交了简历,想要应聘要求

【转】图片缓存之内存缓存技术LruCache、软引用 比较

每当碰到一些大图片的时候,我们如果不对图片进行处理就会报OOM异常,这个问题曾经让我觉得很烦恼,后来终于得到了解决,那么现在就让我和大家一起分享一下吧.这篇博文要讲的图片缓存机制,我接触到的有两钟,一种是软引用,另一种是内存缓存技术.先来看下两者的使用方式,再来作比较.除了加载图片时要用到缓存处理,还有一个比较重要的步骤要做,就是要先压缩图片. 1.压缩图片至于要压缩到什么状态就要看自己当时的处境了,压缩图片的时候既要达到一个小的值,又不能让其模糊,更不能拉伸图片. /** * 加载内存卡图片

[转] 通俗讲解:缓存、缓存算法和缓存框架

通俗讲解:缓存.缓存算法和缓存框架 1引言 我们都听过 cache,当你问他们是什么是缓存的时候,他们会给你一个完美的答案,可是他们不知道缓存是怎么构建的,或者没有告诉你应该采用什么标准去选择缓存框架.在这边文章,我们会去讨论缓存,缓存算法,缓存框架以及哪个缓存框架会更好. 2面试 “缓存就是存贮数据(使用频繁的数据)的临时地方,因为取原始数据的代价太大了,所以我可以取得快一些.” 这就是 programmer one (programmer one 是一个面试者)在面试中的回答(一个月前,他向