自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Redis:內(nèi)存被我用完了!該怎么辦?

存儲 存儲軟件 Redis
Redis是一個內(nèi)存數(shù)據(jù)庫,當(dāng)Redis使用的內(nèi)存超過物理內(nèi)存的限制后,內(nèi)存數(shù)據(jù)會和磁盤產(chǎn)生頻繁的交換,交換會導(dǎo)致Redis性能急劇下降。所以在生產(chǎn)環(huán)境中我們通過配置參數(shù)maxmemoey來限制使用的內(nèi)存大小。

[[378340]]

介紹

Redis是一個內(nèi)存數(shù)據(jù)庫,當(dāng)Redis使用的內(nèi)存超過物理內(nèi)存的限制后,內(nèi)存數(shù)據(jù)會和磁盤產(chǎn)生頻繁的交換,交換會導(dǎo)致Redis性能急劇下降。所以在生產(chǎn)環(huán)境中我們通過配置參數(shù)maxmemoey來限制使用的內(nèi)存大小。

當(dāng)實際使用的內(nèi)存超過maxmemoey后,Redis提供了如下幾種可選策略。

noeviction:寫請求返回錯誤

volatile-lru:使用lru算法刪除設(shè)置了過期時間的鍵值對 volatile-lfu:使用lfu算法刪除設(shè)置了過期時間的鍵值對 volatile-random:在設(shè)置了過期時間的鍵值對中隨機進行刪除 volatile-ttl:根據(jù)過期時間的先后進行刪除,越早過期的越先被刪除

allkeys-lru:在所有鍵值對中,使用lru算法進行刪除 allkeys-lfu:在所有鍵值對中,使用lfu算法進行刪除 allkeys-random:所有鍵值對中隨機刪除

我們來詳細了解一下lru和lfu算法,這是2個常見的緩存淘汰算法?!敢驗橛嬎銠C緩存的容量是有限的,所以我們要刪除那些沒用的數(shù)據(jù),而這兩種算法的區(qū)別就是判定沒用的緯度不一樣?!?/p>

LRU算法

「lru(Least recently used,最近最少使用)算法,即最近訪問的數(shù)據(jù),后續(xù)很大概率還會被訪問到,即是有用的。而長時間未被訪問的數(shù)據(jù),應(yīng)該被淘汰」

lru算法中數(shù)據(jù)會被放到一個鏈表中,鏈表的頭節(jié)點為最近被訪問的數(shù)據(jù),鏈表的尾節(jié)點為長時間沒有被訪問的數(shù)據(jù)

「lru算法的核心實現(xiàn)就是哈希表加雙向鏈表」。鏈表可以用來維護訪問元素的順序,而hash表可以幫我們在O(1)時間復(fù)雜度下訪問到元素。

「至于為什么是雙向鏈表呢」?主要是要刪除元素,所以要獲取前繼節(jié)點。數(shù)據(jù)結(jié)構(gòu)圖示如下

使用雙向鏈表+HashMap

