redis源码之内存回收策略(五)
简介
redis可以配置最大使用内存,当使用内存超过配置的最大值的时候,需要主动进行内存回收。内存回收的两种基本策略:
- lru: 淘汰最长时间没被访问的对象,这种策略需要维护对象被访问时间的信息。
- lfu: 淘汰被访问次数最少的对象,这种策略需要维护对象被访问的频率信息。
redis内存淘汰基本策略
淘汰策略的配置
当redis内存达到使用限制时,可以配置如何选择key进行删除
# volatile表示在expire中进行驱逐, 所有设置了过期时间的key会关联到expire中
# allkeys表示在所有key中进行驱逐
# 如果要准确的淘汰就需要遍历整个字典或者expires字典,代价太高,所以redis是通过随机采样
# 然后排序,然后根据lru或者lfu策略选择删除最合适的key
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key having an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
# The default is:
# 默认策略是不删除
# maxmemory-policy noeviction
# 默认是如果开启了lru或者lfu, 每采样5个key, 就删除一个key
# maxmemory-samples 5
lfu和lru信息的维护
key对应的val对象中存放了lfu和lru相关的信息,每次访问key都会更新val->lru
// get命令调用
lookupKeyReadWithFlags() db.c
// set命令调用
lookupKeyWriteWithFlags() db.c
// db.c 查找key的函数
robj *lookupKey(redisDb *db, robj *key, int flags) {
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) {
// lru和lfu的相关信息是存放在val->lru中的
robj *val = dictGetVal(de);
/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
// LOOKUP_NOTOUCH表示不更新val的访问时间和次数
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
// 如果设置了MAXMEMORY_FLAG_LFU, 也就是配置了达到最大内存的时
// 候使用lfu删除key
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
// 更新val的被访问次数信息
updateLFU(val);
} else {
// 更新val的被访问时间为当前时间
val->lru = LRU_CLOCK();
}
}
return val;
} else {
return NULL;
}
}
// db.c 更新lfu信息
void updateLFU(robj *val) {
unsigned long counter = LFUDecrAndReturn(val);
// 这里使用了随机数来概率性的触发增加counter,一种巧妙的设计
counter = LFULogIncr(counter);
// lru的高24位存放访问时间,低8位存放访问次数,最大为255
val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}
// server.h
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;
内存回收触发机制
# 函数名 文件名
processCommand server.c
# 检查是否达到了最大使用内存限制
performEvictions evict.c
# 随机采样key 并且删除最合适的key
evictionPoolPopulate evict.c
# 异步回收,当开启了lazyfree-lazy-eviction且回收时间成本高于阈值
dbAsyncDelete lazyfree.c
dbSyncDelete db.c
- 每次执行命令都要检查是否超过了最大使用内存,如果超过了就进行内存回收
- 使用池(EvictionPoolLRU,一个数组)记录每次采样的key, 只有比池中的key更加适合删除的key才会被放入池,每次都从池中删除最合适的一个key;所以redis并不是简单的每次采样n个然后删除n个中的一个,而是累积了前面每次采样的最合适的key进行删除。(个人人为这里是不是可以使用一个小顶堆结构维护?如果key的idle时间比小顶堆的顶部大,就替换堆顶进行插入?采样n次后删除整个堆里面的key)
总结
- redis可以使用LRU或者LFU策略进行内存回收
- redis通过采样,并且将采样的结构放入一个数组内进行排序,然后删除最合适的key,每次采样maxmemory-samples个key, 删除一个key

浙公网安备 33010602011771号