《redis深度歷險》十-LRU\LFU
LFU VS LRU
LFU:least frequntly used,按最近的訪問頻率淘汰,比LRU更加精準的表示了一個key的被訪問熱度。
redis物件的熱度
redis的所有物件頭結構中都有一個24bit的欄位,用來記錄物件的熱度。
typedef struct redisObject { unsigned type:4; // 物件型別如 zset,set,hash等 unsigned encoding:4 // 物件編碼如ziplist,intset,skiplist等 unsigned lru:24; // 物件的熱度 int refcount; // 引用計數 void *ptr // 物件的body }robj
LRU
Least Recently Used
在 LRU 模式下, lru 欄位儲存的是 Redis 時鐘 server.lruclocko Redis 時鐘是一個24bit 的整數,預設是 Unix 時間戳對 2^24 取模的結果,大約 97 天清零一次 。當某個 key被訪問一次,它的物件頭結構的 lru欄位值就會被更新為 server.lruclock。
如果 server.lruclock 沒有折返(對 2^24 取模〉,它就是一直遞增的,這意昧著對 象的 lru欄位不會超過 server.lruclock的值。下方左圖
如果超過了,說明 server.lruclock折返了。 通過這個邏輯就可以精準計算出物件多長時間沒有被訪問一一即“物件的空閒時間”。( 下方右圖有些問題,lru不可能超過max的,小於等於max)
LFU
在LFU模式下,lru欄位用來儲存兩個值,分別是ldt(last decrement time)16bit和logc(logistic counter)8bit
loge 是 8 個 bit,用來儲存訪問頻次,因為 8 個 bit 能表示的最大整數值為 255,儲存頻次肯定遠遠不夠,所以這 8 個 bit 儲存的是頻次的對數值,並且這個 值還會隨時間衰減,如果它的值比較小,那麼就很容易被回收。為了確保新建立的 物件不被回收,新物件的這 8 個 bit 會被初始化為一個大於零的值 LFU_INIT_VAL(預設是=5) 。
ldt 是 16 個 bit,用來儲存上一次 loge 的更新時間
ldt不是在物件被訪問時更新的,而是在redis的淘汰邏輯進行時更新的,每次淘汰都是採用隨機策略, 隨機挑選若干個 key,更新這個 key 的“熱度”,淘汰掉“熱度”最低的 key。因為 Redis 採用的是隨機演算法,如果 key 比較多的話,那麼 ldt 更新得可能會比較慢。不 過既然它是分鐘級別的精度,也沒有必要更新得過於頻繁。
ldt 更新的同時也會一同衰減 loge 的值。衰減也有特定的演算法。
總而言之,就是通過logc和ldt相互結合,可以獲得某個鍵的使用頻率。
快取時間戳
redis每一次獲取系統時 間戳都是一次系統呼叫, 系統呼叫相對來說是比較費時間的,作為單執行緒的Redis承 受不起,所以它需要對時間進行快取,獲取時間都是從快取中直接拿。
惰性刪除
共享機制
127.0.0.1:6379> sadd heat wade bosh james
(integer) 3
127.0.0.1:6379> sadd cavs irving love james
(integer) 3
127.0.0.1:6379> sunionstore players heat cavs
(integer) 5
127.0.0.1:6379> smembers players
1) "wade"
2) "bosh"
3) "james"
4) "love"
5) "irving"
新的集合包含了舊集合的所有元素,裡面有一個trick,底層的字串物件被共享了。
為什麼物件共享是懶惰刪除的最大障礙呢,因為懶惰刪除相當於徹底砍掉某個樹枝,將它扔到非同步刪除佇列裡去,這裡是徹底刪除,如果底層的物件是被共享的,就不是徹底刪除,下圖就不是徹底刪除。
為了支援懶惰刪除,就必須實現無共享設計。
非同步刪除的實現
主執行緒將刪除任務傳遞給非同步執行緒,他是通過一個普通的雙向連結串列實現的,因為連結串列需要支援多執行緒併發操作,所以需要有鎖來保護。
執行懶惰刪除時, Redis 將刪除操作的相關引數封裝成一個 bio_job 結構,然後 追加到連結串列尾部。非同步執行緒通過遍歷連結串列摘取 job 元素來挨個執行非同步任務 。
佇列安全
前面提到任務佇列是個不安全的雙向連結串列 , 需要使用鎖來保護它。當主執行緒 將任務追加到佇列之前需要給它加鎖,追加完畢後,再釋放鎖,還需要喚醒非同步線 程一一 如果其在休眠的話。
非同步執行緒需要對任務佇列進行輪詢處理,依次從連結串列表頭摘取元素逐個處理 。 摘取元素的時候也需要加鎖,摘出來之後再解鎖。如果一個元素都沒有,它需要等待, 直到主執行緒來喚醒它繼續工作。