当我们使用
EXPIRE、EXPIREAT、PEXPIRE 和 PEXPIREAT 命令设置一个主键的失效时间时,
Redis 首先到 dict 这个字典表中查找要设置的主键是否存在,如果存在就将这个主键和失效时间添加到 expires 这个字典表。
2).消极方法
1.expireIfNeeded函数
触发:
这个函数在任何访问数据的函数中都会被调用,
Redis 在实现 GET、MGET、HGET、LRANGE 等所有涉及到读取数据的命令时都会调用它
意义:
在读取数据之前先检查一下该key有没有失效,如果失效了就删除它。
2.propagateExpire函数(在上边一个函数中调用) 主要函数
触发:
执行上一个函数时,它在其里边
意义:
用来在正式删除失效主键之前广播这个主键已经失效的信息
操作:
(1).一个是发送到 AOF文件,将删除失效主键的这一操作以 DEL Key 的标准命令格式记录下来;
(2).发送到当前 Redis 服务器的所有 Slave,同样将删除失效主键的这一操作以 DEL Key 的标准命令格式告知这些 Slave 删除各自的失效主键。
以上我们了解了 Redis 是如何以一种消极的方式删除失效主键的,但是仅仅通过这种方式显然是不够的,因为如果某些失效的主键迟迟等不到再次访问的话,Redis 就永远不会知道这些主键已经失效,也就永远也不会删除它们了,这无疑会导致内存空间的浪费。所以有了下边的方法。
3).积极方法:(该方法利用 Redis 的时间事件来实现,即每隔一段时间就中断一下完成一些指定操作)
1.serverCron函数:
触发:
它在 Redis 服务器启动时创建
作用:
这个回调函数不仅要进行失效主键的检查与删除,还要进行统计信息的更新、客户端连接超时的控制、BGSAVE 和 AOF 的触发等等
2.activeExpireCycle函数:
触发:
执行上一个函数时,它在其里边每秒的执行次数由宏定义 【REDIS_DEFAULT_HZ】 来指定,默认每秒钟执行10次。
操作:
a).遍历处理 Redis 服务器中每个数据库的 expires 字典表中,从中尝试着随机抽样【REDIS_EXPIRELOOKUPS_PER_CRON】(默认 值为10)个设置了失效时间的主键,
b).检查它们是否已经失效并删除掉失效的主键,
c).如果失效的主键个数占本次抽样个数的比例超过25%,Redis 会认为当前数据库中的失效主键依然很多,所以它会继续进行下一轮的随机抽样和删除,
d).直到刚才的比例低于25%才停止对当前数据库的处理,转向下一个数据库。
其他:
activeExpireCycle 函数避免失效主键删除占用过多的CPU资源,所以其不会试图一次性处理Redis中的所有数据库,而是最多只处理 REDIS_DBCRON_DBS_PER_CALL(默认值为16)个库,有处理时间上的限制
首先,Memcached 在删除失效主键时也是采用的消极方法,即 Memcached 内部也不会监视主键是否失效,而是在通过 Get 访问主键时才会检查其是否已经失效。
其次,Memcached 与 Redis 在主键失效机制上的最大不同是,Memcached 不会像 Redis
那样真正地去删除失效的主键,而只是简单地将失效主键占用的空间回收。这样当有新的数据写入到系统中时,Memcached 会优先使用那些失效主键的空间。如果失效主键的空间用光了,Memcached 还可以通过 LRU 机制来回收那些长期得不到访问的空间,
因此 Memcached 并不需要像 Redis 中那样的周期性删除操作,这也是由 Memcached 使用的内存管理机制决定的。同时,这里需要指出的是
Redis 在出现 OOM 时同样可以通过配置 maxmemory-policy 这个参数来决定是否采用 LRU 机制来回收内存空间
四。总结:
redis每秒执行10次过期检查,每次中,随机从某个库的expire表中抽取10个key,检测他是否失效,若失效则删除。当失效比例超过1/4,本次重新执行随机抽取10key,不计入10次中的1次,直到这一秒的10次都执行完。
问:
那么有人问了,万一,万一!失效的key做足够的多,1秒的这10次都没执行完又到下一秒了,咋整?
答:
redis有检测机制的,不会让它把CPU拖死的:
a.每次处理数据库个数的限制、
b.activeExpireCycle 函数在一秒钟内执行次数的限制、
c.分配给 activeExpireCycle函数CPU时间的限制、
d.继续删除主键的失效主键数百分比的限制,
Redis 已经大大降低了主键失效机制对系统整体性能的影响,
所以由此也可得,设置失效时间的原则:尽可能避免在同一时间点的大批量key失效,它是需要处理时间的。
消极失效主要函数.propagateExpire函数:
void propagateExpire(redisDb *db, robj *key)
{
robj *argv[2];
//shared.del是在Redis服务器启动之初就已经初始化好的一个常用Redis对象,即DEL命令
argv[0] = shared.del;
argv[1] = key;
incrRefCount(argv[0]);
incrRefCount(argv[1]);
//检查Redis服务器是否开启了AOF,如果开启了就为失效主键记录一条DEL日志
if (server.aof_state != REDIS_AOF_OFF)
feedAppendOnlyFile(server.delCommand,db->id,argv,2);
//检查Redis服务器是否拥有Slave,如果是就向所有Slave发送DEL失效主键的命令,这就是
//上面expireIfNeeded函数中发现自己是Slave时无需主动删除失效主键的原因了,因为它
//只需听从Master发送过来的命令就OK了
if (listLength(server.slaves))
replicationFeedSlaves(server.slaves,db->id,argv,2);
decrRefCount(argv[0]);
decrRefCount(argv[1]);
}