这个手册其实老早就搞到了,只是最近实在太懒了一直没动- - ,希望能坚持把主要的内容翻译完。翻译的过程中会加入一些自己的看法,如果有不准确或错误的地方欢迎各种提意见指出~。
简单来说就是将数据从持久化存储中加载到cache中。这个模式可以提升系统性能,并且保证cache和底层存储中数据的一致性。PS:下文会用“存储空间”来表示data store,在实际的工程中可以代表数据库、文件等持久化到硬盘的存储容器。
Context and Problem
传统的应用程序中经常使用cache来优化那种需要频繁、重复地访问存储中数据的情景。但一般情况下很难使cache中的数据与底层存储中的数据达到完全一致性。程序中必须实现一种策略,不仅能够使得cache中的数据尽可能与存储器中的数据同步,同时当cache中的数据变得陈旧(不同步)时能够感知并进行正确的处理。
Solution
很多商业的缓存系统提供了穿透读(read-through)和穿透写(write-through)/write-behind(http://www.infoq.com/cn/articles/write-behind-caching/
这篇文章把write-behind讲得挺透彻的)。在这些系统中,应用程序都通过缓存来检索/读取数据,如果缓存中不存在所需要的数据,那么缓存会以一种对应用程序透明的方式来从存储器中取出数据并添加到缓存中。此外,任何对数据的改动都会在缓存中进行,而缓存会自动地将这些改动写到存储器中。
而对于那些不提供这些功能的cache来说,应用程序必须自己来维护缓存中的数据:
数据读取:
应用程序可以通过实现read-through策略来达到相同的效果,这种策略在需要将数据加载到cache中时会非常有效,Figure 1 总结了一下这个过程的步骤:
1.检查所要读取的数据是否在缓存中。
2.如果数据不在缓存中,就直接从存储空间中读取数据,将数据返回给应用程序。
3.将这份数据也同时保存到缓存中。
数据写入:
如果应用程序需要写入/更新数据,那么可以通过以下步骤来实现write-through:
1.先修改存储空间中的数据。
2.如果缓存中有对应数据,那么将该数据置为无效(抛弃)。如果之后用到这部分数据,那么缓存会再次从存储空间中加载数据。
Issues and Considerations
当使用这一模式时需要注意以下几点:
1.缓存数据的生命周期。很多缓存实现了一种数据失效规则,即当数据在一个指定的时间内没有被使用时,就会被从缓存中移除。在实际使用中这种规则必须要根据应用程序访问数据的特性来好好设计,以使得缓存在系统中发挥最大作用。比如说不能将这个失效时间设置得过短,否则会导致应用程序频繁地访问存储空间,从存储空间加载数据并将数据插入到缓存。相应的也不应该将失效时间设置得过长,否则会导致缓存中保留许多非热点数据,造成存储空间的浪费。缓存中存放不变的并且会被频繁使用的数据时会发挥其最大作用。
2.缓存数据的清除(抛弃)。绝大多数缓存容量相比存储空间来说要小得多,所以缓存中只能存放相对来说极为有限的数据量,于是在必要的时候,缓存会对数据做清除动作。绝大多数缓存实现了least-recently-used策略来选择那些需要被清除的数据(即最近一段时间用的最少的数据会被清理出去),但这通常来说也是可以定制化的,通过配置缓存的全局数据超时时间等配置项,以及指定单条数据的超时时间,可以使缓存达到最高效率。此外,使用一个全局的缓存数据清除策略有的时候也并不是很合适,比如说有些数据重载代价非常高,那么相对一些使用频率相对来说较高,但重载开销较低的数据来说,这种数据更有必要保存在缓存里。
3.数据一致性。实现这一模式并不保证缓存中的数据和存储空间中的数据能实现一致性。存储空间中的数据很有可能在任意时间被其他进程所修改,此时在缓存中的这份数据不会做同步,除非之后能有操作使得这部分数据从存储空间中重新加载。如果系统频繁地在不同存储空间之间同步数据,那么这一问题会格外严重。
4.本地缓存。对于一个应用程序来说,缓存可以是在本地或者直接基于内存来实现的(原文中着重说明这点是因为在集群中存在利用分布式缓存(即非本地性缓存)或系统采用多层次分层存储(内存-SSD-硬盘),这里所指本地缓存应该指的仅仅是应用程序内部的缓存,比如说在同一个JVM内,并不包括不同进程但在同一物理节点的情况),这种情景下对于频繁读的业务来说,Cache-Aside模式能达到最好的效果。然而,本地内存是私有的,不同的应用程序实例(进程)都保有各自私有的缓存,这很容易造成数据的不一致,所以这种情况下需要将数据的失效时间设短,从而提高从存储空间中同步数据的频率。对于这种情况可以参考一下分布式缓存的实现原理。
PS:最近的确有和同事讨论过相关的话题,缓存这东西随着storm、spark的兴起作用不仅越来越重要,扮演的角色也从一个辅助者转正上位成了主角,是整体系统设计中一个很重要的点。相对于memcache、redis这种成熟的分布式缓存而言,伴随着spark而出现的tachyon似乎又开拓了一个新的路子。我个人是对tachyon中基于ramdisk以及lineage两个主要特色比较欣赏的。然而lineage这种策略有其优点,但也有很多场景并不是很适用。相反,我倒是很看好ramdisk在分布式缓存中扮演的角色。总而言之,期待社区在缓存这块的进步与发展~