標籤:
一直對這redis和memcached的兩個開源緩衝系統的LRU演算法感興趣。今天就打算總結一下這兩個LRU演算法的實現和區別。
首先要知道什麼是LRU演算法:LRU是Least Recently Used 近期最少使用演算法。相關的資料網上一大堆。http://en.wikipedia.org/wiki/Cache_algorithms#LRU
redis的六種策略
rewriteConfigEnumOption(state,"maxmemory-policy",server.maxmemory_policy, "volatile-lru", REDIS_MAXMEMORY_VOLATILE_LRU, "allkeys-lru", REDIS_MAXMEMORY_ALLKEYS_LRU, "volatile-random", REDIS_MAXMEMORY_VOLATILE_RANDOM, "allkeys-random", REDIS_MAXMEMORY_ALLKEYS_RANDOM, "volatile-ttl", REDIS_MAXMEMORY_VOLATILE_TTL, "noeviction", REDIS_MAXMEMORY_NO_EVICTION, NULL, REDIS_DEFAULT_MAXMEMORY_POLICY);
- volatile-lru:從已設定到期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰
- volatile-ttl:從已設定到期時間的資料集(server.db[i].expires)中挑選將要到期的資料淘汰
- volatile-random:從已設定到期時間的資料集(server.db[i].expires)中任意選擇資料淘汰
- allkeys-lru:從資料集(server.db[i].dict)中挑選最近最少使用的資料淘汰
- allkeys-random:從資料集(server.db[i].dict)中任意選擇資料淘汰
- no-enviction(驅逐):禁止驅逐資料
而lru演算法經常是被用在緩衝系統,就比如我們的memcached和redis上。
首先介紹一下redis的。我去最近剛發布的redis3.0上搜尋一下代碼,通過它的代碼實現和如何?lru來講解。
/* The actual Redis Object */#define REDIS_LRU_BITS 24#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */#define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr;} robj;/* Macro used to obtain the current LRU clock. * If the current resolution is lower than the frequency we refresh the * LRU clock (as it should be in production servers) we return the * precomputed value, otherwise we need to resort to a function call. */#define LRU_CLOCK() ((1000/server.hz <= REDIS_LRU_CLOCK_RESOLUTION) ? server.lruclock : getLRUClock())
unsigned int getLRUClock(void) { return (mstime()/REDIS_LRU_CLOCK_RESOLUTION) & REDIS_LRU_CLOCK_MAX;}
定義相關的一些lru的一些常量,並將
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
講object的成員加入一個24位長度的lru成員。
我們發現使用最簡單粗暴的方式,就是和ALL -key機制是一樣,隨機選出16個。通過到期時間對pool記憶體資料進行淘汰。
/* volatile-lru and allkeys-lru policy */ 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) { 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; de = dictFind(dict,pool[k].key); /* 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. */ if (de) { bestkey = dictGetKey(de); break; } else { /* Ghost... */ continue; } } } }
通過lru來淘汰,返回lru時間,然後通過當前lru比較
/* 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) { unsigned long long lruclock = LRU_CLOCK(); if (lruclock >= o->lru) { return (lruclock - o->lru) * REDIS_LRU_CLOCK_RESOLUTION; } else { return (lruclock + (REDIS_LRU_CLOCK_MAX - o->lru)) * REDIS_LRU_CLOCK_RESOLUTION; }}
在servercron會定時更新起LRU的時間。隨機找到maxmemeory中的key,然後根據lru數值進行刪除。
因為
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
這個方法是通過隨機得來的資料。
總結:
redis只是每個Object維護一個相對的時間,淘汰時,隨機取3個或者更多的,找到最老的進行淘汰.這樣節省了雙鏈表的指標開銷,讀時還不用加鎖.雖不能保證一定淘汰最老的,但傾向於淘汰偏老的對象, 經過我們線上的實測:和標準的LRU對比,命中率的損失非常小, 效果不錯。
更多文章,歡迎訪問 http://blog.csdn.net/wallwind
探究redis和memcached的 LRU演算法--------redis的LRU的實現