面試官:Redis過期後key是怎麼樣清理的?

前言

筆者一個同事面試某大廠時被問到的一個問題,這裏拿來講講:Redis過期後key是怎麼樣清理的?

在Redis中,對於過期key的清理主要有惰性清除,定時清理,內存不夠時清理三種方法,下面我們就來具體看看這三種清理方法。

惰性清除

在訪問key時,如果發現key已經過期,那麼會將key刪除。

定時清理

Redis配置項hz定義了serverCron任務的執行週期,默認每次清理時間爲25ms,每次清理會依次遍歷所有DB,從db隨機取出20個key,如果過期就刪除,如果其中有5個key過期,那麼就繼續對這個db進行清理,否則開始清理下一個db。

內存不夠時清理

當執行寫入命令時,如果發現內存不夠,那麼就會按照配置的淘汰策略清理內存,淘汰策略一般有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算法的設計原則時,如果一個數據在最近一段時間被訪問的時次數越多,那麼之後被訪問的概率會越大,基本實現是每個數據都有一個引用計數,每次數據被訪問後,引用計數加1,需要淘汰數據時,淘汰引用計數最小的數據。在Redis的實現中,每次key被訪問後,引用計數是加一個介於0到1之間的數p,並且訪問越頻繁p值越大,而且在一定的時間間隔內,如果key沒有被訪問,引用計數會減少。

面試官:Redis過期後key是怎麼樣清理的?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章