理解Redis的LRU算法源码实现

300 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情

大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈


前言

LRU(Least Recently Used) 算法其实就是将最近被访问距离当前最久的数据删除。

Redis使用的内存将超过maxmemory时,Redis会根据maxmemory_policy淘汰策略来决定将哪些数据淘汰掉。使用到LRU算法的淘汰策略如下所示。

  1. volatile-lru。对设置了过期时间的数据生效,使用LRU算法删除数据;
  2. allkeys-lru。对所有数据生效,使用LRU算法删除数据。

本篇文章将对Redis 6.0.16版本中的LRU算法实现原理进行分析。

正文

一. 算法细节

server.h文件中有redisObject的定义,如下所示。

#define LRU_BITS 24

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS;
    int refcount;
    void *ptr;
} robj;

当某个key被使用时,都会把当前秒时间的后24bit赋值给key对应的redisObjectlru字段(后续简称为keylru)。

所以计算一个key的空闲时间的方法就是将当前秒时间的后24bit减去keylru,但是这样会引入一个问题,会出现当前秒时间的后24bit小于keylru,所以计算一个key的空闲时间时需要先判断当前秒时间的后24bitkeylru的大小关系,如果当前秒时间的后24bit小于keylru,那么计算key的空闲时间的公式如下所示。

当前秒时间的后24bit + (24位最大值 - key的lru)

二. 源码分析

计算redisObject.lru的逻辑在evict.c文件的estimateObjectIdleTime() 方法中,如下所示。

unsigned long long estimateObjectIdleTime(robj *o) {
    // 获取当前秒时间的后24位
    unsigned long long lruclock = LRU_CLOCK();
    if (lruclock >= o->lru) {
	// 当前秒时间的后24位大于等于redisObject.lru时直接相减得到空闲时间
        return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
    } else {
	// 否则需要当前秒时间的后24位 + (24bit最大值 - redisObject.lru)
        return (lruclock + (LRU_CLOCK_MAX - o->lru)) *
                    LRU_CLOCK_RESOLUTION;
    }
}

上面的estimateObjectIdleTime() 方法中会调用LRU_CLOCK() 方法获取当前秒时间的后24位,其实现如下。

#define LRU_CLOCK_RESOLUTION 1000

unsigned int LRU_CLOCK(void) {
    unsigned int lruclock;
    // 一旦定时器每次执行的间隔时间小于等于LRU_CLOCK_RESOLUTION
    // 则当前秒时间的后24位使用server.lruclock
    if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
        lruclock = server.lruclock;
    } else {
	// 否则真正获取一次当前系统的秒时间并取后24位
        lruclock = getLRUClock();
    }
    return lruclock;
}

上面的LRU_CLOCK_RESOLUTIONLRU算法中的时钟分辨率,定义在server.h文件中,默认为1000,单位是ms

最后给出上面出现的获取系统的秒时间的后24位的getLRUClock() 方法的实现,如下所示。

unsigned int getLRUClock(void) {
    // 获取当前系统秒时间的后24位
    return (mstime()/LRU_CLOCK_RESOLUTION) & LRU_CLOCK_MAX;
}

三. 流程图

整体的LRU算法的一个流程图如下所示。

Redis-LRU算法流程图


大家好,我是半夏之沫 😁😁 一名金融科技领域的JAVA系统研发😊😊
我希望将自己工作和学习中的经验以最朴实最严谨的方式分享给大家,共同进步👉💓👈
👉👉👉👉👉👉👉👉💓写作不易,期待大家的关注和点赞💓👈👈👈👈👈👈👈👈
👉👉👉👉👉👉👉👉💓关注微信公众号【技术探界】 💓👈👈👈👈👈👈👈👈

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情