【數(shù)據(jù)結(jié)構(gòu)與算法】Redis中LRU算法的基本思想

前言

LRU(least recently used)是一種緩存置換算法。即在緩存有限的情況下腐螟,如果有新的數(shù)據(jù)需要加載進(jìn)緩存栏豺,則需要將最不可能被繼續(xù)訪問的緩存剔除掉。

基于 HashMap 和 雙向鏈表實(shí)現(xiàn) LRU

  • 整體的設(shè)計(jì)思路是窃诉,可以使用 HashMap 存儲(chǔ) key杨耙,這樣可以做到 save 和 get key的時(shí)間都是 O(1),而 HashMap 的 Value 記錄需要緩存數(shù)據(jù)在 LRU 存儲(chǔ)中的槽飘痛。
image
  • 而 LRU 存儲(chǔ)是基于雙向鏈表實(shí)現(xiàn)的珊膜,下面的圖演示了它的原理。其中 h 代表雙向鏈表的表頭宣脉,t 代表尾部车柠。如果存儲(chǔ)滿了,可以通過 O(1) 的時(shí)間淘汰掉雙向鏈表的尾部,并更新隊(duì)頭竹祷。
image

下面總結(jié)一下核心操作的步驟:

    1. save(key)谈跛,首先通過 hash 算法,在key space 找到 key溶褪,并且嘗試把數(shù)據(jù)存儲(chǔ)到LRU空間币旧,如果LRU空間足夠,則不需要淘汰舊的內(nèi)容;如果緩存空間不足,會(huì)淘汰掉 tail 指向的內(nèi)容髓霞,并更新隊(duì)頭硕舆,存儲(chǔ)后返回使用槽下標(biāo),更新key space 的value忽你。
    1. get(key),通過 hash 找到 key,然后通過 value 找到 LRU空間對(duì)應(yīng)的槽输瓜,更新LRU指向關(guān)系。
  • 完整基于 Java 的代碼參考如下
class DLinkedNode {
    int key;
    int value;
    DLinkedNode pre;
    DLinkedNode post;
}

LRU Cache

public class LRUCache {

    private Hashtable<Integer, DLinkedNode>
            cache = new Hashtable<Integer, DLinkedNode>();
    private int count;
    private int capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.count = 0;
        this.capacity = capacity;

        head = new DLinkedNode();
        head.pre = null;

        tail = new DLinkedNode();
        tail.post = null;

        head.post = tail;
        tail.pre = head;
    }

    public int get(int key) {

        DLinkedNode node = cache.get(key);
        if(node == null){
            return -1; // should raise exception here.
        }

        // move the accessed node to the head;
        this.moveToHead(node);

        return node.value;
    }

    public void set(int key, int value) {
        DLinkedNode node = cache.get(key);

        if(node == null){

            DLinkedNode newNode = new DLinkedNode();
            newNode.key = key;
            newNode.value = value;

            this.cache.put(key, newNode);
            this.addNode(newNode);

            ++count;

            if(count > capacity){
                // pop the tail
                DLinkedNode tail = this.popTail();
                this.cache.remove(tail.key);
                --count;
            }
        }else{
            // update the value.
            node.value = value;
            this.moveToHead(node);
        }
    }
    /**
     * Always add the new node right after head;
     */
    private void addNode(DLinkedNode node){
        node.pre = head;
        node.post = head.post;

        head.post.pre = node;
        head.post = node;
    }

    /**
     * Remove an existing node from the linked list.
     */
    private void removeNode(DLinkedNode node){
        DLinkedNode pre = node.pre;
        DLinkedNode post = node.post;

        pre.post = post;
        post.pre = pre;
    }

    /**
     * Move certain node in between to the head.
     */
    private void moveToHead(DLinkedNode node){
        this.removeNode(node);
        this.addNode(node);
    }

    // pop the current tail.
    private DLinkedNode popTail(){
        DLinkedNode res = tail.pre;
        this.removeNode(res);
        return res;
    }
}

Redis中的LRU算法

首先Redis并沒有使用雙向鏈表實(shí)現(xiàn)一個(gè)LRU算法芬萍。
Redis整體上是一個(gè)大的dict,key是一個(gè)string,而value都會(huì)保存為一個(gè)robj(redisObject)尤揣。
在Redis的dict中每次按key獲取一個(gè)值的時(shí)候,都會(huì)調(diào)用lookupKey函數(shù),如果配置使用了LRU模式,該函數(shù)會(huì)更新value中的lru字段為當(dāng)前秒級(jí)別的時(shí)間戳柬祠。

Redis3.0的LRU實(shí)現(xiàn)算法:

1.第一次隨機(jī)選取的key都會(huì)放入一個(gè)pool中(pool的大小為16),pool中的key是按lru時(shí)間戳大小順序排列的北戏。

2.接下來每次隨機(jī)選取的key的lru值必須小于pool中最小的lru才會(huì)繼續(xù)放入,直到將pool放滿漫蛔。

3.放滿之后嗜愈,每次如果有新的key需要放入,需要將pool中l(wèi)ru最大的一個(gè)key取出莽龟。

4.需要淘汰的時(shí)候蠕嫁,直接從pool中選取一個(gè)lru最小的值然后將其淘汰。

參考文章

Redis中的LRU算法實(shí)現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毯盈,一起剝皮案震驚了整個(gè)濱河市剃毒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌奶镶,老刑警劉巖迟赃,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異厂镇,居然都是意外死亡纤壁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門捺信,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酌媒,“玉大人欠痴,你說我怎么就攤上這事∶胱桑” “怎么了喇辽?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)雨席。 經(jīng)常有香客問我菩咨,道長(zhǎng),這世上最難降的妖魔是什么陡厘? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任抽米,我火速辦了婚禮,結(jié)果婚禮上糙置,老公的妹妹穿的比我還像新娘云茸。我一直安慰自己,他們只是感情好谤饭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布标捺。 她就那樣靜靜地躺著,像睡著了一般揉抵。 火紅的嫁衣襯著肌膚如雪亡容。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天冤今,我揣著相機(jī)與錄音萍倡,去河邊找鬼。 笑死辟汰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的阱佛。 我是一名探鬼主播帖汞,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼凑术!你這毒婦竟也來了翩蘸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤淮逊,失蹤者是張志新(化名)和其女友劉穎催首,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泄鹏,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡郎任,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了备籽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舶治。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霉猛,到底是詐尸還是另有隱情尺锚,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布惜浅,位于F島的核電站瘫辩,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坛悉。R本人自食惡果不足惜伐厌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吹散。 院中可真熱鬧弧械,春花似錦、人聲如沸空民。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)界轩。三九已至画饥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浊猾,已是汗流浹背抖甘。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留葫慎,地道東北人衔彻。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像偷办,于是被迫代替她去往敵國(guó)和親艰额。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容