缓存设计的一些思考

标签: 体系结构与操作系统 缓存设计,LIRS,cache锁粒度 | 发表时间:2011-06-19 20:37 | 作者:chuanhui Sepher
出处:http://www.nosqlnotes.net

互联网架构中缓存无处不在,某厂牛人曾经说过:”缓存就像清凉油,哪里不舒服,抹一下就好了”。高品质的存储容量小,价格高;低品质存储容量大,价格低,缓存的目的就在于”扩充”高品质存储的容量。本文探讨缓存相关的一些问题。

LRU替换算法

缓存的技术点包括内存管理和替换算法。LRU是使用最多的替换算法,每次淘汰最久没有使用的元素。LRU缓存实现分为两个部分:Hash表和LRU链表,Hash表用于查找缓存中的元素,LRU链表用于淘汰。内存常以Slab的方式管理。

上图是Memcache的内存管理示意图,Memcache以Slab方式管理内存块,从系统申请1MB大小的大块内存并划分为不同大小的Chunk,不同Slab的Chunk大小依次为80字节,80 * 1.25,80 * 1.25^2, …。向Memcache中添加item时,Memcache会根据item的大小选择合适的Chunk。

Oceanbase最初也采用LRU算法,只是内存管理有些不同。Oceanbase向系统申请2MB大小的大块内存,插入item时直接追加到最后一个2MB内存块的尾部,当缓存的内存量太大需要回收时根据一定的策略整块回收2MB的内存,比如回收最近最少使用的item所在的2MB内存块。这样的做法虽然不是特别精确,但是内存管理简单,对于系统初期很有好处。

缓存锁

缓存需要操作两个数据结构:Hash表和LRU链表。多线程操作cache时需要加锁,比较直接的做法是整体加一把大锁后再操作Hash表和LRU链表。有如下的优化思路:

1, Hash表和LRU链表使用两把不同的锁,且Hash表锁的粒度可以降低到每个Hash桶一把锁。这种做法的难点是需要处理两种数据结构不一致导致的问题,假设操作顺序为read hash -> del hash item -> del lru item -> read lru item,最后一次read lru item时item所在的内存块可能已经被回收或者重用,一般需要引入引用计数并考虑复杂的时序问题。

2, 采用多个LRU链表以减少LRU表锁粒度。Hash表的锁冲突可以通过增加Hash桶的个数来解决,而LRU链表是一个整体,难以分解。可以将缓存的数据分成多个工作集,每个item属于某个工作集,每个工作集一个LRU链表。这样做的主要问题是可能不均衡,比如某个工作集很热,某些从整体上看比较热的数据也可能被淘汰。

3, 牺牲LRU的精确性以减少锁。比如Mysql中的LRU算法变形,大致如下:将LRU链表分成两部分,前半部分和后半部分,如果访问的item在前半部分,什么也不做,而不是像传统的LRU算法那样将item移动到链表头部;又如Linux Page Cache中的CLOCK算法。Oceanbase目前的缓存算法也是通过牺牲精确性来减少锁。前面提到,Oceanbase缓存以2MB的内存块为单位进行淘汰,最开始采用LRU策略,每次淘汰最近最少使用的item所在的2MB内存块,然而,这样做的问题是需要维护最近最少使用的item,即每次读写缓存都需要加锁。后续我们将淘汰策略修改为:每个2MB的内存块记录一个访问次数和一个最近访问时间,每次读取item时,如果访问次数大于所有2MB内存块访问次数的平均值,更新最近访问时间;否则,将访问次数加1。根据记录的最近访问时间淘汰2MB内存块。虽然,这个算法的缓存命中率不容易评估,但是缓存读取只需要一些原子操作,不需要加锁,大大减少了锁粒度。

4, 批量操作。缓存命中时不需要立即更新LRU链表,而是可以将命中的item保存在线程Buffer中,积累了一定数量后一次性更新LRU链表。

