现代化的缓存设计方案

缓存是提升性能的通用方法,现在大多数的缓存实现都使用了经典的技术。这篇文章中,我们会发掘 Caffeine 中的现代化的实现方法。Caffeine 是一个开源的 Java 缓存库,它能提供高命中率和出色的并发能力。期望读者们能被这些想法激发,进而将它们应用到任何你喜欢的编程语言中。

驱逐策略

缓存的驱逐策略是为了预测哪些数据在短期内最可能被再次用到,从而提升缓存的命中率。由于简洁的实现、高效的运行时表现,以及在常规的使用场景下有不错的命中率,LRU(Least Recently Used)策略或许是最流行的驱逐策略。但 LRU 通过历史数据来预测未来是局限的,它会认为最后到来的数据是最可能被再次访问的,从而给与它最高的优先级。

现代缓存扩展了对历史数据的使用,结合就近程度(recency)和访问频次(frequency)来更好的预测数据。其中一种保留历史信息的方式是使用 popularity sketch(一种压缩、概率性的数据结构)来从一大堆访问事件中定位频繁的访问者。可以参考 CountMin Sketch 算法,它由计数矩阵和多个哈希方法实现。发生一次读取时,矩阵中每行对应的计数器增加计数,估算频率时,取数据对应是所有行中计数的最小值。这个方法让我们从空间、效率、以及适配矩阵的长宽引起的哈希碰撞的错误率上做权衡。

Window TinyLFU(W-TinyLFU)算法将 sketch 作为过滤器,当新来的数据比要驱逐的数据高频时,这个数据才会被缓存接纳。这个许可窗口给予每个数据项积累热度的机会,而不是立即过滤掉。这避免了持续的未命中,特别是在突然流量暴涨的的场景中,一些短暂的重复流量就不会被长期保留。为了刷新历史数据,一个时间衰减进程被周期性或增量的执行,给所有计数器减半。

对于长期保留的数据,W-TinyLFU 使用了分段 LRU(Segmented LRU,缩写 SLRU)策略。起初,一个数据项存储被存储在试用段(probationary segment)中,在后续被访问到时,它会被提升到保护段(protected segment)中(保护段占总容量的 80%)。保护段满后,有的数据会被淘汰回试用段,这也可能级联的触发试用段的淘汰。这套机制确保了访问间隔小的热数据被保存下来,而被重复访问少的冷数据则被回收。

如图中数据库和搜索场景的结果展示,通过考虑就近程度和频率能大大提升 LRU 的表现。一些高级的策略,像 ARCLIRS 和 W-TinyLFU 都提供了接近最理想的命中率。想看更多的场景测试,请查看相应的论文,也可以在使用 simulator 来测试自己的场景。

过期策略

过期的实现里,往往每个数据项拥有不同的过期时间。因为容量的限制,过期后数据需要被懒淘汰,否则这些已过期的脏数据会污染到整个缓存。一般缓存中会启用专有的清扫线程周期性的遍历清理缓存。这个策略相比在每次读写操作时按照过期时间排序的优先队列来清理过期缓存要好,因为后台线程隐藏了的过期数据清除的时间开销。

鉴于大多数场景里不同数据项使用的都是固定的过期时长,Caffien 采用了统一过期时间的方式。这个限制让用 O(1)的有序队列组织数据成为可能。针对数据的写后过期,维护了一个写入顺序队列,针对读后过期,维护了一个读取顺序队列。缓存能复用驱逐策略下的队列以及下面将要介绍的并发机制,让过期的数据项在缓存的维护阶段被抛弃掉。

并发

由于在大多数的缓存策略中,数据的读取都会伴随对缓存状态的写操作,并发的缓存读取被视为一个难点问题。传统的解决方式是用同步锁。这可以通过将缓存的数据划成多个分区来进行锁拆分优化。不幸的是热点数据所持有的锁会比其他数据更常的被占有,在这种场景下锁拆分的性能提升也就没那么好了。当单个锁的竞争成为瓶颈后,接下来的经典的优化方式是只更新单个数据的元数据信息,以及使用随机采样基于 FIFO 的驱逐策略来减少数据操作。这些策略会带来高性能的读和低性能的写,同时在选择驱逐对象时也比较困难。

