曹工说Redis源码(8)–面试时,redis 内存淘汰总被问,但是总答不好

文章导航

Redis源码系列的初衷,是辅助我们更好地明白Redis,更懂Redis,而怎么才气懂,光看是不够的,建议随着下面的这一篇,把环境搭建起来,后续可以自己阅读源码,或者随着我这边一起阅读。由于我用c也是好几年以前了,些许错误在所难免,希望读者能不惜指出。

曹工说Redis源码(1)– redis debug环境搭建,使用clion,到达和调试java一样的效果

曹工说Redis源码(2)– redis server 启动历程剖析及简朴c语言基础知识弥补

曹工说Redis源码(3)– redis server 启动历程完整剖析(中)

曹工说Redis源码(4)– 通过redis server源码来明白 listen 函数中的 backlog 参数

曹工说Redis源码(5)– redis server 启动历程剖析,以及EventLoop每次处置事宜前的前置事情剖析(下)

曹工说Redis源码(6)– redis server 主循环大要流程剖析

曹工说Redis源码(7)– redis server 的周期执行任务,到底要做些啥

什么是内存镌汰

内存镌汰,和平时我们设置redis key的过时时间,不是一回事;内存镌汰是说,假设我们限制redis只能使用8g内存,现在已经使用了这么多了(包罗设置了过时时间的key和没设过时时间的key),那,后续的set操作,还怎么办呢?

是不是只能报错了?

那不行啊,不科学吧,由于有的key,可能已经良久没人用了,可能以后也不会再用到了,那我们是不是可以把这类key给干掉呢?

干掉key的历程,就是内存镌汰。

内存镌汰什么时刻启用

当我们在设置文件里设置了如下属性时:

# maxmemory <bytes>

默认,该属性是被注释掉的。

实在,这个设置项的注释,相当有价值,我们来看看:

# Don't use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys
# according to the eviction policy selected (see maxmemory-policy).
#
# If Redis can't remove keys according to the policy, or if the policy is
# set to 'noeviction', Redis will start to reply with errors to commands
# that would use more memory, like SET, LPUSH, and so on, and will continue
# to reply to read-only commands like GET.
#
# This option is usually useful when using Redis as an LRU cache, or to set
# a hard memory limit for an instance (using the 'noeviction' policy).
#
# WARNING: If you have slaves attached to an instance with maxmemory on,
# the size of the output buffers needed to feed the slaves are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
# buffer of slaves is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
# In short... if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>

渣翻译如下:

不能使用跨越指定数目bytes的内存。当该内存限制被到达时,redis会凭据过时计谋(eviction policy,通过参数 maxmemory-policy来指定)来驱逐key。

若是redis凭据指定的计谋,或者计谋被设置为“noeviction”,redis会最先针对如下这种下令,回复错误。什么下令呢?会使用更多内存的那类下令,好比set、lpush;只读下令照样不受影响,可以正常响应。

该选项通常在redis使用LRU缓存时有用,或者在使用noeviction计谋时,设置一个历程级别的内存limit。

内存镌汰计谋

所谓计谋,意思是,当我们要删除部门key的时刻,删哪些,不删哪些?是不是需要一个计谋?好比是随机删,就像灭霸一样?照样根据lru时间来删,lru的计谋意思就是,最近最少使用的key,将被优先删除。

总之,我们需要定一个规则。

redis默认支持以下计谋:

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select among five behaviors:
# 
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key accordingly to the LRU algorithm
# volatile-random -> remove a random key with 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 expire at all, just return an error on write operations
# 
# Note: with any of the above policies, Redis will return an error on write
#       operations, when there are not suitable keys for eviction.
#
#       At the date of writing this commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction
maxmemory-policy allkeys-lru
针对设置了过时时间的,使用lru算法
# volatile-lru -> remove the key with an expire set using an LRU algorithm

针对所有key,使用lru算法
# allkeys-lru -> remove any key accordingly to the LRU algorithm

针对设置了过时时间的,随机删
# volatile-random -> remove a random key with an expire set

