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

一、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);
        }
    }
}

 

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