【大廠面試02期】Redis過期key是怎么樣清理的?

PS:本文已收錄到1.1K Star數(shù)開源學(xué)習(xí)指南——《大廠面試指北》,如果想要了解更多大廠面試相關(guān)的內(nèi)容及獲取《大廠面試指北》離線PDF版,請掃描下方二維碼碼關(guān)注公眾號“大廠面試”,謝謝大家了!項目地址:http://notfound9.github.io/interviewGuide/#/docs/BATInterview

《大廠面試指北》項目截圖:

image

獲取《大廠面試指北》離線PDF版,請掃描下方二維碼關(guān)注公眾號“大廠面試”

image

【大廠面試02期】Redis過期key是怎么樣清理的?

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

(1)惰性清除

在訪問key時,如果發(fā)現(xiàn)key已經(jīng)過期,那么會將key刪除。

(2)定時清理

Redis配置項hz定義了serverCron任務(wù)的執(zhí)行周期,默認(rèn)每次清理時間為25ms,每次清理會依次遍歷所有DB,從db隨機取出20個key,如果過期就刪除,如果其中有5個key過期,那么就繼續(xù)對這個db進(jìn)行清理,否則開始清理下一個db。

(3)內(nèi)存不夠時清理

當(dāng)執(zhí)行寫入命令時,如果發(fā)現(xiàn)內(nèi)存不夠,那么就會按照配置的淘汰策略清理內(nèi)存,淘汰策略一般有6種,Redis4.0版本后又增加了2種,主要由分為三類

  • 第一類 不處理,等報錯(默認(rèn)的配置)

    • noeviction,發(fā)現(xiàn)內(nèi)存不夠時,不刪除key,執(zhí)行寫入命令時直接返回錯誤信息。(Redis默認(rèn)的配置就是noeviction)
  • 第二類 從所有結(jié)果集中的key中挑選,進(jìn)行淘汰

    • allkeys-random 就是從所有的key中隨機挑選key,進(jìn)行淘汰
    • allkeys-lru 就是從所有的key中挑選最近使用時間距離現(xiàn)在最遠(yuǎn)的key,進(jìn)行淘汰
    • allkeys-lfu 就是從所有的key中挑選使用頻率最低的key,進(jìn)行淘汰。(這是Redis 4.0版本后新增的策略)
  • 第三類 從設(shè)置了過期時間的key中挑選,進(jìn)行淘汰

    這種就是從設(shè)置了expires過期時間的結(jié)果集中選出一部分key淘汰,挑選的算法有:

    • volatile-random 從設(shè)置了過期時間的結(jié)果集中隨機挑選key刪除。

    • volatile-lru 從設(shè)置了過期時間的結(jié)果集中挑選上次使用時間距離現(xiàn)在最久的key開始刪除

    • volatile-ttl 從設(shè)置了過期時間的結(jié)果集中挑選可存活時間最短的key開始刪除(也就是從哪些快要過期的key中先刪除)

    • volatile-lfu 從過期時間的結(jié)果集中選擇使用頻率最低的key開始刪除(這是Redis 4.0版本后新增的策略)

LRU算法

LRU算法的設(shè)計原則是如果一個數(shù)據(jù)近期沒有被訪問到,那么之后一段時間都不會被訪問到。所以當(dāng)元素個數(shù)達(dá)到限制的值時,優(yōu)先移除距離上次使用時間最久的元素。

可以使用雙向鏈表Node+HashMap<String, Node>來實現(xiàn),每次訪問元素后,將元素移動到鏈表頭部,當(dāng)元素滿了時,將鏈表尾部的元素移除,HashMap主要用于根據(jù)key獲得Node以及添加時判斷節(jié)點是否已存在和刪除時快速找到節(jié)點。

PS:使用單向鏈表能不能實現(xiàn)呢,也可以,單向鏈表的節(jié)點雖然獲取不到pre節(jié)點的信息,但是可以將下一個節(jié)點的key和value設(shè)置在當(dāng)前節(jié)點上,然后把當(dāng)前節(jié)點的next指針指向下下個節(jié)點,這樣相當(dāng)于把下一個節(jié)點刪除了

//雙向鏈表
    public static class ListNode {
        String key;//這里存儲key便于元素滿時,刪除尾節(jié)點時可以快速從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;
            }
            //更新尾節(jié)點
            if (last==existNode) {
                last = existNode.pre;
            }
            //移動到最前面
            head.pre = existNode;
            existNode.next = head;
            head = existNode;
            //更新值
            existNode.value = val;
        } else {
            //達(dá)到限制,先刪除尾節(jié)點
            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 {
                //插入頭結(jié)點
                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算法的設(shè)計原則時,如果一個數(shù)據(jù)在最近一段時間被訪問的時次數(shù)越多,那么之后被訪問的概率會越大,基本實現(xiàn)是每個數(shù)據(jù)都有一個引用計數(shù),每次數(shù)據(jù)被訪問后,引用計數(shù)加1,需要淘汰數(shù)據(jù)時,淘汰引用計數(shù)最小的數(shù)據(jù)。在Redis的實現(xiàn)中,每次key被訪問后,引用計數(shù)是加一個介于0到1之間的數(shù)p,并且訪問越頻繁p值越大,而且在一定的時間間隔內(nèi),
如果key沒有被訪問,引用計數(shù)會減少。

最后

大家有什么想法,可以一起討論!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容