Redis緩存失效策略思考
掃描二維碼
隨時(shí)隨地手機(jī)看文章
1 刪除過(guò)期數(shù)據(jù)
我們?cè)O(shè)置Redis元素時(shí)可以指定過(guò)期時(shí)間,那么Redis如何刪除這些超時(shí)元素?Redis采用了兩種策略:定期刪除和惰性刪除。
(1) 定期刪除
Redis每隔一段時(shí)間就檢查哪些KEY已經(jīng)過(guò)期,如果過(guò)期就刪除。但是我們來(lái)設(shè)想一個(gè)問(wèn)題:如果Redis存儲(chǔ)KEY非常多,僅僅超時(shí)檢查這項(xiàng)工作就會(huì)非常耗費(fèi)資源并嚴(yán)重影響服務(wù)能力。為了解決這個(gè)問(wèn)題Redis并不是檢查全量KEY而只是檢查部分,同時(shí)引入了惰性刪除策略。
(2) 惰性刪除
假設(shè)當(dāng)KEY1已經(jīng)過(guò)期,但是由于沒(méi)有被檢查到而未被刪除。那么當(dāng)程序訪問(wèn)KEY1時(shí),Redis會(huì)檢查KEY1是否過(guò)期,如果過(guò)期則刪除并不返回該值,這就是惰性刪除策略。結(jié)合定期刪除和惰性刪除兩種策略,就可以保證過(guò)期數(shù)據(jù)可以被刪除。
2 內(nèi)存淘汰
當(dāng)內(nèi)存不足時(shí)Redis會(huì)選擇一些緩存元素進(jìn)行刪除,那么哪些元素會(huì)被刪除?常見(jiàn)內(nèi)存淘汰策略如下:
no-enviction禁止驅(qū)逐數(shù)據(jù),新寫(xiě)入操作會(huì)報(bào)錯(cuò) volatile-lru從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集選擇最近最少使用的數(shù)據(jù)淘汰 volatile-ttl從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集選擇將要過(guò)期的數(shù)據(jù)淘汰 volatile-random從已設(shè)置過(guò)期時(shí)間的數(shù)據(jù)集選擇任意的數(shù)據(jù)淘汰 allkeys-lru從數(shù)據(jù)集選擇最近最少使用的數(shù)據(jù)淘汰 allkeys-random從數(shù)據(jù)集選擇任意的數(shù)據(jù)淘汰
LRU(Least Recently Used)最近最少使用是比較常用的策略,我們使用JAVA代碼實(shí)現(xiàn)一個(gè)簡(jiǎn)單LRU策略,代碼原理并不復(fù)雜:使用一個(gè)鏈表存儲(chǔ)元素,表頭存儲(chǔ)最近訪問(wèn)的元素,這樣存儲(chǔ)的結(jié)果是表尾存儲(chǔ)最早訪問(wèn)的元素,表頭存儲(chǔ)最近訪問(wèn)的元素,當(dāng)超出鏈表容量時(shí)刪除表尾元素即可。
/** * 元素對(duì)象 * * @author 微信公眾號(hào)「IT徐胖子」 * */public class CacheElement { private String key; private Object value; public CacheElement(String key, Object value) { this.key = key; this.value = value; } public String getKey() { return key; } public Object getValue() { return value; } @Override public String toString() { return "Element [key=" + key + ", value=" + value + "]"; }} /** * LRU緩存策略 * * @author 微信公眾號(hào)「IT徐胖子」 * */public class LRUCache { private int capacity; private LinkedListcache; public LRUCache(int capacity) { this.capacity = capacity; this.cache = new LinkedList<>(); } /** * 獲取緩存元素 * * 找到元素后將元素從原位置刪除并插入到鏈表頭部(最近) */ public CacheElement get(String key) { Iteratoriterator = cache.iterator(); while (iterator.hasNext()) { CacheElement element = iterator.next(); if (element.getKey().equals(key)) { iterator.remove(); System.out.println("獲取到元素=" + element); put(element.getKey(), element.getValue()); return element; } } return null; } /** * 存儲(chǔ)緩存元素 * * 新元素插入到鏈表頭部(最近) */ public boolean put(String key, Object value) { Iteratoriterator = cache.iterator(); while (iterator.hasNext()) { CacheElement element = iterator.next(); if (element.getKey().equals(key)) { iterator.remove(); break; } } if (capacity == cache.size()) { CacheElement deleteElement = cache.removeLast(); System.out.println("容量已滿(mǎn)刪除尾部元素=" + deleteElement); } CacheElement element = new CacheElement(key, value); cache.addFirst(element); System.out.println("插入頭部元素=" + element); return Boolean.TRUE; } @Override public String toString() { return "LRUCache [capacity=" + capacity + ", cache=" + cache + "]"; }} /** * LRU測(cè)試實(shí)例 * * @author 微信公眾號(hào)「IT徐胖子」 * */public class TestCache { public static void main(String[] args) { System.out.println("==================存儲(chǔ)緩存元素=================="); LRUCache cache = new LRUCache(2); CacheElement element0 = new CacheElement("k0", "v0"); CacheElement element1 = new CacheElement("k1", "v1"); CacheElement element2 = new CacheElement("k2", "v2"); cache.put(element0.getKey(), element0.getValue()); cache.put(element1.getKey(), element1.getValue()); cache.put(element2.getKey(), element2.getValue()); System.out.println("==================獲取緩存元素=================="); System.out.println("獲取元素之前緩存對(duì)象=" + cache); cache.get("k1"); System.out.println("獲取元素之后緩存對(duì)象=" + cache); }} ==================存儲(chǔ)緩存元素==================插入頭部元素=Element [key=k0, value=v0]插入頭部元素=Element [key=k1, value=v1]容量已滿(mǎn)刪除尾部元素=Element [key=k0, value=v0]插入頭部元素=Element [key=k2, value=v2] ==================獲取緩存元素==================獲取元素之前緩存對(duì)象=LRUCache [capacity=2, cache=[Element [key=k2, value=v2], Element [key=k1, value=v1]]]獲取到元素=Element [key=k1, value=v1]插入頭部元素=Element [key=k1, value=v1]獲取元素之后緩存對(duì)象=LRUCache [capacity=2, cache=[Element [key=k1, value=v1], Element [key=k2, value=v2]]]
3 文章總結(jié)
本文分析了Redis緩存失效策略:刪除過(guò)期數(shù)據(jù)和內(nèi)存淘汰,并且使用JAVA代碼模擬了LRU策略實(shí)現(xiàn)。
這里我們可以做一個(gè)展開(kāi):Redis分布式鎖是否可靠。因?yàn)镽edis存在內(nèi)存淘汰機(jī)制,那么作為分布式鎖的KEY概率上會(huì)被淘汰,從而導(dǎo)致分布式鎖失效。所以?xún)H僅有分布式鎖是不夠的,我們還需要其它方法,例如設(shè)置數(shù)據(jù)庫(kù)層唯一索引,防止重復(fù)數(shù)據(jù)產(chǎn)生。
特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒(méi)關(guān)注的小伙伴,可以長(zhǎng)按關(guān)注一下:
![]()
![]()
長(zhǎng)按訂閱更多精彩▼
![]()
如有收獲,點(diǎn)個(gè)在看,誠(chéng)摯感謝
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!