针对所有key,随机删
# allkeys-random -> remove a random key, any key

针对设置了过时时间的,马上要过时的,删掉
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)

不外时,不能写了,就报错
# noeviction -> don't expire at all, just return an error on write operations

一样平常呢,我们会设置为:

allkeys-lru,即,针对所有key,举行lru。

源码实现

设置读取

在如下结构体中,界说了如下字段:

struct redisServer {
	...
	unsigned long long maxmemory;   /* Max number of memory bytes to use */
    int maxmemory_policy;           /* Policy for key eviction */
    int maxmemory_samples;          /* Pricision of random sampling */
    ...
}

当我们在设置文件中,进入如下设置时,该结构体中几个字段的值如下:

maxmemory 3mb
maxmemory-policy allkeys-lru
# maxmemory-samples 5  这个取了默认值

曹工说Redis源码(8)--面试时,redis 内存淘汰总被问,但是总答不好

maxmemory_policy为3,是由于枚举值为3:

#define REDIS_MAXMEMORY_VOLATILE_LRU 0
#define REDIS_MAXMEMORY_VOLATILE_TTL 1
#define REDIS_MAXMEMORY_VOLATILE_RANDOM 2
#define REDIS_MAXMEMORY_ALLKEYS_LRU 3
#define REDIS_MAXMEMORY_ALLKEYS_RANDOM 4
#define REDIS_MAXMEMORY_NO_EVICTION 5
#define REDIS_DEFAULT_MAXMEMORY_POLICY REDIS_MAXMEMORY_NO_EVICTION

处置下令时,判断是否举行内存镌汰

在处置下令的时刻,会挪用中的

redis.c  processCommand
    
