Redis過期策略和記憶體淘汰機制(手寫LRU演算法)
1 問題分析:
redis 的過期策略都有哪些?記憶體淘汰機制都有哪些?手寫一下 LRU 程式碼實現?如果你連這個問題都不知道,上來就懵了,回答不出來,那線上你寫程式碼的時候,想當然的認為寫進 redis 的資料就一定會存在,後面導致系統各種 bug,誰來負責?
常見的有兩個問題:
- 往 redis 寫入的資料怎麼沒了?
可能有同學會遇到,在生產環境的 redis 經常會丟掉一些資料,寫進去了,過一會兒可能就沒了。我的天,同學,你問這個問題就說明 redis 你就沒用對啊。redis 是快取,你給當儲存了是吧?
啥叫快取?用記憶體當快取。記憶體是無限的嗎,記憶體是很寶貴而且是有限的,磁碟是廉價而且是大量的。可能一臺機器就幾十個 G 的記憶體,但是可以有幾個 T 的硬碟空間。redis 主要是基於記憶體來進行高效能、高併發的讀寫操作的。
那既然記憶體是有限的,比如 redis 就只能用 10G,你要是往裡面寫了 20G 的資料,會咋辦?當然會幹掉 10G 的資料,然後就保留 10G 的資料了。那幹掉哪些資料?保留哪些資料?當然是幹掉不常用的資料,保留常用的資料了。
- 資料明明過期了,怎麼還佔用著記憶體?
這是由 redis 的過期策略來決定。
2 面試題回答:
redis 過期策略
redis 過期策略是:定期刪除+惰性刪除。
所謂定期刪除,指的是 redis 預設是每隔 100ms 就隨機抽取一些設定了過期時間的 key,檢查其是否過期,如果過期就刪除。
假設 redis 裡放了 10w 個 key,都設定了過期時間,你每隔幾百毫秒,就檢查 10w 個 key,那 redis 基本上就死了,cpu 負載會很高的,消耗在你的檢查過期 key 上了。注意,這裡可不是每隔 100ms 就遍歷所有的設定過期時間的 key,那樣就是一場效能上的災難
但是問題是,定期刪除可能會導致很多過期 key 到了時間並沒有被刪除掉,那咋整呢?所以就是惰性刪除了。這就是說,在你獲取某個 key 的時候,redis 會檢查一下 ,這個 key 如果設定了過期時間那麼是否過期了?如果過期了此時就會刪除,不會給你返回任何東西。
獲取 key 的時候,如果此時 key 已經過期,就刪除,不會返回任何東西。
但是實際上這還是有問題的,如果定期刪除漏掉了很多過期 key,然後你也沒及時去查,也就沒走惰性刪除,此時會怎麼樣?如果大量過期 key 堆積在記憶體裡,導致 redis 記憶體塊耗盡了,咋整?
答案是:走記憶體淘汰機制。
記憶體淘汰機制
redis 記憶體淘汰機制有以下幾個:
- noeviction: 當記憶體不足以容納新寫入資料時,新寫入操作會報錯,這個一般沒人用吧,實在是太噁心了。
- allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的 key(這個是最常用的)。
- allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個 key,這個一般沒人用吧,為啥要隨機,肯定是把最近最少使用的 key 給幹掉啊。
- volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的 key(這個一般不太合適)。
- volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個 key。
- volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的 key 優先移除。
手寫一個 LRU 演算法
你可以現場手寫最原始的 LRU 演算法,那個程式碼量太大了,似乎不太現實。
不求自己純手工從底層開始打造出自己的 LRU,但是起碼要知道如何利用已有的 JDK 資料結構實現一個 Java 版的 LRU。
class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int CACHE_SIZE;
/**
* 傳遞進來最多能快取多少資料
*
* @param cacheSize 快取大小
*/
public LRUCache(int cacheSize) {
// true 表示讓 linkedHashMap 按照訪問順序來進行排序,最近訪問的放在頭部,最老訪問的放在尾部。
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
// 當 map中的資料量大於指定的快取個數的時候,就自動刪除最老的資料。
return size() > CACHE_SIZE;
}
}