redis缓存机制

redis提供了三种缓存机制,LFU,LRU,TTL

1.LFU,least frequently used,即最小使用频率淘汰,每个对象使用共计24bit空间用来实施这个算法,24bit分成2个部分,前16bit用来记录上次减少时间(减少的是后面8bit计数器),后面8个bit是一个对数计数器,用来记录这个对象的访问次数。需要注意的是,这个字段不是一直增加的,也需要减少,否则会出现一个情况,一个对象很久之前被频繁的使用,但是最近没有被使用,若这个值不减少的话,那么这个对象会一直处在一个不会被淘汰的位置。前面16bit的作用就是,记录一个“减量时间”,这是一个降低精度的Unix时间,将16bit的时间转换成分钟,不关心回绕问题。若这个时间很大,那么8bit计数器的值减半,否则只是简单的每次递减1。

2.LRU,last recently used,即最近最少使用淘汰,一般做法是,将hash表的value做成一个指针,指向一个双链表节点,节点中保存实际的value,双链表按照上次访问时间降序排列,当访问到一个对象之后,更新访问时间,并将这个节点移动到表头,若节点不存在就直接插入到表头。当内存门限达到的时候,从链表尾开始删除若干entry。redis为了减少内存使用,不使用双链表或其他结构管理对象,采用随机算法,每次从hash表中随机选择一些key,一般是5个,将这些key存入一个全局的池,池大小一般是16,池中entry按照上次访问时间降序排列,每次从池中选择尾部的entry,就是最差的对象,将这个对象淘汰。

server.h中声明的宏

#define LRU_BITS 24
#define LRU_CLOCK_MAX ((1 << LRU_BITS) - 1) /* Max value of obj->lru */
#define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */

其中,LRU_BITS是管理LRU相关内容需要占用的内存大小,会嵌入对象结构中,LRU_CLOCK_MAX是24bit的掩码,LRU_CLOCK_RESOLUTION是时钟精度,基于ms,这里1000的意思是ms / 1000,得到的就是秒

3.TTL,即生存时间,按照生存时间设置一个对象的生命周期,一个对象生命周期结束之后将其销毁

evict.c中声明的结构

#define EVPOOL_SIZE 16
#define EVPOOL_CACHED_SDS_SIZE 255
struct evictionPoolEntry
{
    unsigned long long idle;    /* Object idle time (inverse frequency for LFU) */
    sds key;                    /* Key name. */
    sds cached;                 /* Cached SDS object for key name. */
    int dbid;                   /* Key DB number. */
};

static struct evictionPoolEntry *EvictionPoolLRU;

其中,EVPOOL_SIZE定义了池的大小,即池中最多容纳16个entry。EVPOOL_CACHED_SDS_SIZE定义了缓存大小,是字节数。entry结构中,idle是一个LRU和LFU共用的字段,当使用LRU的时候,这个字段的含义是对象的空闲时间,当使用LFU的时候,这个时间是反向频率,这是为了编码的时候保持一致,因为在LRU中,空闲时间越大,这个对象越应该被淘汰,LFU是让频率越小的越容易被淘汰,为了在LFU中复用这个字段,使用255 - 频率得到一个反向频率,从而也是值越大越倾向于被淘汰。key就是hash表中key的名字,cached是缓存,目的是不希望多次申请和释放内存,当key存在的时候,cached中的值就是缓存的key的值,key释放的时候,cached不必清零。

evict.c中提供的用于LRU算法的api:

getLRUClock:返回当前时间,是秒数,取后24位

LRU_CLOCK:获取当前时间,根据精度判断是否可以使用getLRUClock,例如,若更新对象频率太快,导致在同一个时间精度下发生2次或者以上,那么这两次对象操作时间无法突破了精度粒度,是一样的,就无法决定这两个对象应该淘汰哪个

estimateObjectIdleTime:获取一个对象的idle时间,方法是用当前时间减上次访问时间,显然,得到的idle时间越大,此对象越倾向于被淘汰

evictionPoolAlloc:创建一个用于LRU算法的池结构,全局有一个池就够了

evictionPoolPopulate:从字典中随机选取若干key加入池,池按照访问时间降序排序,也就是按照idle时间升序排序,池中的entry使用缓存,若key不是很长的话,保存在缓存中,这样就不用多次申请和释放内存。只要池中有空的entry,那么key一定能加进来,否则把key和池中保存的所有key比较一下,把idle时间最小的从池中移除

evict中提供的用于LFU算法的api:

LFUGetTimeInMinutes:返回当前时间,单位是分钟,取后16bit

LFUTimeElapsed:返回当前时间和某个对象上次访问时间的差值,是分钟,内部会处理回绕

LFULogIncr:对数增加一个LFU计数器的值,内部其实是一个随机,counter越大,越不容易加,从而保证一个对数实现

LFUDecrAndReturn:获取一个对象的LFU计数器的值,当对象递减时间到达,减少LFU计数器但是不更新对象的LFU字段,显式更新对象的访问时间和计数器值当一个对象真正被访问到的时候

freeMemoryGetNotCountedMemory:返回AOF和slave buffer占用的内存,在内存到达门限之后要清理的内存不应该包括这两个部门,用此函数计算这两个部分占用内存的值

freeMemoryIfNeeded:清理内存机制的主函数,每次server命令都调用此函数检查内存情况,持续随机找key删除,直到内存降到门限以下。可以改进增加每次删除key的次数,否则虽然降低到门限以下,很快又会达到

你可能感兴趣的:(开源工程)