Redis的常用淘汰策略以及演算法實現

MXC肖某某發表於2021-03-13

一、Redis的記憶體配置

1,Redis配置記憶體為多少合適?

預設:如果不設定最大記憶體大小或者設定最大記憶體大小為0,在64為作業系統下不限制記憶體大小,在32位操作系統下最多使用3GB記憶體。

極限情況:留出一倍記憶體。比如你的redis資料佔用了8G記憶體,那麼你還需要再預留8G空閒記憶體。也就是記憶體需求是16G。記憶體佔用率低於50%是最安全的。

普通情況:正常情況下,在序列化週期內,不會更改所有資料,只會有部分資料更改,那麼,預留出可能產生的更改部分的空間,就行。如果實在要說一個資料的話,一般推薦Redis設定記憶體為最大實體記憶體的75%都是安全的。

2,如何修改記憶體

a)配置檔案修改

  redis.conf中

#設定為100M,單位是byte
maxmemory  104857600

b)命令列修改

config set maxmemory 104857600

3,檢視最大記憶體

config get maxmemory
#或者使用
info memory

4,如果Redis的記憶體你打滿了會怎麼樣?

  

二、Redis的記憶體淘汰策略

1,Redis 過期策略是:定期刪除+惰性刪除。

  所謂定期刪除,指的是 Redis 預設是每隔 100ms 就隨機抽取一些設定了過期時間的 key,檢查其是否過期,如果過期就刪除。

  假設 Redis 裡放了 10w 個 key,都設定了過期時間,你每隔幾百毫秒,就檢查 10w 個 key,那 Redis 基本上就死了,cpu 負載會很高的,消耗在你的檢查過期 key 上了。注意,這裡可不是每隔 100ms 就遍歷所有的設定過期時間的 key,那樣就是一場效能上的災難。實際上 Redis 是每隔 100ms 隨機抽取一些 key 來檢查和刪除的。

  惰性刪除:資料到達過期時間,不做處理。等下次訪問該資料時,如果未過期,返回資料;發現已過期,刪除,返回不存在。

  但是實際上這還是有問題的,如果定期刪除漏掉了很多過期 key,然後你也沒及時去查,也就沒走惰性刪除,此時會怎麼樣?如果大量過期 key 堆積在記憶體裡,導致 Redis 記憶體塊耗盡了,咋整?實際上會走:記憶體淘汰機制。

2,記憶體淘汰機制

Redis記憶體淘汰機制有以下幾個:

  • noeviction: 當記憶體不足以容納新寫入資料時,新寫入操作會報錯,這個一般沒人用吧,實在是太噁心了。
  • allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)。
  • allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個 key,這個一般沒人用吧,為啥要隨機,肯定是把最近最少使用的 key 給幹掉啊。
  • volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的 key(這個一般不太合適)。
  • volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個 key。
  • volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的 key 優先移除。
  • allkeys-lfu: 對所有key使用LFU演算法進行刪除。LFU:最不經常使用,如果一個資料在最近一段時間內使用次數很少,那麼在將來一段時間內被使用的可能性也很小。
  • volatile-lfu: 對所有設定了過期時間的key使用LFU演算法進行刪除。

三、手寫LRU演算法

  力扣題庫

1,採用LinkedHashMap實現

public class Demo015_LRUCacheLinkedHashMap {

    private int capacity;
    private LinkedHashMap<Integer, Integer> linkedHashMap;

    public Demo015_LRUCacheLinkedHashMap(int capacity) {
        this.capacity = capacity;
        /**
         * 三個引數:capacity為容量,0.75位擴容因子,true為按照訪問排序false為按照插入排序
         *   重寫刪除尾結點的方法,一旦發現當前linkhashmap的長度大於總容量就需要刪除*/
        linkedHashMap = new LinkedHashMap<Integer, Integer>(capacity,0.75F,true){
            @Override
            protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
                return super.size() > capacity;
            }
        };
    }

    public void put(int key, int value) {
        linkedHashMap.put(key, value);
    }

    public int get(int key) {
        Integer value = linkedHashMap.getOrDefault(key,-1);
        return value;
    }
}

2,自定義雙向連結串列

  • 定義Node節點:key,val,next和prev
  • 定義DoubleLinkedNode管理Node結點組成頭尾結點的雙向連結串列
  • 定義hashmap儲存每個結點
  • 插入時判斷當前值是否已經存在hashmap中
    • 如果存在就更改當前值,刪除雙向連結串列中原來的這個值,新增新值到連結串列頭結點並修改hashmap中當前值
    • 如果不存在當前值,判斷當前容器是否滿了,如果滿了就刪除連結串列尾部刪除hashmap中資料。並新增新結點到連結串列頭部和hashmap中
  • 獲取時,直接從hashmap中獲取。如果不存在直接返回-1,如果存在就刪除連結串列尾部資料,更新連結串列頭部資料為當前node
public class Demo015_LRUCache {

    class Node<K, V> {
        K key;
        V val;
        Node next;
        Node prev;

        public Node(){
            next = prev = null;
        }

        public Node(K key, V val) {
            this.key = key;
            this.val = val;
            next = prev = null;
        }
    }

    class DoubleLinkedNode<K,V>{
        Node head;
        Node tail;

        public DoubleLinkedNode() {
            head = new Node();
            tail = new Node();
            head.next = tail;
            tail.prev = head;
        }

        public void addHead(Node<K,V> node) {
            node.prev = head;
            node.next = head.next;
            head.next.prev = node;
            head.next = node;
        }

        public void remove(Node<K,V> node) {
            if (node.prev == null || node.next==null) {
                return;
            }
            node.prev.next = node.next;
            node.next.prev = node.prev;
            node.next = null;
            node.prev = null;
        }

        public Node<K,V> getLast() {
            if (tail.prev == head) {
                return null;
            }
            return tail.prev;
        }
    }

    private int capacity;
    private HashMap<Integer, Node<Integer,Integer>> hashMap;
    private DoubleLinkedNode<Integer, Integer> doubleLinkedNode;

    public Demo015_LRUCache(int capacity) {
        this.capacity = capacity;
        hashMap = new HashMap<>();
        doubleLinkedNode = new DoubleLinkedNode<>();
    }

    public int get(int key) {
        Node<Integer,Integer> node = hashMap.get(key);
        if (node == null) {
            return -1;
        }
        doubleLinkedNode.remove(node);
        doubleLinkedNode.addHead(node);
        return node.val;
    }

    public void put(int key, int value) {
        Node<Integer, Integer> node = hashMap.get(key);
        if (node == null) { //沒有新增過
            if (hashMap.size() == capacity) { //達到最大值狀態
                //刪除最後結點
                Node<Integer, Integer> last = doubleLinkedNode.getLast();
                doubleLinkedNode.remove(last);
                hashMap.remove(last.key);
            }
            //新增頭結點
            node = new Node<>(key, value);
            hashMap.put(key,node);
            doubleLinkedNode.addHead(node);
        }else {
            //如果新增過,刪除雙向連結串列的該節點,將其修改值之後新增到頭節點
            doubleLinkedNode.remove(node);
            node.val = value;

            doubleLinkedNode.addHead(node);
            hashMap.put(key, node);
        }
    }
}

 

相關文章