LRU算法全稱Least Recently Used,也就是檢查最近最少使用的數據的算法。這個算法一般使用在內存淘汰策略中,用於將不經常使用的數據轉移出內存,將空間騰給最近更經常使用的「熱點數據」。java
初識這個算法忘了是在操做系統課仍是計算機組成原理課上,其在Redis、Guava等工具中也有很是普遍的應用,甚至是最核心的思想之一。若是從此須要本身設計系統,即便不本身實現這個算法,LRU的思想也仍然是很重要的。redis
算法很簡單,只須要將全部數據按使用時間排序,在須要篩選出LRU數據時,取排名靠後的便可。算法
Redis中的數據量一般很龐大,若是每次對全量數據進行排序,勢必將對服務吞吐量形成影響。所以,Redis在LRU淘汰部分key時,使用的是採樣並計算近似LRU的,所以淘汰的是局部LRU數據。數組
Redis內存淘汰策略dom
maxmemory-policy
配置可選參數:ide
noeviction
:不淘汰,內存超限後寫命令會返回錯誤(如OOM, del命令除外)allkeys-lru
:全部key的LRU機制 在全部key中按照最近最少使用LRU原則剔除key,釋放空間volatile-lru
:易失key的LRU 僅以設置過時時間key範圍內的LRU(如均爲設置過時時間,則不會淘汰)allkeys-random
:全部key隨機淘汰 一視同仁,隨機volatile-random
:易失Key的隨機 僅設置過時時間key範圍內的隨機volatile-ttl
:易失key的TTL淘汰 按最小TTL的key優先淘汰Redis LRU的效果工具
左上-理論LRU效果;右上-Redis3.0中的近似LRU(採樣值10);左下-Redis2.8中的近似LRU(採樣值5);右下-Redis3.0中的近似LRU(採樣值5)spa
淺灰色-被淘汰;灰色-未被淘汰;綠色-新寫入操作系統
補充說明:設計
maxmemory-samples
配置表示採樣值,每次刪除時採集的樣本數——採樣值10,表示從設置中定義的key中取10個key進行LRU計算並刪除LRU的那個key結論:
根據LRU算法,在Java中實現須要這些條件:
對於上述的實現思路,java.util.LinkedHashMap
已經實現了其中的99%,所以直接基於LinkedHashMap實現LRUCache很是簡單。
LinkedHashMap爲LRUCache鋪墊了什麼
accessOrder
選項,開啓後會get方法會有額外操做保證鏈表順序按訪問順序逆序排列newNode
方法和newTreeNode
方法,這兩個方法在HashMap中只是建立Node用的,而在LinkedHashMap中不但建立Node,還將Node放在鏈表末尾afterNodeRemoval
父類在remove一個集合中存在的元素後調用afterNodeInsertion
父類在put、compute、merge後調用afterNodeAccess
父類在replace、compute、merge等替換值後會調用,LinkedHashMap在get中開啓accessOrder時調用,究其根本是在對數據有操做時會調用afterNodeRemoval
實現鏈表的刪除操做afterNodeInsertion
並無實現鏈表的插入操做,但新添加了一個Hook方法boolean removeEldestEntry
,當這個Hook方法返回true時,刪除鏈表頭的節點afterNodeAccess
如前所述,開啓accessOrder後會將被操做的節點放在鏈表末尾,保證鏈表順序按訪問順序逆序排列newNode
在建立一個Node的同時,將Node添加到鏈表末尾newTreeNode
建立TreeNode的同時,將Node添加到鏈表末尾get
完成get功能的同時,若是accessOrder開啓,會調用afterNodeAccess將Node移動到鏈表末尾 覆蓋newNode
和newTreeNode
方法後,在put方法中調用的newNode
和newTreeNode
方法也就連帶實現了鏈表的插入操做綜上,咱們能夠了解到LinkedHashMap爲何可以輕鬆實現LRUCache
accessOrder
並實現了afterNodeAccess
方法,所以可以根據訪問或操做順序將最近使用或最近插入的數據放在鏈表尾,越久沒被使用的數據就越靠近鏈表頭,實現了整個鏈表按照LRU的要求排序boolean removeEldestEntry
,這個方法返回true時將會刪除表頭節點,即LRU中應當淘汰的節點,可是這個方法在LinkedHashMap中的實現永遠返回false到這爲止,實現一個LRUCache就很簡單了:實現這個removeEldestEntry
Hook方法,給LinkedHashMap設置一個閾值,那麼超過這個閾值時就會進行LRU淘汰。
網上隨處可見的Java代碼實現
// 繼承LinkedHashMap
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int MAX_CACHE_SIZE;
public LRUCache(int cacheSize) {
// 使用構造方法 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
// initialCapacity、loadFactor都不重要
// accessOrder要設置爲true,按訪問排序
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
MAX_CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
// 超過閾值時返回true,進行LRU淘汰
return size() > MAX_CACHE_SIZE;
}
}
複製代碼
看似幾行代碼解決的事兒,其實只是冰山一角而已。
Using Redis as an LRU cache – Redis
本文搬自個人博客,歡迎參觀!