• redis源码笔记 有关LRU cache相关的代码


    redis可以被作为类似memcached的应用级缓存使用,在内存超过限制时,按照配置的策略,淘汰掉相应的kv,使得内存可以继续留有足够的空间保存新的数据。

    redis的conf文件中有对该机制的一份很好的解释:

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

    注意,在redis按照master-slave使用时,其maxmeory应设置的比实际物理内存稍小一些,给slave output buffer留有足够的空间。

    redis支持如下五种缓存淘汰策略:

    219 # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
    220 # is reached? You can select among five behavior:
    221 # 
    222 # volatile-lru -> remove the key with an expire set using an LRU algorithm
    223 # allkeys-lru -> remove any key accordingly to the LRU algorithm
    224 # volatile-random -> remove a random key with an expire set
    225 # allkeys->random -> remove a random key, any key
    226 # volatile-ttl -> remove the key with the nearest expire time (minor TTL)
    227 # noeviction -> don't expire at all, just return an error on write operations

    注释已经解释的很清楚了,不再赘述。

    其缓存管理功能,由redis.c文件中的freeMemoryIfNeeded函数实现。如果maxmemory被设置,则在每次进行命令执行之前,该函数均被调用,用以判断是否有足够内存可用,释放内存或返回错误。如果没有找到足够多的内存,程序主逻辑将会阻止设置了REDIS_COM_DENYOOM flag的命令执行,对其返回command not allowed when used memory > 'maxmemory'的错误消息。

    具体代码如下:

    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. */ 计算占用内存大小时,并不计算slave output buffer和aof buffer,因此maxmemory应该比实际内存小,为这两个buffer留足空间。
        mem_used = zmalloc_used_memory();
        if (slaves) {
            listIter li;
            listNode *ln;
    
            listRewind(server.slaves,&li);
            while((ln = listNext(&li))) {
                redisClient *slave = listNodeValue(ln);
                unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave);
                if (obuf_bytes > mem_used)
                    mem_used = 0;
                else
                    mem_used -= obuf_bytes;
            }
        }
        if (server.appendonly) {
            mem_used -= sdslen(server.aofbuf);
            mem_used -= sdslen(server.bgrewritebuf);
        }
    
        /* Check if we are over the memory limit. */
        if (mem_used <= server.maxmemory) return REDIS_OK;
    
        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. */
        mem_tofree = mem_used - server.maxmemory;
        mem_freed = 0;
        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;
                struct dictEntry *de;
                redisDb *db = server.db+j;
                dict *dict;
    
                if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
                    server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM)
                {
                    dict = server.db[j].dict;
                } else {
                    dict = server.db[j].expires;
                }
                if (dictSize(dict) == 0) continue;
    
                /* 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 = dictGetEntryKey(de);
                }//如果是random delete,则从dict中随机选一个key
    
                /* 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++) {
                        sds thiskey;
                        long thisval;
                        robj *o;
    
                        de = dictGetRandomKey(dict);
                        thiskey = dictGetEntryKey(de);
                        /* When policy is volatile-lru we need an additonal 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); //因为dict->expires维护的数据结构里并没有记录该key的最后访问时间
                        o = dictGetEntryVal(de);
                        thisval = estimateObjectIdleTime(o);
    
                        /* Higher idle time is better candidate for deletion */
                        if (bestkey == NULL || thisval > bestval) {
                            bestkey = thiskey;
                            bestval = thisval;
                        }
                    }//为了减少运算量,redis的lru算法和expire淘汰算法一样,都是非最优解,lru算法是在相应的dict中,选择maxmemory_samples(默认设置是3)份key,挑选其中lru的,进行淘汰
                }
    
                /* volatile-ttl */
                else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {
                    for (k = 0; k < server.maxmemory_samples; k++) {
                        sds thiskey;
                        long thisval;
    
                        de = dictGetRandomKey(dict);
                        thiskey = dictGetEntryKey(de);
                        thisval = (long) dictGetEntryVal(de);
    
                        /* Expire sooner (minor expire unix timestamp) is better
                         * candidate for deletion */
                        if (bestkey == NULL || thisval < bestval) {
                            bestkey = thiskey;
                            bestval = thisval;
                        }
                    }//注意ttl实现和上边一样,都是挑选出maxmemory_samples份进行挑选
                }
    
                /* Finally remove the selected key. */
                if (bestkey) {
                    long long delta;
    
                    robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
                    propagateExpire(db,keyobj); //将del命令扩散给slaves
                    /* 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++;
                    decrRefCount(keyobj);
                    keys_freed++;
    
                    /* When the memory to free starts to be big enough, we may
                     * start spending so much time here that is impossible to
                     * deliver data to the slaves fast enough, so we force the
                     * transmission here inside the loop. */
                    if (slaves) flushSlavesOutputBuffers();
                }
            }//在所有的db中遍历一遍,然后判断删除的key释放的空间是否足够
            if (!keys_freed) return REDIS_ERR; /* nothing to free... */
        }
        return REDIS_OK;
    }

    注意,此函数是在执行特定命令之前进行调用的,并且在当前占用内存低于限制后即返回OK。因此可能在后续执行命令后,redis占用的内存就超过了maxmemory的限制。因此,maxmemory是redis执行命令所需保证的最大内存占用,而非redis实际的最大内存占用。(在不考虑slave buffer和aof buffer的前提下)

  • 相关阅读:
    K-lord #1
    P1220 关路灯 (区间DP)
    P1136 迎接仪式 (动态规划)
    P1063 能量项链 (区间DP)
    444 D. Ratings and Reality Shows
    P1896 [SCOI2005]互不侵犯King
    P1841 [JSOI2007]重要的城市
    P1134 阶乘问题
    P1414 又是毕业季II
    P1450 [HAOI2008]硬币购物
  • 原文地址:https://www.cnblogs.com/liuhao/p/2519799.html
Copyright © 2020-2023  润新知