Redis LRU 淘汰原理
思考(作業):基于一個數據結構做緩存,怎么實現LRU——最長時間不被訪問的元素在超過容量時刪除?
問題:如果基于傳統LRU 算法實現Redis LRU 會有什么問題?
需要額外的數據結構存儲,消耗內存。
Redis LRU 對傳統的LRU 算法進行了改良,通過隨機采樣來調整算法的精度。
如果淘汰策略是LRU,則根據配置的采樣值maxmemory_samples(默認是5 個),隨機從數據庫中選擇m 個key, 淘汰其中熱度最低的key 對應的緩存數據。所以采樣參數m 配置的數值越大, 就越能精確的查找到待淘汰的緩存數據,但是也消耗更多的CPU 計算,執行效率降低。
問題:如何找出熱度最低的數據?
Redis 中所有對象結構都有一個lru 字段, 且使用了unsigned 的低24 位,這個字段用來記錄對象的熱度。對象被創建時會記錄lru 值。在被訪問的時候也會更新lru 的值。但是不是獲取系統當前的時間戳,而是設置為全局變量server.lruclock 的值。
源碼:server.h
typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount;void *ptr; } robj;server.lruclock 的值怎么來的?
Redis 中有個定時處理的函數serverCron , 默認每100 毫秒調用函數updateCachedTime 更新一次全局變量的server.lruclock 的值,它記錄的是當前unix時間戳。
源碼:server.c
void updateCachedTime(void) {time_t unixtime = time(NULL);atomicSet(server.unixtime,unixtime);server.mstime = mstime();struct tm tm;localtime_r(&server.unixtime,&tm);server.daylight_active = tm.tm_isdst; }問題:為什么不獲取精確的時間而是放在全局變量中?不會有延遲的問題嗎?
這樣函數lookupKey 中更新數據的lru 熱度值時,就不用每次調用系統函數time,可以提高執行效率。
OK,當對象里面已經有了LRU 字段的值,就可以評估對象的熱度了。
函數estimateObjectIdleTime 評估指定對象的lru 熱度,思想就是對象的lru 值和全局的server.lruclock 的差值越大(越久沒有得到更新), 該對象熱度越低。
源碼evict.c
/* 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) * LRU_CLOCK_RESOLUTION;} else {return (lruclock + (LRU_CLOCK_MAX - o->lru)) *LRU_CLOCK_RESOLUTION;} }server.lruclock 只有24 位,按秒為單位來表示才能存儲194 天。當超過24bit 能表示的最大時間的時候,它會從頭開始計算。
server.h
#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */在這種情況下,可能會出現對象的lru 大于server.lruclock 的情況,如果這種情況出現那么就兩個相加而不是相減來求最久的key。
為什么不用常規的哈希表+雙向鏈表的方式實現?需要額外的數據結構,消耗資源。而Redis LRU 算法在sample 為10 的情況下,已經能接近傳統LRU 算法了。
https://redis.io/topics/lru-cache
問題:除了消耗資源之外,傳統LRU 還有什么問題?
如圖,假設A 在10 秒內被訪問了5 次,而B 在10 秒內被訪問了3 次。因為B 最后一次被訪問的時間比A 要晚,在同等的情況下,A 反而先被回收。
問題:要實現基于訪問頻率的淘汰機制,怎么做?
LFU
server.h
typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount;void *ptr; } robj;當這24 bits 用作LFU 時,其被分為兩部分:
高16 位用來記錄訪問時間(單位為分鐘,ldt,last decrement time)
低8 位用來記錄訪問頻率,簡稱counter(logc,logistic counter)
counter 是用基于概率的對數計數器實現的,8 位可以表示百萬次的訪問頻率。
對象被讀寫的時候,lfu 的值會被更新。
db.c——lookupKey
void updateLFU(robj *val) {unsigned long counter = LFUDecrAndReturn(val);counter = LFULogIncr(counter);val->lru = (LFUGetTimeInMinutes()<<8) | counter; }增長的速率由,lfu-log-factor 越大,counter 增長的越慢
redis.conf 配置文件
# lfu-log-factor 10如果計數器只會遞增不會遞減,也不能體現對象的熱度。沒有被訪問的時候,計數器怎么遞減呢?
減少的值由衰減因子lfu-decay-time(分鐘)來控制,如果值是1 的話,N 分鐘沒有訪問就要減少N。
redis.conf 配置文件
# lfu-decay-time 1?
總結
以上是生活随笔為你收集整理的Redis LRU 淘汰原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis中的淘汰策略
- 下一篇: 为什么需要Redis 集群