Redis原理篇——内存回收

Redis是基于内存存储的,性能强。Redis的性能瓶颈也在于内存,但是单节点内存不宜过大,会影响持久化或主从同步性能。

通过配置文件来设置Redis的最大内存:
在这里插入图片描述

一、过期策略

Redis是键值类型的数据库,所有的key和value保存在 Dict 结构中,在Redis数据库结构体中,有两个Dict:一个用来记录key-value,另一个记录key-TTL

typedef struct redisDb {
    
    
    dict *dict;                 /* 存放所有key和value */
    dict *expires;              /* 存放key和对应的TTL存活时间 只包含设置了TTL的key */
    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;                     /* Database ID 使用的是几号库 0~15 */
    long long avg_ttl;          /* Average TTL, 平均TTL时间 */
    unsigned long expires_cursor; /* expire检查时在dict中抽取的索引位置 */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

在这里插入图片描述
因此只需要查Redis中的key对应的TTL,就可以判断这个key是否过期

删除策略

TTL到期就立刻删除吗?

可以设置定时器,当一个key到期了就立刻删除,但是对于大量key来说,设置大量定时器对CPU的占用率高,影响Redis主线程的性能,因此Redis不会立刻删除到期的key

1.惰性删除:

在访问一个key的时候检查该key的存活时间,如果已经过期了才执行删除。

robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
    
    
     // 检查key是否过期
    expireIfNeeded(db,key);
    return lookupKey(db,key,flags);
}

robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
    
    
    robj *val;
      // 检查key是否过期
    if (expireIfNeeded(db,key) == 1) {
    
    
    /*
        ......... 略
    */ 
    
    }
   /*
        ......... 略
    */
}

int expireIfNeeded(redisDb *db, robj *key) {
    
    
      // 判断是否过期,如果没过期就返回0
    if (!keyIsExpired(db,key)) return 0;

    /*
        ......... 略
    */
  

    /* 删除过期的key */
    deleteExpiredKeyAndPropagate(db,key);
    return 1;
}

惰性删除存在的问题:

惰性删除是在访问一个key的时候检查是否过期,如果一个key过期了但是客户端长时间没有操作,那么这个key就一直不能被删除,如果这样的key存在大量的,那么就会一直占据着内存,影响性能。

2.周期删除

周期性的抽样一部分过期的key,然后执行删除。
执行周期有两种:

  • SLOW模式: Redis会设置一个定时任务serverCron(),按照server.hz的频率来执行过期key清理
  1. 执行频率受server.hz影响,默认为10,即每秒执行10次,每个执行周期100ms
  2. 执行清理耗时不超过一次执行周期的25%
  3. 逐个遍历db,抽取20个key判断是否过期
  4. 如果没达到时间上限(25ms)并且过期key比例大于10%,再进行一次抽样,否则结束
  • FAST模式: Redis的每个事件循环前会调用beforeSleep()函数,执行过期key清理
  1. 执行频率受beforeSleep()调用频率影响,但两次FAST模式间隔不低于2ms
  2. 执行清理耗时不超过1ms
  3. 依次遍历db,抽取20个key判断是否过期
  4. 如果没达到时间上限(1ms)并且过期key比例大于10%,再进行一次抽样,否则结束

二、内存淘汰策略

内存淘汰: Redis内存使用达到设置的阈值时,Redis主动挑选部分key删除以释放内存。

Redis支持8种不同的策略:

  • noeviction: 不淘汰任何key,内存满时不允许写入新数据(默认策略)
  • volatile-ttl:对设置TTL的key,比较剩余的TTL值,先淘汰数值小的
  • allkeys-random: 对全体key,随机进行淘汰。
  • volatile-random:对设置了TTL的key,随机淘汰。
  • allkeys-lru:对全体key,基于LRU算法进行淘汰
  • volatile-lru:对设置了TTL的key,基于LRU算法进行淘汰
  • allkeys-lfu:对全体key,基于LFU算法进行淘汰
  • volatile-lfu:对设置了TTL的key,基于LFU算法进行淘汰
typedef struct redisObject {
    
    
    unsigned type:4; // 对象类型,分别为五种基本数据类型  占4个bit位
    unsigned encoding:4; // 编码方式 11 中 占4个bit位
    unsigned lru:LRU_BITS; /*LRU以秒为单位记录最近一次访问时间,长度为24bit  LFU:高16位以分钟为单位记录最近一次访问时间,低8位记录逻辑访问次数*/
    int refcount;  // 引用计数器 0表示对象无人引用 可以被回收
    void *ptr;  // 指针,指向存放实际数据的空间
} robj;

LFU的访问次数之所以叫逻辑访问次数,是因为并不是每次key被访问都计数。而是通过运算:

  1. 生成0~1之间的随机数R
  2. 计算1/(旧次数*lfu_log_factor+1),记录为P,lfu_log_factor默认为10
  3. 如果R<P,计数器+1,最大不超过255
  4. 访问次数会随时间衰减,距离上一次访问时间每隔lfu_decay_time分钟(默认1),计数器-1

猜你喜欢

转载自blog.csdn.net/qq_52595134/article/details/128169801