LIRS思想

Cache有两个问题:一个是前面提到的降低锁粒度,另一个是提高精准度,或者称为提高命中率。LRU在大多数情况下表现是不错的,但是有如下的问题:

1, 顺序扫描。顺序扫描的情况下LRU没有命中情况,而且会淘汰其它将要被访问的item从而污染cache。

2, 循环的数据集大于缓存大小。如果循环访问且数据集大于缓存大小,那么没有命中情况。

之所以会出现上述一些比较极端的问题,是因为LRU只考虑访问时间而没有考虑访问频率,而LIRS在这方面做得比较好。LIRS将数据分为两部分:LIR(Low Inner-reference Recency)和HIR(High Inner-reference Recency),其中,LIR中的数据是热点,在较短的时间内被访问了至少两次。LIRS可以看成是一种分级思想:第一级是HIR,第二级是LIR,数据先进入到第一级,当数据在较短的时间内被访问两次时成为热点数据则进入LIR,HIR和LIR内部都采用LRU策略。这样,LIR中的数据比较稳定,解决了LRU的上述两个问题。LIRS论文中提出了一种实现方式,不过我们可以做一些变化,如可以实现两级cache,cache元素先进入第一级cache,当访问频率达到一定值(比如2)时升级到第二级,第一级和第二级均内部采用LRU进行替换。Oracle Buffer Cache中的Touch Count算法也是采用了类似的思想。

SSD与缓存

SSD发展很快,大有取代传统磁盘之势。SSD的发展是否会使得单机缓存变得毫无必要我们无从得知,目前,Memory + SSD + 磁盘的混合存储方案还是比较靠谱的。SSD使用可以有如下不同的模式:

1, write-back:数据读写都走SSD,内存中的数据写入到SSD即可,另外有单独的线程定期将SSD中的数据刷到磁盘。典型的代表如Facebook Flashcache。

2, write-through:数据写操作需要先写到磁盘,内存和SSD合在一起看成两级缓存,即cache中相对较冷的数据在SSD,相对较热的数据在内存。

当然,随着SSD的应用,我想减少缓存锁粒度的重要性会越来越突出。

总结&推荐资料

到目前为止,我们在SSD,缓存相关优化的工作还是比较少的。今后的一年左右时间,我们将会投入一定的精力在系统优化上,相信到时候再来总结的时候认识会更加深刻。我想,缓存相关的优化工作首先要做的是根据需求制定一个大致的评价标准,接着使用实际数据做一些实验,最终可能会同时保留两到三种实现方式或者配置略微有所不同的缓存实现。缓存相关的推荐资料如下:

[1] Touch Count Algorithm. http://youyus.com/wp-content/uploads/resource/Shallahamer%20TC4a.pdf

[2] LIRS. http://portal.acm.org/citation.cfm?id=511340

相关 [缓存 设计 思考] 推荐:

缓存设计的一些思考

- Sepher - NOSQL Notes
互联网架构中缓存无处不在,某厂牛人曾经说过:”缓存就像清凉油,哪里不舒服,抹一下就好了”. 高品质的存储容量小,价格高;低品质存储容量大,价格低,缓存的目的就在于”扩充”高品质存储的容量. 缓存的技术点包括内存管理和替换算法. LRU是使用最多的替换算法,每次淘汰最久没有使用的元素. LRU缓存实现分为两个部分:Hash表和LRU链表,Hash表用于查找缓存中的元素,LRU链表用于淘汰.

表单设计的思考

- - 腾讯ISUX - 社交用户体验设计 - Better Experience Through Design
我们几乎每天都会接触形形色色的表单,登录账号、填写信息以获取服务、发布内容等. 然而填写表单的过程往往不是特别愉悦的,我们需要消耗时间输入信息,点击提交,可能还需要等待审核;尤其是碰到较为复杂、流程长的表单,如果用户体验较差,很容易让人产生挫败感,在中途选择放弃. 那么,如何提高用户填写表单的效率,防止他们出错或中途流失,提升愉悦度及转化率呢.

