Redis系列之过期淘汰机制

概述

使用redis时,一般是作为缓存系统,而不是存储系统。缓存系统,即需要设置一个生存时间(TTL,time to live);存储系统,即不设置生存时间,永不过期。除了生存时间,还有一个过期时间的概念,expire time,效果一样,本文不加以区分。带有TTL属性的key在Redis中被称为是不稳定的。设置TTL时间后,又想让缓存永不过期,可使用persist key,persist可以移除一个键的过期时间。过期时间timestamp是一个unix时间戳。

设置过期时间的几类方式:

  1. expire key time:单位为秒;pexpire key time:单位毫秒;
  2. expireat key timestamp或者pexpireat key timestamp为key指定过期时间,单位分别为秒和毫秒;p表示毫秒,不再赘述;
  3. setex(String key, int seconds, String value),只能用于字符串键,还有其他指令如setnx

expire、pexpire、expireat、pexpireat四个命令,前三个底层都是基于pexpireat,

怎么得知某个键的剩余过期时间还有多少?通过TTL命令或PTTL命令

设置过期时间后,Redis如何判断是否过期,怎么删除?

过期策略

有3个删除策略:

  1. 定时删除;
  2. 惰性删除;
  3. 定期删除

定时删除

策略:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除。

优点:内存友好,保证内存被尽快释放。

缺点:

  1. 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,显得避重就轻没有优先级;
  2. 定时器的创建需要用到redis的时间事件,其实现方式为无序链表,查找事件的时间复杂度为O(N),效率低;

惰性删除

策略:key过期时不删除,每次从键空间获取键时,检查键是否过期,若过期,则删除,返回null。

优点:对CPU时间友好。

缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,则可能发生内存泄露(无用的垃圾占用大量的内存)

定期删除

策略:每隔一段时间,程序对数据库进行一次检查,删除过期键。扫描什么库,删除多少键,有算法决定。定期删除主要是为了避免定时删除和惰性删除的问题,是前两者的一个组合折中。

优点:

  1. 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用,解决定时删除的CPU时间占用问题;
  2. 定期删除过期key,解决惰性删除的内存浪费问题。

难点:

  1. 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除),每次执行时间太长,或者执行频率太高对cpu都是一种压力。
  2. 每次进行定期删除操作执行之后,需要记录遍历循环到哪个标志位,以便下一次定期时间来时,从上次位置开始进行循环遍历。

说明:
memcached只用惰性删除,而redis使用惰性删除与定期删除,二者的区别之一;

对于懒汉式删除而言,并不是只有获取key的时候才会检查key是否过期,在某些设置key的方法上也会检查(eg.setnx key2 value2:该方法类似于memcached的add方法,如果设置的key2已经存在,那么该方法返回false,什么都不做;如果设置的key2不存在,那么该方法设置缓存key2-value2。假设调用此方法的时候,发现redis中已经存在了key2,但是该key2已经过期了,如果此时不执行删除操作的话,setnx方法将会直接返回false,也就是说此时并没有重新设置key2-value2成功,所以对于一定要在setnx执行之前,对key2进行过期检查)。

Redis

Redis采用的过期策略:惰性删除+定期删除,在合理使用CPU时间和避免内存空间浪费之间取得平衡。

惰性删除

db.c/expireIfNeeded函数实现,所有读写数据库的Redis命令在执行之前都会调用此函数对键进行过期检查,过期则删除。相当于一个过滤器的角色。

定期删除

redis.c/activeExpireCycle函数实现,每当服务器周期性操作redis.c/serverCron函数时被调用执行,在规定的时间内,分多次遍历服务器的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,过期则删除。
伪代码:

流程总结:

  1. 函数每次执行时,都从一定数量的数据库中取出一定数量的随机键进行检查,删除其中的过期键;
  2. 全局变量current_db记录当前activeExpireCycle函数检查进度,在下一次被调用时,接着上一次的进度进行处理;
  3. 随着函数activeExpireCycle的不断执行,服务器中的所有数据库都会被检查一遍,此时可以将current_db重置为0,开始新一轮的检查。

内存不足

当前已用内存超过maxmemory限定时,触发主动清理策略,Redis有6中策略:

  • volatile-lru:只对设置过期时间的key进行LRU(默认值)
  • allkeys-lru:删除lru算法的key
  • volatile-random:随机删除即将过期key
  • allkeys-random:随机删除
  • volatile-ttl:删除即将过期的
  • noeviction:永不过期,返回错误当mem_used内存已经超过maxmemory的设定,对于所有的读写请求,都会触发

当mem_used内存已经超过maxmemory的设定,对于所有的读写请求,都会触发redis.c/freeMemoryIfNeeded(void)函数清理超出的内存,清理过程是阻塞的,直到清理出足够的内存空间。所以如果在达到maxmemory并且调用方还在不断写入的情况下,可能会反复触发主动清理策略,导致请求会有一定的延迟。

清理时会根据用户配置的maxmemory-policy来做适当的清理(一般是LRU或TTL),这里的LRU或TTL策略并不是针对redis的所有key,而是以配置文件中的maxmemory-samples个key作为样本池进行抽样清理。

maxmemory-samples在redis-3.0.0中的默认配置为5,如果增加,会提高LRU或TTL的精准度,redis作者测试的结果是当这个配置为10时已经非常接近全量LRU的精准度,并且增加maxmemory-samples会导致在主动清理时消耗更多的CPU时间,建议:
尽量不要触发maxmemory,最好在mem_used内存占用达到maxmemory的一定比例后,需要考虑调大hz以加快淘汰,或者进行集群扩容。
如果能够控制住内存,则可以不用修改maxmemory-samples配置;如果Redis本身就作为LRU cache服务(这种服务一般长时间处于maxmemory状态,由Redis自动做LRU淘汰),可以适当调大maxmemory-samples

原理

expires字典,过期字典。待补充

参考

Redis设计与实现

posted @ 2020-05-19 13:05  johnny233  阅读(29)  评论(0编辑  收藏  举报  来源