Redis缓存失效机制

Redis缓存失效的故事要从EXPIRE这个命令提及,EXPIRE容许用户为某个key指定超时时间,当超过这个时间以后key对应的值会被清除,这篇文章主要在分析Redis源码的基础上站在Redis设计者的角度去思考Redis缓存失效的相关问题。redis

 

Redis缓存失效机制

Redis缓存失效机制是为应对缓存应用的一种很常见的场景而设计的,讲个场景:数据库

咱们为了减轻后端数据库的压力,很开心的借助Redis服务把变化频率不是很高的数据从DB load出来放入了缓存,所以以后的一段时间内咱们均可以直接从缓存上拿数据,然而咱们又但愿一段时间以后,咱们再从新的从DB load出当前的数据放入缓存,这个事情怎么作呢?

问题提出来了,这个问题怎么解决呢?好吧,咱们对于手头的语言工具很熟悉,坚信能够很快的写出这么一段逻辑:咱们记录上次从db load数据的时间,而后每次响应服务的时候都去判断时间是否是过时了,要不要从db从新load了……。固然这种方法也是能够的,然而当咱们查阅Redis command document的时候,发现咱们作了原本不须要作的事情,Redis自己提供这种机制,咱们只要借助EXPIRE命令就能够轻松的搞定这件事情:后端

EXPIRE key 30

上面的命令即为key设置30秒的过时时间,超过这个时间,咱们应该就访问不到这个值了,到此为止咱们大概明白了什么是缓存失效机制以及缓存失效机制的一些应用场景,接下来咱们继续深刻探究这个问题,Redis缓存失效机制是如何实现的呢?缓存

 

延迟失效机制

延迟失效机制即当客户端请求操做某个key的时候,Redis会对客户端请求操做的key进行有效期检查,若是key过时才进行相应的处理,延迟失效机制也叫消极失效机制。咱们看看t_string组件下面对get请求处理的服务端端执行堆栈:网络

getCommand 
     -> getGenericCommand 
            -> lookupKeyReadOrReply 
                   -> lookupKeyRead 
                         -> expireIfNeeded

关键的地方是expireIfNeed,Redis对key的get操做以前会判断key关联的值是否失效,这里先插入一个小插曲,咱们看看Redis中实际存储值的地方是什么样子的:函数

typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP) */
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;
    long long avg_ttl;          /* Average TTL, just for stats */
} redisDb;

上面是Redis中定义的一个结构体,dict是一个Redis实现的一个字典,也就是每一个DB会包括上面的五个字段,咱们这里只关心两个字典,一个是dict,一个是expires:工具

  1. dict是用来存储正常数据的,好比咱们执行了set key "hahaha",这个数据就存储在dict中。
  2. expires使用来存储关联了过时时间的key的,好比咱们在上面的基础之上有执行的expire key 1,这个时候就会在expires中添加一条记录。

回过头来看看expireIfNeeded的流程,大体以下:oop

  1. 从expires中查找key的过时时间,若是不存在说明对应key没有设置过时时间,直接返回。
  2. 若是是slave机器,则直接返回,由于Redis为了保证数据一致性且实现简单,将缓存失效的主动权交给Master机器,slave机器没有权限将key失效。
  3. 若是当前是Master机器,且key过时,则master会作两件重要的事情:1)将删除命令写入AOF文件。2)通知Slave当前key失效,能够删除了。
  4. master从本地的字典中将key对于的值删除。

 

主动失效机制

主动失效机制也叫积极失效机制,即服务端定时的去检查失效的缓存,若是失效则进行相应的操做。this

咱们都知道Redis是单线程的,基于事件驱动的,Redis中有个EventLoop,EventLoop负责对两类事件进行处理:spa

  1. 一类是IO事件,这类事件是从底层的多路复用器分离出来的。
  2. 一类是定时事件,这类事件主要用来事件对某个任务的定时执行。

看起来Redis的EventLoop和Netty以及JavaScript的EventLoop功能设计的大概相似,一方面对网络I/O事件处理,一方面还能够作一些小任务。

为何讲到Redis的单线程模型,由于Redis的主动失效机制逻辑是被当作一个定时任务来由主线程执行的,相关代码以下:

if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        redisPanic("Can't create the serverCron time event.");
        exit(1);
    }

serverCron就是这个定时任务的函数指针,adCreateTimeEvent将serverCron任务注册到EventLoop上面,并设置初始的执行时间是1毫秒以后。接下来,咱们想知道的东西都在serverCron里面了。serverCron作的事情有点多,咱们只关心和本篇内容相关的部分,也就是缓存失效是怎么实现的,我认为看代码作什么事情,调用堆栈仍是比较直观的:

aeProcessEvents
    ->processTimeEvents
        ->serverCron 
             -> databasesCron 
                   -> activeExpireCycle 
                           -> activeExpireCycleTryExpire

EventLoop经过对定时任务的处理,触发对serverCron逻辑的执行,最终之执行key过时处理的逻辑,值得一提的是,activeExpireCycle逻辑只能由master来作。

 

遗留问题

Redis对缓存失效的处理机制大概分为两种,一种是客户端访问key的时候消极的处理,一种是主线程按期的积极地去执行缓存失效清理逻辑,上面文章对于一些细节尚未展开介绍,可是对于Redis缓存失效实现机制这个话题,本文留下几个问题:

  1. Redis缓存失效逻辑为何只有master才能操做?
  2. 上面提到若是客户端访问的是slave,slave并不会清理失效缓存,那么此次客户端岂不是获取了失效的缓存?
  3. 上面介绍的两种缓存失效机制各有什么优缺点?Redis设计者为何这么设计?
  4. 服务端对客户端的请求处理是单线程的,单线程又要去处理失效的缓存,是否是会影响Redis自己的服务能力?

 

参考文献

《Redis源码》

相关文章
相关标签/搜索