雙向鏈表節(jié)點定義如下

  1. public class ListNode<K, V> { 
  2.     K key
  3.     V value; 
  4.     ListNode pre; 
  5.     ListNode next
  6.  
  7.     public ListNode() {} 
  8.  
  9.     public ListNode(K key, V value) { 
  10.         this.key = key
  11.         this.value = value; 
  12.     } 

封裝雙向鏈表的常用操作

  1. public class DoubleList { 
  2.  
  3.     private ListNode head; 
  4.     private ListNode tail; 
  5.  
  6.     public DoubleList() { 
  7.         head = new ListNode(); 
  8.         tail = new ListNode(); 
  9.         head.next = tail; 
  10.         tail.pre = head; 
  11.     } 
  12.  
  13.     public void remove(ListNode node) { 
  14.         node.pre.next = node.next
  15.         node.next.pre = node.pre; 
  16.     } 
  17.  
  18.     public void addLast(ListNode node) { 
  19.         node.pre = tail.pre; 
  20.         tail.pre = node; 
  21.         node.pre.next = node; 
  22.         node.next = tail; 
  23.     } 
  24.  
  25.     public ListNode removeFirst() { 
  26.         if (head.next == tail) { 
  27.             return null
  28.         } 
  29.         ListNode first = head.next
  30.         remove(first); 
  31.         return first
  32.     } 

封裝一個緩存類,提供最基本的get和put方法?!感枰⒁猓@兩種基本的方法都涉及到對兩種數(shù)據(jù)結(jié)構(gòu)的修改」。

  1. public class MyLruCache<K, V> { 
  2.  
  3.     private int capacity; 
  4.     private DoubleList doubleList; 
  5.     private Map<K, ListNode> map; 
  6.  
  7.     public MyLruCache(int capacity) { 
  8.         this.capacity = capacity; 
  9.         map = new HashMap<>(); 
  10.         doubleList = new DoubleList(); 
  11.     } 
  12.  
  13.     public V get(Object key) { 
  14.         ListNode<K, V> node = map.get(key); 
  15.         if (node == null) { 
  16.             return null
  17.         } 
  18.         // 先刪除該節(jié)點,再接到尾部 
  19.         doubleList.remove(node); 
  20.         doubleList.addLast(node); 
  21.         return node.value; 
  22.     } 
  23.  
  24.     public void put(K key, V value) { 
  25.         // 直接調(diào)用這邊的get方法,如果存在,它會在get內(nèi)部被移動到尾巴,不用再移動一遍,直接修改值即可 
  26.         if ((get(key)) != null) { 
  27.             map.get(key).value = value; 
  28.             return
  29.         } 
  30.  
  31.         // 如果超出容量,把頭去掉 
  32.         if (map.size() == capacity) { 
  33.             ListNode listNode = doubleList.removeFirst(); 
  34.             map.remove(listNode.key); 
  35.         } 
  36.  
  37.         // 若不存在,new一個出來 
  38.         ListNode node = new ListNode(key, value); 
  39.         map.put(key, node); 
  40.         doubleList.addLast(node); 
  41.     } 

這里我們的實現(xiàn)為最近訪問的放在鏈表的尾節(jié)點,不經(jīng)常訪問的放在鏈表的頭節(jié)點

測試一波,輸出為鏈表的正序輸出(代碼為了簡潔沒有貼toString方法)

  1. MyLruCache<String, String> myLruCache = new MyLruCache<>(3); 
  2. // {5 : 5} 
  3. myLruCache.put("5""5"); 
  4. // {5 : 5}{3 : 3} 
  5. myLruCache.put("3""3"); 
  6. // {5 : 5}{3 : 3}{4 : 4} 
  7. myLruCache.put("4""4"); 
  8. // {3 : 3}{4 : 4}{2 : 2} 
  9. myLruCache.put("2""2"); 
  10. // {4 : 4}{2 : 2}{3 : 3} 
  11. myLruCache.get("3"); 

「因為LinkedHashMap的底層實現(xiàn)就是哈希表加雙向鏈表,所以你可以用LinkedHashMap替換HashMap和DoubleList來改寫一下上面的類」。

我來演示一下更騷的操作,只需要重寫一個構(gòu)造函數(shù)和removeEldestEntry方法即可。

使用LinkedHashMap實現(xiàn)LRU

  1. public class LruCache<K, V> extends LinkedHashMap<K, V> { 
  2.  
  3.     private int cacheSize; 
  4.  
  5.  
  6.     public LruCache(int cacheSize) { 
  7.         /** 
  8.          * initialCapacity: 初始容量大小 
  9.          * loadFactor: 負載因子 
  10.          * accessOrder: false基于插入排序(默認(rèn)),true基于訪問排序 
  11.          */ 
  12.         super(cacheSize, 0.75f, true); 
  13.         this.cacheSize = cacheSize; 
  14.     } 
  15.  
  16.     /** 
  17.      * 當(dāng)調(diào)用put或者putAll方法時會調(diào)用如下方法,是否刪除最老的數(shù)據(jù),默認(rèn)為false 
  18.      */ 
  19.     @Override 
  20.     protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { 
  21.         return size() > cacheSize; 
  22.     } 

注意這個緩存并不是線程安全的,可以調(diào)用Collections.synchronizedMap方法返回線程安全的map

  1. LruCache<String, String> lruCache = new LruCache(3); 
  2. Map<String, String> safeMap = Collections.synchronizedMap(lruCache); 

Collections.synchronizedMap實現(xiàn)線程安全的方式很簡單,只是返回一個代理類。代理類對Map接口的所有方法加鎖

  1. public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { 
  2.     return new SynchronizedMap<>(m); 

LFU算法

「LRU算法有一個問題,當(dāng)一個長時間不被訪問的key,偶爾被訪問一下后,可能會造成一個比這個key訪問更頻繁的key被淘汰?!?/p>

即LRU算法對key的冷熱程度的判斷可能不準(zhǔn)確。而LFU算法(Least Frequently Used,最不經(jīng)常使用)則是按照訪問頻率來判斷key的冷熱程度的,每次刪除的是一段時間內(nèi)訪問頻率較低的數(shù)據(jù),比LRU算法更準(zhǔn)確。

使用3個hash表實現(xiàn)lfu算法

那么我們應(yīng)該如何組織數(shù)據(jù)呢?

為了實現(xiàn)鍵值的對快速訪問,用一個map來保存鍵值對

  1. private HashMap<K, Integer> keyToFreq; 

還需要用一個map來保存鍵的訪問頻率

  1. private HashMap<K, Integer> keyToFreq; 

「當(dāng)然你也可以把值和訪問頻率封裝到一個類中,用一個map來替代上述的2個map」

接下來就是最核心的部分,刪除訪問頻率最低的數(shù)據(jù)。

  • 為了能在O(1)時間復(fù)雜度內(nèi)找到訪問頻率最低的數(shù)據(jù),我們需要一個變量minFreq記錄訪問最低的頻率
  • 每個訪問頻率有可能對應(yīng)多個鍵。當(dāng)空間不夠用時,我們要刪除最早被訪問的數(shù)據(jù),所以需要如下數(shù)據(jù)結(jié)構(gòu),Map<頻率, 有序集合>。每次內(nèi)存不夠用時,刪除有序集合的第一個元素即可。并且這個有序集合要能快速刪除某個key,因為某個key被訪問后,需要從這個集合中刪除,加入freq+1對應(yīng)的集合中
  • 有序集合很多,但是能滿足快速刪除某個key的只有set,但是set插入數(shù)據(jù)是無序的。「幸虧Java有LinkedHashSet這個類,鏈表和集合的結(jié)合體,鏈表不能快速刪除元素,但是能保證插入順序。集合內(nèi)部元素?zé)o序,但是能快速刪除元素,完美」

下面就是具體的實現(xiàn)。

  1. public class LfuCache<K, V> { 
  2.  
  3.     private HashMap<K, V> keyToVal; 
  4.     private HashMap<K, Integer> keyToFreq; 
  5.     private HashMap<Integer, LinkedHashSet<K>> freqTokeys; 
  6.  
  7.     private int minFreq; 
  8.     private int capacity; 
  9.  
  10.     public LfuCache(int capacity) { 
  11.         keyToVal = new HashMap<>(); 
  12.         keyToFreq = new HashMap<>(); 
  13.         freqTokeys = new HashMap<>(); 
  14.         this.capacity = capacity; 
  15.         this.minFreq = 0; 
  16.     } 
  17.  
  18.     public V get(K key) { 
  19.         V v = keyToVal.get(key); 
  20.         if (v == null) { 
  21.             return null
  22.         } 
  23.         increaseFrey(key); 
  24.         return v; 
  25.     } 
  26.  
  27.     public void put(K key, V value) { 
  28.         // get方法里面會增加頻次 
  29.         if (get(key) != null) { 
  30.             // 重新設(shè)置值 
  31.             keyToVal.put(key, value); 
  32.             return
  33.         } 
  34.  
  35.         // 超出容量,刪除頻率最低的key 
  36.         if (keyToVal.size() >= capacity) { 
  37.             removeMinFreqKey(); 
  38.         } 
  39.  
  40.         keyToVal.put(key, value); 
  41.         keyToFreq.put(key, 1); 
  42.         // key對應(yīng)的value存在,返回存在的key 
  43.         // key對應(yīng)的value不存在,添加key和value 
  44.         freqTokeys.putIfAbsent(1, new LinkedHashSet<>()); 
  45.         freqTokeys.get(1).add(key); 
  46.         this.minFreq = 1; 
  47.     } 
  48.  
  49.     // 刪除出現(xiàn)頻率最低的key 
  50.     private void removeMinFreqKey() { 
  51.         LinkedHashSet<K> keyList = freqTokeys.get(minFreq); 
  52.         K deleteKey = keyList.iterator().next(); 
  53.         keyList.remove(deleteKey); 
  54.         if (keyList.isEmpty()) { 
  55.             // 這里刪除元素后不需要重新設(shè)置minFreq 
  56.             // 因為put方法執(zhí)行完會將minFreq設(shè)置為1 
  57.             freqTokeys.remove(keyList); 
  58.         } 
  59.         keyToVal.remove(deleteKey); 
  60.         keyToFreq.remove(deleteKey); 
  61.     } 
  62.  
  63.     // 增加頻率 
  64.     private void increaseFrey(K key) { 
  65.         int freq = keyToFreq.get(key); 
  66.         keyToFreq.put(key, freq + 1); 
  67.         freqTokeys.get(freq).remove(key); 
  68.         freqTokeys.putIfAbsent(freq + 1, new LinkedHashSet<>()); 
  69.         freqTokeys.get(freq + 1).add(key); 
  70.         if (freqTokeys.get(freq).isEmpty()) { 
  71.             freqTokeys.remove(freq); 
  72.             // 最小頻率的set為空,key被移動到minFreq+1對應(yīng)的set了 
  73.             // 所以minFreq也要加1 
  74.             if (freq == this.minFreq) { 
  75.                 this.minFreq++; 
  76.             } 
  77.         } 
  78.     } 

測試一下

  1. LfuCache<String, String> lfuCache = new LfuCache(2); 
  2. lfuCache.put("1""1"); 
  3. lfuCache.put("2""2"); 
  4. // 1 
  5. System.out.println(lfuCache.get("1")); 
  6. lfuCache.put("3""3"); 
  7. // 1的頻率為2,2和3的頻率為1,但2更早之前被訪問,所以被清除 
  8. // 結(jié)果為null 
  9. System.out.println(lfuCache.get("2")); 

 

責(zé)任編輯:武曉燕 來源: Java識堂
相關(guān)推薦

2024-11-11 00:00:06

MySQLID數(shù)據(jù)類型

2020-03-28 15:50:18

數(shù)據(jù)庫自增ID維護

2019-10-12 09:50:46

Redis內(nèi)存數(shù)據(jù)庫

2019-09-18 15:49:06

MySQL數(shù)據(jù)庫面試官

2023-10-17 09:41:04

自增主鍵MySQL

2021-04-13 10:41:25

Redis內(nèi)存數(shù)據(jù)庫

2021-03-17 10:55:14

Redis哈希數(shù)據(jù)庫

2022-04-24 08:23:19

Redis內(nèi)存淘汰策略

2022-04-22 10:30:07

框架JavaScript前端

2022-02-14 15:03:50

MySQL程序員面試

2021-03-13 14:04:43

Redis內(nèi)存策略

2011-06-30 17:58:30

網(wǎng)站被K

2020-03-18 19:00:29

電腦內(nèi)存不足系統(tǒng)

2016-11-24 14:44:49

云計算

2010-11-17 11:06:34

跳槽

2018-08-20 19:39:14

區(qū)塊鏈職業(yè)崗位

2010-05-20 16:08:01

亞馬遜故障

2024-10-09 17:06:52

RedisHash哈希表

2024-10-18 09:55:50

RedisHash數(shù)據(jù)

2020-09-29 12:15:13

生死鎖MySQL
點贊
收藏

51CTO技術(shù)棧公眾號