Redis专题-缓存穿透、缓存雪崩、缓存击穿
一.緩存穿透
緩存穿透概念
緩存穿透是指查詢一個(gè)一定不存在的數(shù)據(jù),在數(shù)據(jù)庫(kù)沒有,自然在緩存中也不會(huì)有。導(dǎo)致用戶查詢的時(shí)候,在緩存中找不到對(duì)應(yīng)key的value,每次都要去數(shù)據(jù)庫(kù)再查詢一遍,如果從存儲(chǔ)層查不到數(shù)據(jù)則不寫入緩存,這將導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請(qǐng)求都要到存儲(chǔ)層去查詢,失去了緩存的意義。在流量大時(shí),可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應(yīng)用,這就是漏洞。
緩存穿透解決方案
有很多種方法可以有效地解決緩存穿透問題。
緩存空值
如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù)據(jù)不存在,還是系統(tǒng)故障)我們?nèi)匀话堰@個(gè)空結(jié)果進(jìn)行緩存,但它的過期時(shí)間會(huì)很短,最長(zhǎng)不超過5分鐘。通過這個(gè)設(shè)置的默認(rèn)值存放到緩存,這樣第二次到緩存中獲取就有值了,而不會(huì)繼續(xù)訪問數(shù)據(jù)庫(kù)。
采用布隆過濾器BloomFilter
在緩存之前加一層BloomFilter,將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中,一個(gè)一定不存在的數(shù)據(jù)會(huì)被這個(gè)bitmap攔截掉,從而避免了對(duì)底層存儲(chǔ)系統(tǒng)的查詢壓力。在查詢的時(shí)候先去BloomFilter去查詢key是否存在,如果不存在就直接返回,存在再去查詢緩存,緩存中沒有再去查詢數(shù)據(jù)庫(kù)。對(duì)BloomFilter有興趣的可以看我的另一篇文章教你用BitMap排序、查找和存儲(chǔ)大量數(shù)據(jù)。
二.緩存雪崩
緩存雪崩概念
緩存雪崩是指在我們?cè)O(shè)置緩存時(shí)采用了相同的過期時(shí)間,導(dǎo)致緩存在某一時(shí)刻同時(shí)失效,所有原本應(yīng)該訪問緩存的請(qǐng)求都去查詢數(shù)據(jù)庫(kù)了,而對(duì)數(shù)據(jù)庫(kù)CPU和內(nèi)存造成巨大壓力,嚴(yán)重的會(huì)造成數(shù)據(jù)庫(kù)宕機(jī)。
緩存雪崩解決方案
設(shè)置不同的過期時(shí)間,讓緩存失效的時(shí)間點(diǎn)盡量均勻
在原有的失效時(shí)間基礎(chǔ)上增加一個(gè)隨機(jī)值,比如1-5分鐘隨機(jī),這樣每一個(gè)緩存的過期時(shí)間的重復(fù)率就會(huì)降低,就很難引發(fā)集體失效的事件。
考慮用隊(duì)列或者鎖讓程序執(zhí)行在壓力范圍之內(nèi),當(dāng)然這種方案可能會(huì)影響并發(fā)量;
熱點(diǎn)數(shù)據(jù)可以考慮不失效。
三.緩存擊穿
緩存擊穿概念
在平常高并發(fā)的系統(tǒng)中,大量的請(qǐng)求同時(shí)查詢一個(gè)key,此時(shí)這個(gè)key正好失效了,就會(huì)導(dǎo)致大量的請(qǐng)求都打到數(shù)據(jù)庫(kù)上面去,造成數(shù)據(jù)庫(kù)請(qǐng)求量過大,壓力劇增,這種現(xiàn)象我們稱為緩存擊穿。
緩存擊穿解決方案
使用互斥鎖(mutex key)
業(yè)界比較常用的做法,是使用mutex。簡(jiǎn)單地來說,就是在緩存失效的時(shí)候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者M(jìn)emcache的ADD)去set一個(gè)mutex key,當(dāng)操作返回成功時(shí),再進(jìn)行l(wèi)oad db的操作并回設(shè)緩存,否則,就重試整個(gè)get緩存的方法。
SETNX,是「SET if Not eXists」的縮寫,也就是只有不存在的時(shí)候才設(shè)置,可以利用它來實(shí)現(xiàn)鎖的效果。redis2.6.1之前版本未實(shí)現(xiàn)setnx的過期時(shí)間,這里給出兩種版本代碼參考:
//2.6.1之前 String get(String key) { String value = redis.get(key); if (value == null) { if (redis.setnx(key_mutex, "1")) { // 3 min timeout to avoid mutex holder crash redis.expire(key_mutex, 3 * 60) value = db.get(key); redis.set(key, value); redis.delete(key_mutex); } else { //其他線程休息50毫秒后重試 Thread.sleep(50); get(key); } } } //2.6.1之后 String get(key) {String value = redis.get(key);if (value == null) { //代表緩存值過期//設(shè)置3min的超時(shí),防止del操作失敗的時(shí)候,下次緩存過期一直不能load dbif (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表設(shè)置成功value = db.get(key);redis.set(key, value, expire_secs);redis.del(key_mutex);} else { //這個(gè)時(shí)候代表同時(shí)候的其他線程已經(jīng)load db并回設(shè)到緩存了,這時(shí)候重試獲取緩存值即可sleep(50);get(key); //重試}} else {return value; }}永遠(yuǎn)不過期
這里的“永遠(yuǎn)不過期”包含兩層意思:
從實(shí)戰(zhàn)看,這種方法對(duì)于性能非常友好,唯一不足的就是構(gòu)建緩存時(shí)候,其余線程(非構(gòu)建緩存的線程)可能訪問的是老數(shù)據(jù),但是對(duì)于一般的互聯(lián)網(wǎng)功能來說這個(gè)還是可以忍受。
猜你感興趣:
Redis專題-集群模式
Redis專題-持久化方式
Redis專題-底層數(shù)據(jù)結(jié)構(gòu)與使用場(chǎng)景
Redis專題-緩存穿透、緩存雪崩、緩存擊穿
更多文章請(qǐng)點(diǎn)擊:更多…
總結(jié)
以上是生活随笔為你收集整理的Redis专题-缓存穿透、缓存雪崩、缓存击穿的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谈谈InnoDB下的记录锁,间隙锁,ne
- 下一篇: MYSQL专题-由简到繁理解索引结构