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;
}
}