int processCommand(redisClient *c) {
    /* The QUIT command is handled separately. Normal command procs will
     * go through checking for replication and QUIT will cause trouble
     * when FORCE_REPLICATION is enabled and would be implemented in
     * a regular command proc. */
    // 稀奇处置 quit 下令
    void *commandName = c->argv[0]->ptr;
    redisLog(REDIS_NOTICE, "The server is now processing %s", commandName);

    if (!strcasecmp(c->argv[0]->ptr, "quit")) {
        addReply(c, shared.ok);
        c->flags |= REDIS_CLOSE_AFTER_REPLY;
        return REDIS_ERR;
    }

    /* Now lookup the command and check ASAP about trivial error conditions
     * such as wrong arity, bad command name and so forth. */
    // 1 查找下令,并举行下令合法性检查,以及下令参数个数检查
    c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
    if (!c->cmd) {
        // 没找到指定的下令
        flagTransaction(c);
        addReplyErrorFormat(c, "unknown command '%s'",
                            (char *) c->argv[0]->ptr);
        return REDIS_OK;
    }

    /* Check if the user is authenticated */
    //2 检查认证信息
    if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand) {
        flagTransaction(c);
        addReply(c, shared.noautherr);
        return REDIS_OK;
    }

    /* If cluster is enabled perform the cluster redirection here.
     *
     * 3 若是开启了集群模式,那么在这里举行转向操作。
     *
     * However we don't perform the redirection if:
     *
     * 不外,若是有以下情形泛起,那么节点不举行转向:
     *
     * 1) The sender of this command is our master.
     *    下令的发送者是本节点的主节点
     *
     * 2) The command has no key arguments. 
     *    下令没有 key 参数
     */
    if (server.cluster_enabled &&
        !(c->flags & REDIS_MASTER) &&
        !(c->cmd->getkeys_proc == NULL && c->cmd->firstkey == 0)) {
        int hashslot;

        // 集群已下线
        if (server.cluster->state != REDIS_CLUSTER_OK) {
            flagTransaction(c);
            addReplySds(c, sdsnew("-CLUSTERDOWN The cluster is down. Use CLUSTER INFO for more information\r\n"));
            return REDIS_OK;

            // 集群运作正常
        } else {
            int error_code;
            clusterNode *n = getNodeByQuery(c, c->cmd, c->argv, c->argc, &hashslot, &error_code);
            // 不能执行多键处置下令
            if (n == NULL) {
                flagTransaction(c);
                if (error_code == REDIS_CLUSTER_REDIR_CROSS_SLOT) {
                    addReplySds(c, sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
                } else if (error_code == REDIS_CLUSTER_REDIR_UNSTABLE) {
                    /* The request spawns mutliple keys in the same slot,
                     * but the slot is not "stable" currently as there is
                     * a migration or import in progress. */
                    addReplySds(c, sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
                } else {
                    redisPanic("getNodeByQuery() unknown error.");
                }
                return REDIS_OK;

                //3.1 下令针对的槽和键不是本节点处置的,举行转向
            } else if (n != server.cluster->myself) {
                flagTransaction(c);
                // -<ASK or MOVED> <slot> <ip>:<port>
                // 例如 -ASK 10086 127.0.0.1:12345
                addReplySds(c, sdscatprintf(sdsempty(),
                                            "-%s %d %s:%d\r\n",
                                            (error_code == REDIS_CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
                                            hashslot, n->ip, n->port));

                return REDIS_OK;
            }

            // 若是执行到这里,说明键 key 所在的槽由本节点处置
            // 或者客户端执行的是无参数下令
        }
    }

    /* Handle the maxmemory directive.
     *
     * First we try to free some memory if possible (if there are volatile
     * keys in the dataset). If there are not the only thing we can do
     * is returning an error. */
    //4 若是设置了最大内存,那么检查内存是否跨越限制,并做响应的操作
    if (server.maxmemory) {
        //4.1 若是内存已跨越限制,那么实验通过删除过时键来释放内存
        int retval = freeMemoryIfNeeded();
        // 若是即将要执行的下令可能占用大量内存(REDIS_CMD_DENYOOM)
        // 而且前面的内存释放失败的话
        // 那么向客户端返回内存错误
        if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {
            flagTransaction(c);
            addReply(c, shared.oomerr);
            return REDIS_OK;
        }
    }    
    ....
  • 1处,查找下令,对应的函数指针(类似于java里的计谋模式,凭据下令,找对应的计谋)
  • 2处,检查,是否密码准确
  • 3处,集群相关操作;
  • 3.1处,不是本节点处置,直接返回ask,指示客户端转向
  • 4处,判断是否设置了maxMemory,这里就是本文重点:设置了maxMemory时,内存镌汰计谋
  • 4.1处,挪用了下方的 freeMemoryIfNeeded

接下来,深入4.1处:


int freeMemoryIfNeeded(void) {
    size_t mem_used, mem_tofree, mem_freed;
    int slaves = listLength(server.slaves);

    /* Remove the size of slaves output buffers and AOF buffer from the
     * count of used memory. */
    // 盘算出 Redis 现在占用的内存总数,但有两个方面的内存不会盘算在内:
    // 1)从服务器的输出缓冲区的内存
    // 2)AOF 缓冲区的内存
    mem_used = zmalloc_used_memory();
    if (slaves) {
		...
    }
    if (server.aof_state != REDIS_AOF_OFF) {
        mem_used -= sdslen(server.aof_buf);
        mem_used -= aofRewriteBufferSize();
    }

    /* Check if we are over the memory limit. */
    //1 若是现在使用的内存大小比设置的 maxmemory 要小,那么无须执行进一步操作
    if (mem_used <= server.maxmemory) return REDIS_OK;

    //2 若是占用内存比 maxmemory 要大,然则 maxmemory 计谋为不镌汰,那么直接返回
    if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)
        return REDIS_ERR; /* We need to free memory, but policy forbids. */

    /* Compute how much memory we need to free. */
    // 3 盘算需要释放若干字节的内存
    mem_tofree = mem_used - server.maxmemory;

    // 初始化已释放内存的字节数为 0
    mem_freed = 0;

    // 凭据 maxmemory 计谋,
    //4 遍历字典,释放内存并纪录被释放内存的字节数
    while (mem_freed < mem_tofree) {
        int j, k, keys_freed = 0;

        // 遍历所有字典
        for (j = 0; j < server.dbnum; j++) {
            long bestval = 0; /* just to prevent warning */
            sds bestkey = NULL;
            dictEntry *de;
            redisDb *db = server.db + j;
            dict *dict;

            if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
                server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM) {
                // 若是计谋是 allkeys-lru 或者 allkeys-random 
                //5 那么镌汰的目的为所有数据库键
                dict = server.db[j].dict;
            } else {
                // 若是计谋是 volatile-lru 、 volatile-random 或者 volatile-ttl 
                //6 那么镌汰的目的为带过时时间的数据库键
                dict = server.db[j].expires;
            }


            /* volatile-random and allkeys-random policy */
            // 若是使用的是随机计谋,那么从目的字典中随机选出键
            if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||
                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM) {
                de = dictGetRandomKey(dict);
                bestkey = dictGetKey(de);
            }
            /* volatile-lru and allkeys-lru policy */
            //7 
            else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
                     server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU) {
                struct evictionPoolEntry *pool = db->eviction_pool;

                while (bestkey == NULL) {
                    // 8 
                    evictionPoolPopulate(dict, db->dict, db->eviction_pool);
                    /* Go backward from best to worst element to evict. */
                    for (k = REDIS_EVICTION_POOL_SIZE - 1; k >= 0; k--) {
                        if (pool[k].key == NULL) continue;
                        // 8.1
                        de = dictFind(dict, pool[k].key);

                        /* 8.2 Remove the entry from the pool. */
                        sdsfree(pool[k].key);
                        /* Shift all elements on its right to left. */
                        memmove(pool + k, pool + k + 1,
                                sizeof(pool[0]) * (REDIS_EVICTION_POOL_SIZE - k - 1));
                        /* Clear the element on the right which is empty
                         * since we shifted one position to the left.  */
                        pool[REDIS_EVICTION_POOL_SIZE - 1].key = NULL;
                        pool[REDIS_EVICTION_POOL_SIZE - 1].idle = 0;

                        /* If the key exists, is our pick. Otherwise it is
                         * a ghost and we need to try the next element. */
                        // 8.3
                        if (de) {
                            bestkey = dictGetKey(de);
                            break;
                        } else {
                            /* Ghost... */
                            continue;
                        }
                    }
                }
            }

                /* volatile-ttl */
                // 计谋为 volatile-ttl ,从一集 sample 键中选出过时时间距离当前时间最接近的键
            else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {
                ...
            }

            /* Finally remove the selected key. */
            // 8.4 删除被选中的键
            if (bestkey) {
                long long delta;

                robj *keyobj = createStringObject(bestkey, sdslen(bestkey));
                propagateExpire(db, keyobj);
                /* We compute the amount of memory freed by dbDelete() alone.
                 * It is possible that actually the memory needed to propagate
                 * the DEL in AOF and replication link is greater than the one
                 * we are freeing removing the key, but we can't account for
                 * that otherwise we would never exit the loop.
                 *
                 * AOF and Output buffer memory will be freed eventually so
                 * we only care about memory used by the key space. */
                // 盘算删除键所释放的内存数目
                delta = (long long) zmalloc_used_memory();
                dbDelete(db, keyobj);
                delta -= (long long) zmalloc_used_memory();
                mem_freed += delta;

                // 对镌汰键的计数器增一
                server.stat_evictedkeys++;

                notifyKeyspaceEvent(REDIS_NOTIFY_EVICTED, "evicted",
                                    keyobj, db->id);
                decrRefCount(keyobj);
                keys_freed++;
				...
            }
        }

        if (!keys_freed) return REDIS_ERR; /* nothing to free... */
    }

    return REDIS_OK;
}
  • 1处,若是现在使用的内存大小比设置的 maxmemory 要小,那么无须执行进一步操作

  • 2处,若是占用内存比 maxmemory 要大,然则 maxmemory 计谋为不镌汰,那么直接返回

  • 3处,盘算需要释放若干字节的内存

  • 4处,遍历字典,释放内存并纪录被释放内存的字节数

  • 5处,若是计谋是 allkeys-lru 或者 allkeys-random 那么镌汰的目的为所有数据库键

  • 6处,若是计谋是 volatile-lru 、 volatile-random 或者 volatile-ttl ,那么镌汰的目的为带过时时间的数据库键

  • 7处,若是使用的是 LRU 计谋, 那么从 sample 键中选出 IDLE 时间最长的谁人键

  • 8处,挪用evictionPoolPopulate,该函数在下面解说,该函数的功效是,传入一个链表,即这里的db->eviction_pool,然后在函数内部,随机找出n个key,放入传入的链表中,并根据空闲时间排序,空闲最久的,放到最后。

    当该函数,返回后,db->eviction_pool这个链内外就存放了我们要镌汰的key。

    老大吩咐的可重入分布式锁,终于完美的实现了!!!

  • 8.1处,找到这个key,这个key,在后边会被删除

  • 8.2处,下面这一段,从db->eviction_pool将这个已经处置了的key删掉

  • 8.3处,若是这个key,是存在的,则跳出循环,在后面8.4处,会被删除

  • 8.4处,删除这个key

