惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
博客园 - 【当耐特】
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
MyScale Blog
MyScale Blog
WordPress大学
WordPress大学
The GitHub Blog
The GitHub Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Hugging Face - Blog
Hugging Face - Blog
I
InfoQ
B
Blog RSS Feed
腾讯CDC
云风的 BLOG
云风的 BLOG
N
Netflix TechBlog - Medium
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
雷峰网
雷峰网
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
DataBreaches.Net
The Cloudflare Blog
V
V2EX
S
SegmentFault 最新的问题

博客园 - xinghebuluo

在云主机上部署OpenClaw并集成微信插件:从零到上手完整指南 mongodb 数据块迁移的源码分析 mongodb 数据块的迁移流程介绍 Guava Cache源码浅析 mongodb在双活(主备)机房的部署方案和切换方案设计 mongodb oplog日志详解和格式分析 Redis之ziplist源码分析 Redis之quicklist源码分析 mongodb中出现_id字段重复记录的排查笔记 JAVA中int转string及String.valueOf()的使用 String,StringBuilder和StringBuffer的特点和使用场景 ReentrantLock的原理解析 mongodb的TTL索引介绍(超时索引) JBoss AS7(Application Server 7)的Standalone模式和Domain模式 mongodb 配置均衡器的运行窗口 mongodb添加延时节点 AFNetworking 遇到错误 Code=-1016 "Request failed: unacceptable content-type: text/plain" 使用cocoa捕获dock栏中的“退出”事件,解决qt开发的应用程序退出异常的问题 【Grails 代理Proxy设置】部署Grails遇到Error Resolve error obtaining dependencies:错误,及解决方法
Redis LRU源码分析
xinghebuluo · 2020-03-29 · via 博客园 - xinghebuluo

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”,通常是使用双向链表来实现,会占用大量内存,所以redis使用的是近似LRU的算法,即每次访问时,给该对象记录一下当前的时间戳(单位秒),当需要删除数据时,随机选取5个元素,删除最久未被访问的。

下面先看下redis object的定义(基于redis 2.8.8版本源代码):

/* The actual Redis Object */
#define REDIS_LRU_BITS 24
#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */
#define REDIS_LRU_CLOCK_RESOLUTION 1 /* LRU clock resolution in seconds */
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    int refcount;
    void *ptr;
} robj;

其中“REDIS_LRU_CLOCK_RESOLUTION”是LRU算法的精度,默认1S,数字越低精度越高,精度越高,lru时间溢出时间越短,下面细看。

其中的“lru”记录最后一次访问的时间戳,由于只有24位,无法记录完整的时间,因此只记录了unix time的低24位,24 bits数据要溢出的话需要194天,而缓存的数据更新非常频繁,已经足够了。

下面看下创建redis对象时的代码:

robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = REDIS_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;

    /* Set the LRU to the current lruclock (minutes resolution). */
    o->lru = server.lruclock;
    return o;
}

我们看到是直接把server.lruclock赋值给lru,而server.lruclock会由redis的后台线程每秒更新10次,其他需要的地方直接引用,减少unix time访问,节省资源,代码如下:

void updateLRUClock(void) {
    server.lruclock = (server.unixtime/REDIS_LRU_CLOCK_RESOLUTION) &
                                                REDIS_LRU_CLOCK_MAX; // 把unix time的低24位赋值给server.lruclock
}

当后续再访问redis对象时,更新lru字段,如下:

/*-----------------------------------------------------------------------------
 * C-level DB API
 *----------------------------------------------------------------------------*/

robj *lookupKey(redisDb *db, robj *key) {
    dictEntry *de = dictFind(db->dict,key->ptr);
    if (de) {
        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. */
        if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) // 在执行rdb任务或者aof任务时,不能更新该字段
            val->lru = server.lruclock;
        return val;
    } else {
        return NULL;
    }
}

下面再看下LRU策略触发时,是如何选择对象的:

 /* volatile-lru and allkeys-lru policy */
            else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
            {
                for (k = 0; k < server.maxmemory_samples; k++) { //  server.maxmemory_samples 默认是5
                    sds thiskey;
                    long thisval;
                    robj *o;

                    de = dictGetRandomKey(dict);   // 随机选择一个对象
                    thiskey = dictGetKey(de);
                    /* When policy is volatile-lru we need an additional lookup
                     * to locate the real key, as dict is set to db->expires. */
                    if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
                        de = dictFind(db->dict, thiskey);
                    o = dictGetVal(de);
                    thisval = estimateObjectIdleTime(o);  // 获取对象的lru值

                    /* Higher idle time is better candidate for deletion */
                    if (bestkey == NULL || thisval > bestval) {
                        bestkey = thiskey;
                        bestval = thisval;
                    }
                }
            }
/* Given an object returns the min number of seconds the object was never
 * requested, using an approximated LRU algorithm. */
unsigned long estimateObjectIdleTime(robj *o) {  // 计算对象距离上次访问流逝的时间,单位秒
    if (server.lruclock >= o->lru) {
        return (server.lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
    } else {  // o->lru > server.lruclock,说明距离上次访问已经至少超过一个REDIS_LRU_CLOCK_MAX了(194天)
        return ((REDIS_LRU_CLOCK_MAX - o->lru) + server.lruclock) *
                    REDIS_LRU_CLOCK_RESOLUTION;
    }
}

从上面的代码可以看出,redis的lru近似算法有三个要点:

1. 守护线程,每秒更新10次server.lruclock值(unix time的低24位)。

2. redis对象首次创建和后续访问时,把当前server.lruclock赋值给该对象的lru。

3. redis在处理命令时,会检查内存使用情况,如果超过限制且配置了LRU策略,则:

    a). 随机选择5个元素。

    b). 删除其中最久未被访问的元素。

    c). 已使用内存是否还是超过限制,如果是则跳转到步骤a继续,否则本次删除结束。