Web应用的缓存设计模式

- - robbin的自言自语
从10年前的2003年开始,在Web应用领域,ORM(对象-关系映射)框架就开始逐渐普及,并且流行开来,其中最广为人知的就是Java的开源ORM框架Hibernate,后来Hibernate也成为了EJB3的实现框架;2005年以后,ORM开始普及到其他编程语言领域,其中最有名气的是Ruby on rails框架的ORM - ActiveRecord.

“本地缓存”架构设计

- - ITeye博客
最近在做的项目其实是对老系统的一个深度改造,在老系统里缓存使用这块感觉有些瑕疵. 在老系统里不管是“配置数据”还是“业务数据”都统一使用redis作为缓存. “业务数据”使用redis作为缓存无可厚非,但“配置数据”使用使用redis就感觉不是很妥. 首先:过渡依赖redis,一些开关配置都依赖redis,如果redis服务挂掉整个服务瘫痪;.

细节思考表单交互设计之必选项思考

- 翔 - 携程UED
每当页面中出现洋洋洒洒的表单,用户就会开始感到头疼,有些用户就会直接选择放弃,而我想讨论的是,如何面对表单时让用户直接注意他们需要填写的必填项,减少不需要的信息的干扰. 必选项是以什么形式出现在现如今的表单中的呢. 下面是一个关于web表单设计的调查报告,这个结果来源于100个令人瞩目的网站. 41%的网站使用标签右对齐 (YouTube, Facebook, Metacafe).

思考系统API设计的问题

- edware_love - C++博客-首页原创精华区
最近正好在思考系统API设计中考量的一些问题,. 我现在的理解是这样的,假设有巨大的真实内存. windows首先将高2G的内存自己占了,用作各种内核对象. 这2G内存共享给每个进程,但进程不能直接访问,只能通过windows给定的函数访问. : 然后每个进程都给他2G内存,进程如果创建自己的对象就放到自己那2G内存里面,如果要建立内核对象就放到共享的那高2G里面去.

财务系统设计的思考

- - 行业应用 - ITeye博客
说到财务系统的设计,就不由得联想到了目前很流行的一个职业“互联网产品经理”,他们的设计着眼于用户体验,创造出新的功能,改善着上亿网民的生活,比如扫一扫,摇一摇等. 财务系统不同于互联网的产品,它的复杂性对于没有深入了解它的人来说,是不太能想象出来的. 互联网的功能开发,讲究的是时效,从一个点子,到产品发布可能只用一周的时间,然后如果市场冷淡,可能第三周就下线了.

卡诺模型—设计品质与设计价值的思考

- 盛开 - 所有文章 - UCD大社区
对于设计的结果,我们总是希望看到亮点,品质,细节,等等. 但是这些考量的点混合在一起,让设计师在执行设计工作的时候很难针对每一个设计进行核实考虑. 我想分享给大家的是,对于产品中不同类型的品质,其所要达到的目的是不同的,设计的终点也是不同的,只有在合适的位置用对力,才能塑造出优秀的设计. 同时也能发现,其实我们的工作很多时间是在默默的做设计,绝大多数工作并不是每个都是那么重要和耀眼,但是却是必须和紧要的.

一名设计师的职业化思考

- 品味视界 - Taobao UED Team
开篇先说好,这个题目其实并不适合我这个阅历的人来讲,但是作为工作过7-8年的设计来说,这是不得不思考的问题,今天只是和大家汇报下我自己的想法,仅此而已,言论如有不当,纯属参考. ——弥难(lcjeremy). 自己做视觉设计也小又年头了也没混出什么成就,先后做过广告公司、传媒公司,电视媒体和互联网公司,小到几人的唱片公司工作室,大到千人万人集团公司.