另一种可行方案来自于数据库理论,通过提交日志的方式来扩展写的性能。写入操作先记入日志中,随后异步的批量执行,而不是立即写入到数据结构中。这种思想可以应用到缓存中,执行哈希表的操作,将操作记录到缓冲区,然后在合适的时机执行缓冲区中的内容。这个策略依然需要同步锁或者 tryLock,不同的是把对锁的竞争转移到对缓冲区的追加写上。

在 Caffeine 中,有一组缓冲区被用来记录读写。一次访问首先会被因线程而异的哈希到 stripped ring buffer 上,当检测到竞争时,缓冲区会自动扩容。一个 ring buffer 容量满载后,会触发异步的执行操作,而后续的对该 ring buffer 的写入会被丢弃,直到这个 ring buffer 可被使用。虽然因为 ring buffer 容量满而无法被记录该访问,但缓存值依然会返回给调用方。这种策略信息的丢失不会带来大的影响,因为 W-TinyLFU 能识别出我们希望保存的热点数据。通过使用因线程而异的哈希算法替代在数据项的键上做哈希,缓存避免了瞬时的热点 key 的竞争问题。

写数据时,采用更传统的并发队列,每次变更会引起一次立即的执行。虽然数据的损失是不可接受的,但我们仍然有很多方法可以来优化写缓冲区。所有类型的缓冲区都被多个的线程写入,但却通过单个线程来执行。这种多生产者/单个消费者的模式允许了更简单、高效的算法来实现。

缓冲区和细粒度的写带来了单个数据项的操作乱序的竞态条件。插入、读取、更新、删除都可能被各种顺序的重放,如果这个策略控制的不合适,则可能引起悬垂索引。解决方案是通过状态机来定义单个数据项的生命周期。

基准测试中,缓冲区随着哈希表的增长而增长,它的的使用相对更节省资源。读的性能随着 CPU 的核数线性增长,是哈希表吞吐量的 33%。写入有 10%的性能损耗,这是因为更新哈希表时的竞争是最主要的开销。

 

结论

还有许多实用的话题没有被覆盖到。包括最小化内存的技巧,当复杂度上升时保证质量的测试技术以及确定优化是否值得的性能分析方法。这些都是缓存的实践者需要关注的点,因为一旦这些被忽视,就很难重拾掌控缓存带来的复杂度的信心。

Caffeine 的设计实现来自于大量的洞见和许多贡献者的共同努力。它这些年的演化离不开一些人的帮助:Charles Fry, Adam Zell, Gil Einziger, Roy Friedman, Kevin Bourrillion, Bob Lee, Doug Lea, Josh Bloch, Bob Lane, Nitsan Wakart, Thomas Müeller, Dominic Tootell, Louis Wasserman, and Vladimir Blagojevic. Thanks to Nitsan Wakart, Adam Zell, Roy Friedman, and Will Chu for their feedback on drafts of this article。

转载自 并发编程网 - ifeve.com

时间: 2024-11-02 13:37:12

现代化的缓存设计方案的相关文章

ShopEx开发团队

author : ShopEx开发团队 since : 2009-12-03 $Rev: 195 $ 前言和导读 安装和使用 2.1. 安装shopex 2.1.1. 如何选择主机 2.2. 初始化配置系统 2.3. 系统调优 2.3.1. url rewrite 2.3.2. 搜索引擎优化(SEO) 2.3.3. 服务器配置 2.4. 操作技巧 2.4.1. 使用快捷键 2.4.2. 使用条码扫描器 2.5. 业务成长之后... 2.6. 升级方法 扩展shopex 3.1. 插件体系 3.1

缓存是新的内存(转)

  英文原文:Cache is the new RAM 这是一次在 defrag 2014的演讲.  这是经过长时间地多次技术变革后的(多个)技术优势之一.你看到了实际上突破.如果你只是看到了其中的一部分,很难正确推断.你要么短期有进展,要么落后很远.令人惊讶的不是事物变化的速度,而是一点一滴长期工程实践的突破.这是史端乔交换机,一个自动连接电话线路设备,在1891年发明的. 1951年,正是转向数字交换技术之时,一个典型的集中式交换中心基本上还是维多利亚时期的技术的放大版.每个转接过来的电话都

