全是乾貨的技術號: 本文已收錄在github,歡迎 star/fork: https://github.com/Wasabi1234/Java-Interview-Tutorialjava
1 設置帶過時時間的 key
expire key seconds 時間複雜度:O(1)
設置key
的過時時間。超時後,將會自動刪除該key
。在Redis的術語中一個key
的相關超時是volatile的。git
超時後只有對key
執行DEL、SET、GETSET時纔會清除。 這意味着,從概念上講全部改變key
而不用新值替換的全部操做都將保持超時不變。 例如,使用 INCR
遞增key的值,執行 LPUSH
將新值推到 list 中或用 HSET
改變hash的field
,這些操做都使超時保持不變。github
- 使用
PERSIST
命令能夠清除超時,使其變成一個永久key
- 若
key
被RENAME
命令修改,相關的超時時間會轉移到新key
- 若
key
被RENAME
命令修改,好比原來就存在Key_A
,而後調用RENAME Key_B Key_A
命令,這時無論原來Key_A
是永久的仍是設爲超時的,都會由Key_B
的有效期狀態覆蓋
注意,使用非正超時調用 EXPIRE/PEXPIRE 或具備過去時間的 EXPIREAT/PEXPIREAT 將致使key被刪除而不是過時(所以,發出的key事件將是 del,而不是過時)。redis
1.1 刷新過時時間
對已經有過時時間的key
執行EXPIRE
操做,將會更新它的過時時間。有不少應用有這種業務場景,例如記錄會話的session。算法
1.2 Redis 以前的 2.1.3 的差別
在 Redis 版本以前 2.1.3 中,使用更改其值的命令更改具備過時集的密鑰具備徹底刪除key的效果。因爲如今修復的複製層中存在限制,所以須要此語義。spring
EXPIRE 將返回 0,而且不會更改具備超時集的鍵的超時。shell
1.3 返回值
1
若是成功設置過時時間。0
若是key
不存在或者不能設置過時時間。
1.4 示例
假設有一 Web 服務,對用戶最近訪問的最新 N 頁感興趣,這樣每一個相鄰頁面視圖在上一個頁面以後不超過 60 秒。從概念上講,能夠將這組頁面視圖視爲用戶的導航會話,該會話可能包含有關ta當前正在尋找的產品的有趣信息,以便你能夠推薦相關產品。緩存
可以使用如下策略輕鬆在 Redis 中對此模式建模:每次用戶執行頁面視圖時,您都會調用如下命令:session
MULTI RPUSH pagewviews.user:<userid> http://..... EXPIRE pagewviews.user:<userid> 60 EXEC
若是用戶空閒超過 60 秒,則將刪除該key,而且僅記錄差別小於 60 秒的後續頁面視圖。 此模式很容易修改,使用 INCR 而不是使用 RPUSH 的列表。dom
1.5 帶過時時間的 key
一般,建立 Redis 鍵時沒有關聯的存活時間。key將永存,除非用戶以顯式方式(例如 DEL 命令)將其刪除。 EXPIRE 族的命令可以將過時項與給定key關聯,但代價是該key使用的額外內存。當key具備過時集時,Redis 將確保在通過指定時間時刪除該key。 可以使用 EXPIRE 和 PERSIST 命令(或其餘嚴格命令)更新或徹底刪除生存的關鍵時間。
1.6 過時精度
在 Redis 2.4 中,過時可能不許確,而且可能介於 0 到 1 秒之間。 因爲 Redis 2.6,過時偏差從 0 到 1 毫秒。
1.7 過時和持久化
過時信息的鍵存儲爲絕對 Unix 時間戳(Redis 版本 2.6 或更高版本爲毫秒)。這意味着即便 Redis 實例不處於活動狀態,時間也在流動。 要使過時工做良好,必須穩定計算機時間。若將 RDB 文件從兩臺計算機上移動,其時鐘中具備大 desync,則可能會發生有趣的事情(如加載時加載到過時的全部key)。 即便運行時的實例,也始終會檢查計算機時鐘,例如,若是將一個key設置爲 1000 秒,而後在未來設置計算機時間 2000 秒,則該key將當即過時,而不是持續 1000 秒。
2 Redis 如何使key過時
鍵的過時方式有兩種:被動方式 - 惰性刪除,主動方式 - 按期刪除。
2.1 惰性刪除
當客戶端嘗試訪問key時,key會被動過時,即Redis會檢查該key是否設置了過時時間,若是過時了就會刪除,也不會返回任何東西。 注意並不是是key到期了就會被自動刪除,而是當查詢該key時,Redis再很懶惰地檢查是否刪除。這和 spring 的延遲初始化有着殊途同歸之妙。
固然,這是不夠的,由於有過時的key,永遠不會再訪問。不管如何,這些key都應過時,所以請按期 Redis 在具備過時集的key之間隨機測試幾個key。已過時的全部key將從key空間中刪除。
2.2 按期刪除
具體來講,以下 Redis 每秒 10 次:
- 測試 20 個帶有過時的隨機鍵
- 刪除找到的全部已過時key
- 若是超過 25% 的key已過時,從步驟 1 從新開始
這是一個微不足道的機率算法,基本上假設咱們的樣本表明整個key空間,繼續過時,直到可能過時的key百分比低於 25%。 這意味着在任何給定時刻,使用內存的已過時的最大鍵量等於最大寫入操做量/秒除以 4。
2.3 在複製鏈路和 AOF 文件中處理過時的方式
爲了在不犧牲一致性的狀況下得到正確行爲,當key過時時,DEL 操做將同時在 AOF 文件中合成並獲取全部附加的從節點。這樣,過時的這個處理過程集中到主節點中,尚未一致性錯誤的可能性。
可是,雖然鏈接到主節點的從節點不會獨立過時key(但會等待來自master的 DEL),但它們仍將使用數據集中現有過時的完整狀態,所以,當選擇slave做爲master時,它將可以獨立過時key,徹底充當master。
但是,不少過時key,你沒及時去查,按期刪除也漏掉了,大量過時key堆積內存,Redis內存殆耗盡!
所以還需有內存淘汰機制!
3 內存淘汰
3.1 內存淘汰策略
noeviction(Redis默認策略)
不會繼續服務寫請求 (DEL 請求能夠繼續服務),讀請求能夠繼續進行。這樣 能夠保證不會丟失數據,可是會讓線上的業務不能持續進行。
- config.c
createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, server.maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
allkeys-random
當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某key。憑啥隨機呢,至少也是把最近最少使用的key刪除。
allkeys-lru(Least Recently Used)
當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key,沒有設置過時時間的 key 也會被淘汰。
allkeys-lfu(Least Frequently Used)
LRU的關鍵是看頁面最後一次被使用到發生調度的時間長短,而LFU關鍵是看必定時間段內頁面被使用的頻率。
volatile-lru
嘗試淘汰設置了過時時間的 key,最少使用的 key 優先被淘汰。 沒有設置過時時間的 key 不會被淘汰,這樣能夠保證須要持久化的數據不會忽然丟失。 區別於 allkey-lru,這個策略要淘汰只是過時的 key 集合。
volatile-lfu
volatile-random
淘汰的 key 是過時 key 集合中隨機的 key。
volatile-ttl
淘汰的策略不是 LRU,而是 key 的剩餘壽命 ttl 的值,ttl 越小越優先被淘汰。
volatile-xxx 策略只會針對帶過時時間的 key 進行淘汰,allkeys-xxx 策略會對全部的 key 進行淘汰。
- 若是你只是拿 Redis 作緩存,那應該使用 allkeys-xxx,客戶端寫緩存時沒必要攜帶過時時間。
- 若是你還想同時使用 Redis 的持久化功能,那就使用 volatile-xxx 策略,這樣能夠保留沒有設置過時時間的 key,它們是永久的 key 不會被 LRU 算法淘汰。
3.2 手寫LRU
確實有時會問這個,由於有些候選人若是確實過五關斬六將,前面的問題都答的很好,那麼其實讓他寫一下LRU算法,能夠考察一下編碼功底
你能夠現場手寫最原始的LRU算法,那個代碼量太大了,不太現實
public class LRUCache<K, V> extends LinkedHashMap<K, V> { private final int CACHE_SIZE; // 這裏就是傳遞進來最多能緩存多少數據 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 eldest) { // 當KV數據量大於指定緩存個數時,就自動刪除最老數據 return size() > CACHE_SIZE; } }
參考