redis evict.c内存淘汰机制的源码分析

        众所周知,redis是一个内存数据库,所有的键值对都是存储在内存中。当数据变多之后,由于内存有限就要淘汰一些键值对,使得内存有足够的空间来保存新的键值对。在redis中,通过设置server.maxmemory来限定内存的使用(server.maxmemory为0,不限制内存),到达server.maxmemory就会触发淘汰机制。

redis主要提供6种淘汰策略:

      1)volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

      2)volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不常使用的数据淘汰

      3)volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

      4)volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

      4)allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

      5)allkeys-lfu:从数据集(server.db[i].dict)中挑选最不常使用的数据淘汰

      6)allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

      7)no-enviction(驱逐):禁止驱逐数据

redis在每次执行客户端的命令的时候都会检查使用内存是否超过server.maxmemory,如果超过就进行淘汰数据。

int processCommand(client *c) {
	……//server.maxmemory为0,表示对内存没有限制
	if (server.maxmemory) {
	//判断内存,进行内存淘汰
        int retval = freeMemoryIfNeeded();
        ……
    }
    ……
}
int freeMemoryIfNeeded(void) {
    mem_reported = zmalloc_used_memory();//获取redis内存使用
    if (mem_reported <= server.maxmemory) return C_OK; 
    mem_used = mem_reported;
    if (slaves) {
        listRewind(server.slaves,&li);
        while((ln = listNext(&li))) {
            ……//减去slaves的output缓冲区
        }
    }//aof的缓冲区的内存使用
    if (server.aof_state != AOF_OFF) {
        mem_used -= sdslen(server.aof_buf);
        mem_used -= aofRewriteBufferSize();
    }
    /* Check if we are still over the memory limit. */
    if (mem_used <= server.maxmemory) return C_OK;
    /* Compute how much memory we need to free. */
    mem_tofree = mem_used - server.maxmemory;
    mem_freed = 0;
    if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
        goto cant_free; /* 禁止驱逐数据 */
    //进行数据驱逐
    while (mem_freed < mem_tofree) {
    	……
    	sds bestkey = NULL;
        if (server.maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
            server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
        {   //进行ttl或者lru淘汰机制
            struct evictionPoolEntry *pool = EvictionPoolLRU;
            while(bestkey == NULL) {
                unsigned long total_keys = 0, keys;
                for (i = 0; i < server.dbnum; i++) {
                    db = server.db+i;
                    dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
                            db->dict : db->expires;
                    if ((keys = dictSize(dict)) != 0) {
                        evictionPoolPopulate(i, dict, db->dict, pool);
                        //pool根据机制构建的evictionPool
                    }
                }/*在evictionPool中从后往前选择一个还在存在数据库中的键值进行驱逐*/
                for (k = EVPOOL_SIZE-1; k >= 0; k--) {
                    if (pool[k].key == NULL) continue;
                    bestdbid = pool[k].dbid;
                    if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
                        de = dictFind(server.db[pool[k].dbid].dict,
                            pool[k].key);
                    } else {
                        de = dictFind(server.db[pool[k].dbid].expires,
                            pool[k].key);
                    }
                    ……
                    if (de) {
                        bestkey = dictGetKey(de);
                        break;
                    } else {
                        /* Ghost... Iterate again. */
                    }
                }
            }
        }
        else if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM ||
                 server.maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
        {   /* 从db->dict或者db->expires随机选择一个键值对进行淘汰*/
            for (i = 0; i < server.dbnum; i++) {
                j = (++next_db) % server.dbnum;
                db = server.db+j;
                dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
                        db->dict : db->expires;
                if (dictSize(dict) != 0) {
                    de = dictGetRandomKey(dict);
                    bestkey = dictGetKey(de);
                    bestdbid = j;
                    break;
                }
            }
        }//驱逐选中的键值对
        if (bestkey) {
            db = server.db+bestdbid;
            robj *keyobj = createStringObject(bestkey,sdslen(bestkey));
            propagateExpire(db,keyobj,server.lazyfree_lazy_eviction);
            delta = (long long) zmalloc_used_memory();
            if (server.lazyfree_lazy_eviction)
                dbAsyncDelete(db,keyobj);
            else
                dbSyncDelete(db,keyobj);
            delta -= (long long) zmalloc_used_memory();
            mem_freed += delta;
            server.stat_evictedkeys++;
            decrRefCount(keyobj);
            keys_freed++;
            if (slaves) flushSlavesOutputBuffers();
        }

    }
    return C_OK;
cant_free://进行内存空间的惰性释放
    while(bioPendingJobsOfType(BIO_LAZY_FREE)) {
        if (((mem_reported - zmalloc_used_memory()) + mem_freed) >= mem_tofree)
            break;
        usleep(1000);
    }
    return C_ERR;
}

根据淘汰机制从随机选取的键值对中选取键值对构建evictionPool

1)LRU数据淘汰机制:在数据集中随机选取几个键值对,选择lru最大的一部分键值对构建evictionPool。

2)LFU数据淘汰机制:在数据集中随机选取几个键值对,选择lfu最小的一部分键值对构建evictionPool。

3)TTL数据淘汰机制:从设置过期时间的数据集中随机选取几个键值对,选择TTL最大的一部分键值对构建evictionPool。

void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {
    int j, k, count;
    dictEntry *samples[server.maxmemory_samples];
    //从数据集sampledict随机选取键值对
    count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);
    for (j = 0; j < count; j++) {
        de = samples[j];
        key = dictGetKey(de);
        if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {
            if (sampledict != keydict) de = dictFind(keydict, key);
            o = dictGetVal(de);
        }        
        if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
            idle = estimateObjectIdleTime(o);//LRU机制,计算lru值
        } else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
            idle = 255-LFUDecrAndReturn(o);//LFU机制,计算lfu值
        } else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
            idle = ULLONG_MAX - (long)dictGetVal(de);//TTL机制,计算ttl值
        }
        k = 0;
        //根据idle从小到大将键值对插入到pool(插入排序的机制),但只保留idle最大的EVPOOL_SIZE个
        while (k < EVPOOL_SIZE &&pool[k].key &&pool[k].idle < idle) 
        	k++;
        if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {
            continue;
        } else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
            /* Inserting into empty position. No setup needed before insert. */
        } else {
            if (pool[EVPOOL_SIZE-1].key == NULL) {
                sds cached = pool[EVPOOL_SIZE-1].cached;
                memmove(pool+k+1,pool+k,sizeof(pool[0])*(EVPOOL_SIZE-k-1));
                pool[k].cached = cached;
            } else {
                k--;
                sds cached = pool[0].cached; /* Save SDS before overwriting. */
                if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);
                memmove(pool,pool+1,sizeof(pool[0])*k);
                pool[k].cached = cached;
            }
        }
        int klen = sdslen(key);
        if (klen > EVPOOL_CACHED_SDS_SIZE) {
            pool[k].key = sdsdup(key);
        } else {
            memcpy(pool[k].cached,key,klen+1);
            sdssetlen(pool[k].cached,klen);
            pool[k].key = pool[k].cached;
        }
        pool[k].idle = idle;
        pool[k].dbid = dbid;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值