Redis缓存Key过期原理和内存淘汰策略

Key过期原理

Redis通过字典(Redis字典通过哈希表来实现)来存储键的过期时间,字典的键是指向RedisDb的指针(使用指针可以避免浪费),字典的值是一个毫秒的时间戳,所以当前时间大于字典值的时候这个键就过期了,就可以对这个键进行删除(删除一个键不仅要删除redisDb数据库中的键,也要删除过期字典中的键)。

通过pexpireat命令来设置过期时间的命令,其他命令最终也会转换成pexpireat。给一个键设置过期时间,就是将这个键的指针及给定的到期时间戳加到过期字典中。

在这里插入图片描述

Redis删除过期Key的策略

  • 被动式删除

    当客户端尝试访问某个key的时候,发现当前key已经过期了,就直接删除这个key。

    如果一些过期的key没有被访问,就会导致这些key一直存在并且占用内存。

  • 主动式删除

    Redis会定期扫描过期的key并且进行删除,并且Redis底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU影响。其原理如下:

    • 从过期的key中随机选取20个key,删除其中已经过期的key;
    • 在这20个key中如果超过了25%的过期key,则重新执行当前步骤。实际上这是利用了一种概率算法。

被动式删除对CPU更加友好,主动式删除对内存更加友好。所以Redis采用的是:被动式删除+主动式删除

Redis内存淘汰策略

背景

仅仅通过上面的删除过期Key策略在业务实际场景中可能会漏掉了很多过期的key,如果没有被及时删除掉可能会引发内存溢出。

Redis内存淘汰策略

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  4. allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  6. no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
  7. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  8. allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key

相关源码

源码路径:redis/src/server.h

typedef struct redisDb {
    dict *dict;                 //数据库键空间,保存着数据库中所有键值对
    dict *expires;              // 过期字典,保存着键的过期时间
    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 */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
    clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;

源码路径:redis/src/dict.h

typedef struct dict {
    dictType *type; //dictType中定义了很多dict中常用的方法,具体见dictType
    void *privdata; //一些私有数据
    dictht ht[2]; //两个dictht数组,正常只有一个在用,只有当rehash时才会使用两个
    long rehashidx; /* dic是否正在进行rehash,当不为-1时,说明正在rehash */
    unsigned long iterators; /* 遍历器的个数*/
} dict;
typedef struct dictht {
    dictEntry **table; //dictEntry数组保存真正的键值对
    unsigned long size; //table数组的长度
    unsigned long sizemask; //table数组的长度-1,为了方便计算键值对保存在table中的位置而定义的变量
    unsigned long used; //dictht中已经存在的键值对数量
} dictht;
typedef struct dictEntry {
    void *key;  //键值对中的key
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v; //键值对中的value,对不同的value选取不同的值
    struct dictEntry *next; //指向下一个dictEntry的指针
} dictEntry;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值