LRU算法與增強

2019-12-21     sandag

概要

本文的想法來自於本人學習MySQL時的一個知識點:MySQL Innodb引擎中對緩衝區的處理。雖然沒有仔細研究其源碼實現,但其設計仍然啟發了我。

本文針對LRU存在的問題,思考一種增強算法來避免或降低緩存污染,主要辦法是對原始LRU空間劃分出young與old兩段區域 ,通過命中數(或block時間)來控制,並用一個0.37的百分比係數規定old的大小。

內容分以下幾小節,實現代碼為Java:

1.LRU基本概念

2.LRU存在問題與LRUG設計

3.LRUG詳細說明

4.完整示例代碼

1.LRU基本概念

LRU(Least recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據。常用於一些緩衝區置換,頁面置換等處理。

一個典型的雙向鍊表+HashMap的LRU如下:

2.LRU存在問題與LRUG設計

LRU的問題是無法迴避突發性的熱噪數據,造成緩存數據的污染。對此有些LRU的變種,如LRU-K、2Q、MQ等,通過維護兩個或多個隊列來控制緩存數據的更新淘汰。我把本文討論的算法叫LRUG,僅是我寫代碼時隨便想的一個名字。

LRUG使用HashMap和雙向鍊表,沒有其他的維護隊列,而是在雙向鍊表上劃分young,old區域,young段在old段之前,有新數據時不會馬上插入到young段,而是先放入old段,若該數據持續命中,次數超過一定數量(也可以是鎖定一段時間)後再進行插入首部的動作。兩段以37%為界,即滿載後old段的大小最多占總容量的37%。(圖1)

(圖1)

3.LRUG詳細說明

3.1首先給出雙向鍊表的節點結構,其中hitNum是命中次數:

    private static class Node{        int hitNum;        K key;        V value;        Node prev;        Node next;        Node(K key,V value){            this.key=key;            this.value=value;            hitNum=0;        }    }

3.2在加載階段,數據以先後順序加入鍊表,半滿載時,young段已滿,新數據以插入方式加入到old段,如圖2所示。注意半滿載時,也可能有madeYoung操作,把old區的數據提到young頭。

(圖2)

    public void put(K key,V value){        Node node=caches.get(key);        if(node==null){            if(caches.size()>=capcity){                caches.remove(last.key);                removeLast();            }            node=new Node(key,value);            if(caches.size()>=pointBorder){                madeOld(node);            }else{                madeYoung(node);            }        }else {            node.value=value;            if(++node.hitNum>BLOCK_HIT_NUM){                madeYoung(node);            }        }        caches.put(key,node);    }

3.3當數據命中時,如果位於young區,命中數+1後進行常規的madeYoung操作,把該項提到鍊表首部。如圖3

(圖3)

如果命中項位於old區,對命中數+1後與BLOCK_HIT_NUM設置的值做判斷,超過設定值說明該項數據可能不是突發數據,進行madeYoung操作提到鍊表首部,否則不做處理。

特別的,如果命中項正好是point,則point應該往後退一項,指向原point的下一項,此時young區膨脹了一項,而old區縮小了一項。極端情況下,ponit項持續被命中並進行madeYoung,point不斷後退直到尾巴,此時young區占有100%容量,而old區為0,設置point指向last,意味著新數據項加入時,淘汰掉young區的末尾,而新數據項放在末尾成為old區。如圖4

(圖4)

    public void madeYoung(Node node){        if(first==node){            return;        }        if(node==point){            point=node.next;            if(point==null) {                point=last;            }        }        if(node.next!=null){            node.next.prev=node.prev;        }        if(node.prev!=null){            node.prev.next=node.next;        }        if(node==last){            last=node.prev;        }        if(first==null||last==null){            first=last=node;            point=null;            return;        }        node.next=first;        first.prev=node;        first=node;    }    public void madeOld(Node node){        if(point.prev!=null){            point.prev.next=node;            node.prev=point.prev;        }        if(point.next!=null){            node.next=point.next;            point.next.prev=node;        }        point=node;    }

3.4需要一個清理的方法。也可以設置一些監測方法,如一段時間內的命中數(監測命中率)等,這與本篇主要內容無關就不寫在這了。

    public void removeLast(){        if(last!=null){            if(last==point) {                point=null;            }            last=last.prev;            if(last==null) {                first=null;            }else{                last.next=null;            }        }    }

4.示例代碼

主要代碼如下,時間倉促,可能一些地方會考慮不周,讀者如發現,歡迎指出。

package com.company;import java.util.HashMap;public class LRUNum {    private HashMap caches;    private Node first;    private Node last;    private Node point;    private int size;    private int capcity;    private static final int BLOCK_HIT_NUM=2;    private static final float MID_POINT=0.37f;    private int pointBorder;    public LRUNum(int capcity){        this.size=0;        this.capcity=capcity;        this.caches=new HashMap(capcity);        this.pointBorder=this.capcity-(int)(this.capcity*this.MID_POINT);    }    public void put(K key,V value){        Node node=caches.get(key);        if(node==null){            if(caches.size()>=capcity){                caches.remove(last.key);                removeLast();            }            node=new Node(key,value);            if(caches.size()>=pointBorder){                madeOld(node);            }else{                madeYoung(node);            }        }else {            node.value=value;            if(++node.hitNum>BLOCK_HIT_NUM){                madeYoung(node);            }        }        caches.put(key,node);    }    public V get(K key){        Node node =caches.get(key);        if(node==null){            return null;        }        if(++node.hitNum>BLOCK_HIT_NUM){            madeYoung(node);        }        return node.value;    }    public Object remove(K key){        Node node =caches.get(key);        if(node!=null){            if(node.prev!=null){                node.prev.next=node.next;            }            if(node.next!=null){                node.next.prev=node.prev;            }            if(node==first){                first=node.next;            }            if(node==last){                last=node.prev;            }        }        return caches.remove(key);    }    public void removeLast(){        if(last!=null){            if(last==point) {                point=null;            }            last=last.prev;            if(last==null) {                first=null;            }else{                last.next=null;            }        }    }    public void clear(){        first=null;        last=null;        point=null;        caches.clear();    }    public void madeYoung(Node node){        if(first==node){            return;        }        if(node==point){            point=node.next;            if(point==null) {                point=last;            }        }        if(node.next!=null){            node.next.prev=node.prev;        }        if(node.prev!=null){            node.prev.next=node.next;        }        if(node==last){            last=node.prev;        }        if(first==null||last==null){            first=last=node;            point=null;            return;        }        node.next=first;        first.prev=node;        first=node;    }    public void madeOld(Node node){        if(point.prev!=null){            point.prev.next=node;            node.prev=point.prev;        }        if(point.next!=null){            node.next=point.next;            point.next.prev=node;        }        point=node;    }    private static class Node{        int hitNum;        K key;        V value;        Node prev;        Node next;        Node(K key,V value){            this.key=key;            this.value=value;            hitNum=0;        }    }}
文章來源: https://twgreatdaily.com/zh-cn/7cm4J28BMH2_cNUg02zM.html