Redis的LRU缓存淘汰算法实现(中)

2.3 近似LRU算法的实际执行

Redis之所以实现近似LRU,是为减少内存资源和操作时间上的开销。

2.3.1 何时触发算法执行?

近似LRU主要逻辑在performEvictions。


performEvictions被evictionTimeProc调用,而evictionTimeProc函数又是被processCommand调用。


processCommand,Redis处理每个命令时都会调用:

Redis的LRU缓存淘汰算法实现(中)

然后,isSafeToPerformEvictions还会再次根据如下条件判断是否继续执行performEvictions:
Redis的LRU缓存淘汰算法实现(中)

Redis的LRU缓存淘汰算法实现(中)

一旦performEvictions被调用,且maxmemory-policy被设置为allkeys-lru或volatile-lru,近似LRU就被触发执行了。

2.3.2 近似LRU具体执行过程

执行可分成如下步骤:

2.3.2.1 判断当前内存使用情况

调用getMaxmemoryState评估当前内存使用情况,判断当前Redis Server使用内存容量是否超过maxmemory配置值。

若未超过maxmemory,则返回C_OK,performEvictions也会直接返回。

Redis的LRU缓存淘汰算法实现(中)

getMaxmemoryState评估当前内存使用情况的时候,若发现已用内存超出maxmemory,会计算需释放的内存量。这个释放内存大小=已使用内存量-maxmemory。


但已使用内存量并不包括用于主从复制的复制缓冲区大小,这是getMaxmemoryState通过调用freeMemoryGetNotCountedMemory计算的。


Redis的LRU缓存淘汰算法实现(中)

而若当前Server使用的内存量超出maxmemory上限,则performEvictions会执行while循环淘汰数据释放内存。


为淘汰数据,Redis定义数组EvictionPoolLRU,保存待淘汰的候选KV对,元素类型是evictionPoolEntry结构体,保存了待淘汰KV对的空闲时间idle、对应K等信息:

Redis的LRU缓存淘汰算法实现(中)

Redis的LRU缓存淘汰算法实现(中)

这样,Redis Server在执行initSever进行初始化时,会调用evictionPoolAlloc为EvictionPoolLRU数组分配内存空间,该数组大小由EVPOOL_SIZE决定,默认可保存16个待淘汰的候选KV对。


performEvictions在淘汰数据的循环流程中,就会更新这个待淘汰的候选KV对集合,即EvictionPoolLRU数组。

2.3.2.2 更新待淘汰的候选KV对集合

performEvictions调用evictionPoolPopulate,其会先调用dictGetSomeKeys,从待采样哈希表随机获取一定数量K:


  1. dictGetSomeKeys采样的哈希表,由maxmemory_policy配置项决定:
    1. 若maxmemory_policy=allkeys_lru,则待采样哈希表是Redis Server的全局哈希表,即在所有KV对中采样
    2. 否则,待采样哈希表就是保存着设置了TTL的K的哈希表。


Redis的LRU缓存淘汰算法实现(中)

  1. dictGetSomeKeys采样的K的数量由配置项maxmemory-samples决定,默认5:

Redis的LRU缓存淘汰算法实现(中)

于是,dictGetSomeKeys返回采样的KV对集合。evictionPoolPopulate根据实际采样到的KV对数量count,执行循环:调用estimateObjectIdleTime计算在采样集合中的每一个KV对的空闲时间:

Redis的LRU缓存淘汰算法实现(中)

接着,evictionPoolPopulate遍历待淘汰的候选KV对集合,即EvictionPoolLRU数组,尝试把采样的每个KV对插入EvictionPoolLRU数组,取决于如下条件之一:


  1. 能在数组中找到一个尚未插入KV对的空位
  2. 能在数组中找到一个KV对的空闲时间<采样KV对的空闲时间


有一成立,evictionPoolPopulate就能把采样KV对插入EvictionPoolLRU数组。等所有采样键值对都处理完后,evictionPoolPopulate函数就完成对待淘汰候选键值对集合的更新了。


接下来,performEvictions开始选择最终被淘汰的KV对。


上一篇:Redis的LRU缓存淘汰算法实现(上)


下一篇:Redis的IO多路复用和多线程特性会破坏分布式锁的原子性吗?(下)