分布式系统缓存设计浅析

    前几天听了部门内朋春大牛讲分布式缓存的一个技术分享,还是非常有收获。

  PPT如下:

 

    这个分享的副标题是“简单的事情从来不简单”,这句话讲得非常在理。缓存看似简单,但要做“好”一个缓存系统也是很有讲究的。   

    写点自己的心得收获吧:

    1. 分布式缓存面临比较大的三个问题:

       (1) 数据一致性。

               在分布式系统这点显得尤为重要,主要原因有三点:

               缓存系统与底层数据的一致性。这点在底层系统是“可读可写”时,写得尤为重要

               有继承关系的缓存之间的一致性。为了尽量提高缓存命中率,缓存也是分层:全局缓存,二级缓存。他们是存在继承关系的。全局缓存可以有二级缓存来组成。

               多个缓存副本之间的一致性。为了保证系统的高可用性,缓存系统背后往往会接两套存储系统(如memcache,redis等),以上的ppt也主要是讲这方面的内容。

       (2)缓存雪崩

               当缓存系统重启或者所有缓存在同一时刻失效(比如某些系统为了提高速度,会在系统启动是统一将大部分数据刷到缓存中,此时如果设置缓存时间都是24小时,那24小时过后,那就悲剧)时,应用系统由于扛不住压力而直接挂掉。

       (3)缓存穿透

               查询一个必然不存在的数据,查询一个必然不存在的key,每次都会访问DB,如果有人恶意破坏,那么很可能直接对DB造成影响。

        第一点偏重数据的真实性和实时性,而第二点和第三点更多从性能上考虑。同时缓存并不一定是必需的,特别是当写操作特别频繁时。

        

     2. 缓存数据的淘汰

     原先缓存数据的淘汰往往是用设置缓存时间,比如我设置某个数据的缓存时间是24小时,之后的24内这个缓存是不会失效的。优点当然是简单,缺点也很明显就是不都灵活,没做到好的精细化管理。

    我们能利用的资源就是:1. 给缓存加tag,2. 版本号(必须单调递增,时间戳是最好的选择)3. 提供手动清理缓存的接口。

    相关步骤可以参见以上的PPT内容。

    缓存相关接口:

var me = Cache.create(...);
me.set(key, value, ttl, tags);
me.get(key);
me.tagrm(tag, offset, flush);

    缓存的数据结构如下:

var data = {
  ‘i’:now, /** 数据写入时间戳 */
  ‘e’:now + ttl,/** 预期过期时间 */
  ‘k’:key, /** 原始key */
  ‘v’:value, /** 原始值 */
  ‘t’:tags /** tag列表 */
};

    我刚开始的也没弄明白为什么在data中还要存“原始的key”,后来经朋春提醒,才弄明白:原始的key可能过长或者存在特殊字符时,是不能直接作为某些系统的key,因此往往会对原始key做一次hash来作为缓存的新key。

    

    3. 缓存淘汰的策略

    缓存淘汰的策略有两种:

    (1) 定时去清理过期的缓存。

   (2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。

    两者各有优劣,第一种的缺点是维护大量缓存的key是比较麻烦的,第二种的缺点就是每次用户请求过来都要判断缓存失效,逻辑相对比较复杂,具体用哪种方案,大家可以根据自己的应用场景来权衡。

 

 

 

 

 

 

 

 

 

posted @ 2012-05-26 19:03 lengyuhong 阅读(...) 评论(...) 编辑 收藏