1. 程式人生 > >Redis快取篇(二)淘汰機制:快取滿了怎麼辦?

Redis快取篇(二)淘汰機制:快取滿了怎麼辦?

上一講提到,快取的容量總是小於後端資料庫的。隨著業務系統的使用,快取資料會撐滿記憶體空間,該怎麼處理呢?

本節我們來學習記憶體淘汰機制。在Redis 4.0之前有6種記憶體淘汰策略,之後又增加2種,一共8種,如下圖所示:

  • noeviction策略:記憶體空間達到maxmemory時,不會淘汰資料,有新寫入時會返回錯誤。
  • volatile-ttl策略:針對設定了過期時間的鍵值對,根據過期時間的先後進行修改,越早過期的越先被刪除。
  • volatile-random策略:在設定了過期時間的鍵值對中,進行隨機刪除。
  • volatile-lru策略:使用LRU演算法篩選設定了過期時間的鍵值對,進行刪除。
  • volatile-lfu策略:使用LFU演算法篩選設定了過期時間的鍵值對,進行刪除。
  • allkeys-random策略:在所有鍵值對中隨機選擇並刪除資料。
  • allkeys-lru策略:使用LRU演算法在所有資料中進行篩選並刪除資料。
  • allkeys-lfu策略:使用LFU演算法在所有資料中進行篩選並刪除資料。

對於TTL、Random比較好理解,下面學習一下LRU和LFU演算法。

LRU演算法

LRU演算法,全稱Least Recently Used。

其中MRU端指最近訪問的資料;LRU端指最早訪問的資料。

被訪問的資料和新插入的資料會移到MRU端,空間滿了後從LRU端刪除。這樣一來,最早訪問的資料會逐漸被淘汰。

但LRU演算法也有其缺點:

  • 需要用連結串列管理所有快取資料,帶來額外的空間開銷
  • 大量資料被訪問,就會帶來很多連結串列移動操作,降低Redis效能

而Redis對其進行簡化:

  • Redis會記錄每個資料的最近一次訪問的時間戳(RedisObject中的lru欄位)
  • Redis第一次淘汰資料時,會隨機選出N個數據,作為一個候選集合。
  • 然後比較這N個數據的lru,把lru最小的從快取中淘汰。

當再次淘汰資料時,會挑選資料放到第一次淘汰時的候選集合,要求小於候選集合中最小的lru值才能加入。

其中maxmemory-samples配置項:表示選出的個數N。可以通過以下命令進行設定:

CONFIG SET maxmemory-samples 100

 

LFU演算法

LFU演算法是在LRU策略基礎上,為每個資料增加一個計數器,來統計這個資料的訪問次數。

使用LFU策略篩選淘汰資料時,根據資料的訪問次數進行篩選,把訪問次數最低的資料淘汰。如果兩個資料訪問次數相同,再比較兩個資料的訪問時效性,把更久的資料淘汰。

如何實現

LFU也是使用RedisObject的lru欄位來實現。

把24bit的lru欄位拆分成兩部分:

  • ldt值:lru欄位的前16bit,表示資料的訪問時間戳;
  • counter值:lru欄位的後8bit,表示資料的訪問次數;

當LFU策略淘汰資料時,Redis在候選集合中,根據lru欄位的後8bit選擇訪問次數最小的資料進行淘汰。如果訪問次數相同,再根據lru欄位的前16bit值大小,選擇更久的資料進行淘汰。

關於counter只有8bit(255)的問題

Redis並沒有採用資料每被訪問一次,就+1的規則,而是採用一個類似於隨機+1的規則:

double r = (double)rand()/RAND_MAX;
...
double p = 1.0/(baseval*server.lfu_log_factor+1);
if (r < p) counter++;   

 

通過設定 lfu_log_factor 配置項來控制計數器值增加的速度,避免counter很快到255。下圖是 lfu_log_factor 設定不同值時,counter的增長情況:

總結

  • 如何設定快取空間大小:設定為總資料量的15%到30%,兼顧訪問效能和記憶體空間開銷。
  • 優先使用allkeys-lru策略。如果業務資料中有明顯的冷熱資料區分,建議使用allkeys-lru策略。
  • 如果業務資料訪問頻繁相關不大,沒有明顯的冷熱資料區分,建議使用allkeys-random策略。
  • 如果業務中有置頂的需要,可以使用volatile-lru策略,同時不給這些置頂資料設定過期時間。

參考資料

  • 24 | 替換策略:快取滿了怎麼辦?