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