在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(一)的更多相关文章

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

    Solaris ZFS ARC的改动(相对于IBM ARC) 如我前面所说,ZFS实现的ARC和IBM提出的ARC淘汰算法并不是完全一致的.在某些方面,它做了一些扩展: ·         ZFS A ...

  2. 使用Retrofit和Okhttp实现网络缓存。无网读缓存,有网根据过期时间重新请求 (转)

    使用Retrofit和Okhttp实现网络缓存,更新于2016.02.02原文链接:http://www.jianshu.com/p/9c3b4ea108a7 本文使用 Retrofit2.0.0-b ...

  3. TimesTen 应用层数据库缓存学习:4. 仅仅读缓存

    在运行本文样例前.首先先运行TimesTen 应用层数据库缓存学习:2. 环境准备中的操作. Read-only Cache Group的概念 仅仅读缓存组例如以下图: 仅仅读缓存组(Read-Onl ...

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

    今天我想对一个Greenfield项目上可以采用的各种性能优化策略作个对比.换言之,该项目没有之前决策强加给它的各种约束限制,也还没有被优化过. 具体来说,我想比较的两种优化策略是优化MySQL和缓存 ...

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

    每当碰到一些大图片的时候,我们如果不对图片进行处理就会报OOM异常,这个问题曾经让我觉得很烦恼,后来终于得到了解决,那么现在就让我和大家一起分享一下吧.这篇博文要讲的图片缓存机制,我接触到的有两钟,一 ...

  6. Redis缓存雪崩,缓存穿透,热点key解决方案和分析

    缓存穿透 缓存系统,按照KEY去查询VALUE,当KEY对应的VALUE一定不存在的时候并对KEY并发请求量很大的时候,就会对后端造成很大的压力. (查询一个必然不存在的数据.比如文章表,查询一个不存 ...

  7. Hibernatne 缓存中二级缓存简单介绍

    hibernate的session提供了一级缓存,每个session,对同一个id进行两次load,不会发送两条sql给数据库,但是session关闭的时候,一级缓存就失效了. 二级缓存是Sessio ...

  8. 一级缓存、二级缓存、延迟加载、hibernate session 域 pojo三种状态

    1.一级缓存(session缓存 ).二级缓存      意义:提高hibernate查询效率.    缺点:可能会因并发,产生数据不一致.      本质:基于session 的缓存,利用hiber ...

  9. [原创]java WEB学习笔记93:Hibernate学习之路---Hibernate 缓存介绍,缓存级别,使用二级缓存的情况,二级缓存的架构集合缓存,二级缓存的并发策略,实现步骤,集合缓存,查询缓存,时间戳缓存

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

随机推荐

  1. Bzoj3197: [Sdoi2013]assassin

    题面 传送门 Sol 套路:找出重心,如果有两个就新建一个点 然后把这棵树hash一下 设\(f[i][j]\)表示第一颗树到\(i\)第二棵树到\(j\),子树\(i,j\)同构的付出的最小代价 转 ...

  2. GIS平台结构设计

    前言: WebGIS由于技术发展和功能定位的原因,一般在进行架构设计的时候更多地考虑是否容易实现.用户交互.数据传输方便.渲染效果等方面,对强GIS的应用考虑得少,所以架构上与桌面的GIS平台很不一样 ...

  3. Qt Quick程序的发布

    要将程序发布出去,首先需要使用release方式编译程序,然后将生成的.exe可执行文件和需要的库文件发在一起打包进行发布. 要确定需要哪些动态库文件,可以直接双击.exe文件,提示缺少那个dll文件 ...

  4. Python爬虫教程-10-UserAgent和常见浏览器UA值

    Python爬虫教程-10-UserAgent和常见浏览器UA值 有时候使用爬虫会被网站封了IP,所以需要去模拟浏览器,隐藏用户身份, UserAgent 包含浏览器信息,用户身份,设备系统信息 Us ...

  5. 使用Netty3或Netty4发布Http协议服务

    现在是2018年1月11日18:12分,已经是下班时间了,小Alan今天给大家简单的介绍一下Netty,让大家以后在使用到Netty的时候能够有一定的了解和基础,这样深入学习Netty以及以后灵活应用 ...

  6. PURGE 的用法说明

    PURGE PurposeUse the PURGE statement to remove a table or index from your recycle bin and release al ...

  7. 如何查看服务器CPU核心数和线程数

    知道服务器CPU型号,那么我们如何在服务器里面查看服务器CPU核心数和线程数呢? 步骤: 先用鼠标右键点击屏幕最下方的任务栏空白处.会弹出一个菜单. 在菜单中用鼠标左键点选“启动任务管理器”. 点击任 ...

  8. python基础——操作系统简介

    不同应用领域的主流操作系统 l  桌面操作系统 l  服务器操作系统 l  嵌入式操作系统 l  移动设备操作系统 桌面操作系统 Windows系列 用户群体很大 MacOS 适合于开发人员 Linu ...

  9. NAT原理简介、各种 ADSL Modem 及路由器的端口映射方法

    NAT原理简介 NAT英文全称是“Network Address Translation”,中文意思是“网络地址转换”,它是一个IETF(Internet Engineering Task Force ...

  10. Java学习---程序设计_基础题[1]

    180813 补全没有的答案! 0. 数组排序大全[冒泡/选择/快速/插入] package com.ftl; import java.io.BufferedReader; import java.i ...