分布式缓存HttpRuntime.cache应用到单点登陆中_优化登陆

以前的设计方案,是我们在数据库中放一个表,用作存储验证登陆成功的用户,并且生成用户TOKEN(令牌) 分布式缓存+集群的解决方案图:

基于Hadoop开发网络云盘系统架构设计方案第一稿

引言 云计算技术的发展,各种网络云盘技术如雨后春笋,层出不穷,百度.新浪.网易都推出了自己的云盘系统,本文基于开源框架Hadoop设计实现了一套自己的网络云盘系统,方案为初步设计方案,不断完善中. 一.总体架构 二.方案说明 2.1 系统切分 从用户角度,整个系统划分为ECDisk客户端.ECDisk运营管理平台.HDFS分布式文件存储集群和账户数据应用平台四部分. 2.2 功能需求 文件管理:浏览.文件上传.文件下载.文件删除 用户管理:用户注册.用户登录.用户注销.账户充值.账户查询 三.技

CYQ.Data V5 分布式缓存Redis应用开发及实现算法原理介绍

前言: 自从CYQ.Data框架出了数据库读写分离.分布式缓存MemCache.自动缓存等大功能之后,就进入了频繁的细节打磨优化阶段. 从以下的更新列表就可以看出来了,3个月更新了100条次功能: + View Code 其实更多的时间,是放在ASP.NET Aries 业务开发框架上,上里下外全部重构了一遍. 前几天,决定把Redis集成进来,一鼓作气,解决了. 下面分享一下经历: 最初的想法: 一开始我是拒绝的,不愿动态调用第三方的客户端(关联依赖的dll太多). 最近打算支持Redis,有

ORM数据层框架的设计热点:更新指定的列的几种设计方案

ORM框架的定义:对象-关系映射(Object/Relation Mapping,简称ORM) 常见的是:数据库结构=>映射Object(实体属性)=>基于实体类的操作. 还有一种:数据库结构=>映射Object(内存表结构)=>基于内存表的操作. 当然,如果你有创意,你还能创造出更多的映射载体来实现ORM. 避免思维定式:  由于思维定式,很多开发者,只有见到基于实体类映射,才会认为是一种ORM框架,于是很少人去思考其它映射载体来实现ORM. 这个思维定式,和早期在ASP.NET

Web缓存基础:术语、HTTP报头和缓存策略

Web缓存基础:术语.HTTP报头和缓存策略 简介 对于您的站点的访问者来说,智能化的内容缓存是提高用户体验最有效的方式之一.缓存,或者对之前的请求的临时存储,是HTTP协议实现中最核心的内容分发策略之一.分发路径中的组件均可以缓存内容来加速后续的请求,这受控于对该内容所声明的缓存策略. 在这份指南中,我们将讨论一些Web内容缓存的基本概念.这主要包括如何选择缓存策略以保证互联网范围内的缓存能够正确的处理您的内容.我们将谈一谈缓存带来的好处.副作用以及不同的策略能带来的性能和灵活性的最大结合.

酒店综合布线设计方案

当今社会,信息已成为一种非常关键性的资源,它必须精确.迅速地传输于各种通讯设备.数据处理设备和显示设备之间.由于这一原因,公司.企业.政府部门都会要求以最快速度对这些通讯及信息系统进行调整和改进,并根据需要配置成各种不同的结构. 而在国内,既便是在一些新设计的建筑物内,往往仍沿用过去的那些布线技术,致使各种系统的布线无法兼容,难以适应新技术的发展,且管线拥挤不堪,而配线上的投资往往是重复的.这种情况,还会随着公司.企业.政府部门的扩大.设备的更新.人员的变动.办公环境的变更,而变得越来越糟.任何

jQuery缓存技术的简单探究

如果与java语言世界对比的话,jQuery Data模块和jQuery Queue模块类似java的集合框架.jQuery Data模块的功能简单来说就是"在某个元素"上对缓存数据"做增删改查的CRUD操作"."某个元素"可能是原生的dom对象,也可能是js内核对象,jQuery Data模块区别对待它们.按照jQuery通用编程风格,一般对数据的写和读是设计到同一个方法的,更新也属于写操作类型,这样就只有两个方法了--data()和remov