redisソースノート-LRU cacheに関するコード

19196 ワード

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 keys196 # 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 is199 # set to 'noeviction', Redis will start to reply with errors to commands200 # that would use more memory, like SET, LPUSH, and so on, and will continue201 # to reply to read-only commands like GET.202 #203 # This option is usually useful when using Redis as an LRU cache, or to set204 # 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 subtracted208 # from the used memory count, so that network problems / resyncs will209 # not trigger a loop where keys are evicted, and in turn the output210 # buffer of slaves is full with DELs of keys evicted triggering the deletion211 # 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 lower214 # limit for maxmemory so that there is some free RAM on the system for slave215 # 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 maxmemory220 # is reached? You can select among five behavior:221 # 222 # volatile-lru -> remove the key with an expire set using an LRU algorithm223 # allkeys-lru -> remove any key accordingly to the LRU algorithm224 # volatile-random -> remove a random key with an expire set225 # allkeys->random -> remove a random key, any key226 # 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
注釈が明確に説明されており、後述しない5つのキャッシュ淘汰ポリシーをサポートしています.そのキャッシュ管理機能は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を考慮しない前提で)