【大廠面試02期】Redis過期key是怎麼樣清理的?
在Redis中,對於過期key的清理主要有惰性清除,定時清理,記憶體不夠時清理三種方法,下面我們就來具體看看這三種清理方法。
(1)惰性清除
在訪問key時,如果發現key已經過期,那麼會將key刪除。
(2)定時清理
Redis配置項hz定義了serverCron任務的執行週期,預設每次清理時間為25ms,每次清理會依次遍歷所有DB,從db隨機取出20個key,如果過期就刪除,如果其中有5個key過期,那麼就繼續對這個db進行清理,否則開始清理下一個db。
(3)記憶體不夠時清理
當執行寫入命令時,如果發現記憶體不夠,那麼就會按照配置的淘汰策略清理記憶體,淘汰策略一般有6種,Redis4.0版本後又增加了2種,主要由分為三類
-
第一類 不處理,等報錯(預設的配置)
- noeviction,發現記憶體不夠時,不刪除key,執行寫入命令時直接返回錯誤資訊。(Redis預設的配置就是noeviction)
-
第二類 從所有結果集中的key中挑選,進行淘汰
- allkeys-random 就是從所有的key中隨機挑選key,進行淘汰
- allkeys-lru 就是從所有的key中挑選最近使用時間距離現在最遠的key,進行淘汰
- allkeys-lfu 就是從所有的key中挑選使用頻率最低的key,進行淘汰。(這是Redis 4.0版本後新增的策略)
-
第三類 從設定了過期時間的key中挑選,進行淘汰
這種就是從設定了expires過期時間的結果集中選出一部分key淘汰,挑選的演算法有:
-
volatile-random 從設定了過期時間的結果集中隨機挑選key刪除。
-
volatile-lru 從設定了過期時間的結果集中挑選上次使用時間距離現在最久的key開始刪除
-
volatile-ttl 從設定了過期時間的結果集中挑選可存活時間最短的key開始刪除(也就是從哪些快要過期的key中先刪除)
-
volatile-lfu 從過期時間的結果集中選擇使用頻率最低的key開始刪除(這是Redis 4.0版本後新增的策略)
-
LRU演算法
LRU演算法的設計原則是如果一個資料近期沒有被訪問到,那麼之後一段時間都不會被訪問到。所以當元素個數達到限制的值時,優先移除距離上次使用時間最久的元素。
可以使用雙向連結串列Node+HashMap<String, Node>來實現,每次訪問元素後,將元素移動到連結串列頭部,當元素滿了時,將連結串列尾部的元素移除,HashMap主要用於根據key獲得Node以及新增時判斷節點是否已存在和刪除時快速找到節點。
PS:使用單向連結串列能不能實現呢,也可以,單向連結串列的節點雖然獲取不到pre節點的資訊,但是可以將下一個節點的key和value設定在當前節點上,然後把當前節點的next指標指向下下個節點,這樣相當於把下一個節點刪除了
//雙向連結串列
public static class ListNode {
String key;//這裡儲存key便於元素滿時,刪除尾節點時可以快速從HashMap刪除鍵值對
Integer value;
ListNode pre = null;
ListNode next = null;
ListNode(String key, Integer value) {
this.key = key;
this.value = value;
}
}
ListNode head;
ListNode last;
int limit=4;
HashMap<String, ListNode> hashMap = new HashMap<String, ListNode>();
public void add(String key, Integer val) {
ListNode existNode = hashMap.get(key);
if (existNode!=null) {
//從連結串列中刪除這個元素
ListNode pre = existNode.pre;
ListNode next = existNode.next;
if (pre!=null) {
pre.next = next;
}
if (next!=null) {
next.pre = pre;
}
//更新尾節點
if (last==existNode) {
last = existNode.pre;
}
//移動到最前面
head.pre = existNode;
existNode.next = head;
head = existNode;
//更新值
existNode.value = val;
} else {
//達到限制,先刪除尾節點
if (hashMap.size() == limit) {
ListNode deleteNode = last;
hashMap.remove(deleteNode.key);
//正是因為需要刪除,所以才需要每個ListNode儲存key
last = deleteNode.pre;
deleteNode.pre = null;
last.next = null;
}
ListNode node = new ListNode(key,val);
hashMap.put(key,node);
if (head==null) {
head = node;
last = node;
} else {
//插入頭結點
node.next = head;
head.pre = node;
head = node;
}
}
}
public ListNode get(String key) {
return hashMap.get(key);
}
public void remove(String key) {
ListNode deleteNode = hashMap.get(key);
ListNode preNode = deleteNode.pre;
ListNode nextNode = deleteNode.next;
if (preNode!=null) {
preNode.next = nextNode;
}
if (nextNode!=null) {
nextNode.pre = preNode;
}
if (head==deleteNode) {
head = nextNode;
}
if (last == deleteNode) {
last = preNode;
}
hashMap.remove(key);
}
LFU演算法
LFU演算法的設計原則時,如果一個資料在最近一段時間被訪問的時次數越多,那麼之後被訪問的概率會越大,基本實現是每個資料都有一個引用計數,每次資料被訪問後,引用計數加1,需要淘汰資料時,淘汰引用計數最小的資料。在Redis的實現中,每次key被訪問後,引用計數是加一個介於0到1之間的數p,並且訪問越頻繁p值越大,而且在一定的時間間隔內,
如果key沒有被訪問,引用計數會減少。
最後
大家有什麼想法,歡迎進群一起討論(因為大群已經滿200人了,大家可以掃碼進這個小群,我拉大家進大群)!本文已收錄到1.1K Star數開源學習指南——《大廠面試指北》,如果想要了解更多大廠面試相關的內容,瞭解更多可以看
http://notfound9.github.io/interviewGuide/#/docs/BATInterview