选择哪些key作为被镌汰的key

前面我们看到,在7处,若是为lru计谋,则会进入8处的函数:

evictionPoolPopulate。

该函数的名称为:填充(populate)驱逐(eviction)工具池(pool)。驱逐的意思,就是现在到达了maxmemory,没办法,只能最先删除掉一部门元素,来腾空间了,否则新的put类型的下令,基本没办法执行。

该方式的也许思绪是,使用lru的时刻,随机找n个key,类似于抽样,然后放到一个链表,凭据空闲时间排序。

详细看看该方式的实现:

void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {

其中,传入的第三个参数,是要被填充的工具,在c语言中,习惯传入一个入参,然后在函数内部填充或者修改入参工具的属性。

该属性,就是前面说的谁人链表,用来存放网络的随机的元素,该链表中节点的结构如下:

struct evictionPoolEntry {
    unsigned long long idle;    /* Object idle time. */
    sds key;                    /* Key name. */
};

该结构共2个字段,一个存储key,一个存储空闲时间。

该链表中,共maxmemory-samples个元素,会根据idle时间是非排序,idle时间长的在链表尾部,(假设头在左,尾在右)。

void evictionPoolPopulate(dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
    int j, k, count;
    dictEntry *_samples[EVICTION_SAMPLES_ARRAY_SIZE];
    dictEntry **samples;

    /* Try to use a static buffer: this function is a big hit...
     * Note: it was actually measured that this helps. */
    if (server.maxmemory_samples <= EVICTION_SAMPLES_ARRAY_SIZE) {
        samples = _samples;
    } else {
        samples = zmalloc(sizeof(samples[0]) * server.maxmemory_samples);
    }

    /* 1 Use bulk get by default. */
    count = dictGetRandomKeys(sampledict, samples, server.maxmemory_samples);

	// 2
    for (j = 0; j < count; j++) {
        unsigned long long idle;
        sds key;
        robj *o;
        dictEntry *de;

        de = samples[j];
        key = dictGetKey(de);
        /* If the dictionary we are sampling from is not the main
         * dictionary (but the expires one) we need to lookup the key
         * again in the key dictionary to obtain the value object. */
        if (sampledict != keydict) de = dictFind(keydict, key);
        // 3
        o = dictGetVal(de);
        // 4
        idle = estimateObjectIdleTime(o);

        /* 5  Insert the element inside the pool.
         * First, find the first empty bucket or the first populated
         * bucket that has an idle time smaller than our idle time. */
        k = 0;
        while (k < REDIS_EVICTION_POOL_SIZE &&
               pool[k].key &&
               pool[k].idle < idle)
            k++;
        
		...
            
        // 6
        pool[k].key = sdsdup(key);
        pool[k].idle = idle;
    }
    if (samples != _samples) zfree(samples);
}
  • 1处,获取 server.maxmemory_samples个key,这里是随机获取的,(dictGetRandomKeys),这个值,默认值为5,放到samples中

  • 2处,遍历返回来的samples

  • 3处,挪用如下宏,获取val

    he的类型为dictEntry:

    /*
     * 哈希表节点
     */
    typedef struct dictEntry {
        
        // 键
        void *key;
    
        // 值
        union {
            // 1
            void *val;
            uint64_t u64;
            int64_t s64;
        } v;
    
        // 指向下个哈希表节点,形成链表
        struct dictEntry *next;
    
    } dictEntry;
    

    以是,这里去

    robj *o; 
    
    o = dictGetVal(de);
    

    现实就是获取其v属性中的val,(1处):

    #define dictGetVal(he) ((he)->v.val)
    
  • 4处,准备盘算该val的空闲时间

    我们上面3处,看到,获取的o的类型为robj。我们现在看看怎么盘算工具的空闲时长:

    /* Given an object returns the min number of milliseconds the object was never
     * requested, using an approximated LRU algorithm. */
    unsigned long long estimateObjectIdleTime(robj *o) {
        //4.1 获取系统的当前时间
        unsigned long long lruclock = LRU_CLOCK();
        // 4.2
        if (lruclock >= o->lru) {
            // 4.3
            return (lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION;
        } else {
            return (lruclock + (REDIS_LRU_CLOCK_MAX - o->lru)) *
                        REDIS_LRU_CLOCK_RESOLUTION;
        }
    }
    

    这里,4.1处,获取系统的当前时间;

    4.2处,若是系统时间,大于工具的lru时间

    4.3处,则用系统时间减去工具的lru时间,再乘以单元,换算为毫秒,最终返回的单元,为毫秒(可以看注释。)

    #define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
    
  • 5处,这里拿当前元素,和pool中已经放进去的元素,从第0个最先对照,若是当前元素的idle时长,大于pool中指针0指向的元素,则和pool中索引1的元素对照;直到条件不满足为止。

    这句话意思就是,类似于冒泡,把当前元素一直往后冒,直到idle时长小于被对照的元素为止。

  • 6处,把当前元素放进pool中。

经由上面的处置后,链表中存放了所有的抽样元素,且ide时间最长的,在最右边。

工具另有字段存储空闲时间?

前面4处,说到,用系统的当前时间,减去工具的lru时间。

人人看看工具的结构体

typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    //1 工具最后一次被接见的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用计数
    int refcount;

    // 指向现实值的指针
    void *ptr;

} robj;

上面1处,lru属性,就是用来存储这个。

建立工具时,直接使用当前系统时间建立

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;

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

1处即是。

robj *createEmbeddedStringObject(char *ptr, size_t len) {
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);
    struct sdshdr *sh = (void*)(o+1);

    o->type = REDIS_STRING;
    o->encoding = REDIS_ENCODING_EMBSTR;
    o->ptr = sh+1;
    o->refcount = 1;
    // 1
    o->lru = LRU_CLOCK();

    sh->len = len;
    sh->free = 0;
    if (ptr) {
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

1处即是。

每次查找该key时,刷新时间

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. */
        // 更新时间信息(只在不存在子历程时执行,防止损坏 copy-on-write 机制)
        if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
            // 1
            val->lru = LRU_CLOCK();

        // 返回值
        return val;
    } else {

        // 节点不存在

        return NULL;
    }
}

1处即是,包罗get、set等种种操作,都市刷新该时间。

仔细看下面的客栈,set的,get同理:

曹工说Redis源码(8)--面试时,redis 内存淘汰总被问,但是总答不好

总结

人人有没有更清晰一些呢?

总的来说,就是,设置了max-memory后,到达该内存限制后,会在处置下令时,检查是否要举行内存镌汰;若是要镌汰,则凭据maxmemory-policy的计谋来。

随机选择maxmemory-sample个元素,根据空闲时间排序,拉链表;挨个挨个消灭。

原创文章,作者:7h28新闻网,如若转载,请注明出处:https://www.7h28.com/archives/15879.html