Redis 緩存高并發(fā)常見(jiàn)問(wèn)題
0x01:緩存的三大問(wèn)題
緩存穿透:訪問(wèn)不存在的數(shù)據(jù)(Bloom Filter,緩存空對(duì)象)
緩存擊穿:熱點(diǎn) key 重建過(guò)程中,造成的緩存問(wèn)題(分布式鎖)
緩存雪崩:Redis 宕機(jī),或緩存批量失效(高可用集群,錯(cuò)開(kāi)緩存失效時(shí)間)
緩存粒度控制
通俗來(lái)講,緩存粒度問(wèn)題就是我們?cè)谑褂镁彺鏁r(shí),是將所有數(shù)據(jù)緩存還是緩存部分?jǐn)?shù)據(jù)?
數(shù)據(jù)類(lèi)型 | 通用性 | 空間占用 (內(nèi)存空間+網(wǎng)絡(luò)碼率) | 代碼維護(hù) |
全部數(shù)據(jù) | 高 | 大 | 簡(jiǎn)單 |
部分?jǐn)?shù)據(jù) | 低 | 小 | 較為復(fù)雜 |
0x02:緩存穿透問(wèn)題,訪問(wèn)不存在的數(shù)據(jù)
緩存穿透是指查詢(xún)一個(gè)一定不存在的數(shù)據(jù),由于緩存不命中,并且出于容錯(cuò)考慮, 如果從存儲(chǔ)層查不到數(shù)據(jù)則不寫(xiě)入緩存,這將導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請(qǐng)求都要到存儲(chǔ)層去查詢(xún),失去了緩存的意義。
可能造成原因
業(yè)務(wù)代碼自身問(wèn)題
惡意攻擊。爬蟲(chóng)等等
危害
對(duì)底層數(shù)據(jù)源壓力過(guò)大,有些底層數(shù)據(jù)源不具備高并發(fā)性。 例如 MySQL 一般來(lái)說(shuō)單臺(tái)能夠扛1000 QPS 就已經(jīng)很不錯(cuò)了
解決方案
緩存空對(duì)象
缺點(diǎn):惡意攻擊的清空下,會(huì)產(chǎn)生大量的空對(duì)象,數(shù)據(jù)庫(kù)壓力仍然存在,治標(biāo)不治本
public class NullValueResultDO implements Serializable {
}
public class UserManager {
UserDAO userDAO;
LocalCache localCache;
public UserDO getUser(String userNick) {
Object object = localCache.get(userNick);
if(object != null) {
if(object instanceof NullValueResultDO) {
return null;
}
return (UserDO)object;
} else {
User user = userDAO.getUser(userNick);
if(user != null) {
localCache.put(userNick,user);
} else {
localCache.put(userNick, new NullValueResultDO());
}
return user;
}
}
}
布隆過(guò)濾器
Google 布隆過(guò)濾器的缺點(diǎn)
不需要網(wǎng)絡(luò) IO,效率高,維護(hù)成本低
缺點(diǎn):基于JVM內(nèi)存的一種布隆過(guò)濾器,重啟即失效,本地內(nèi)存無(wú)法用在分布式場(chǎng)景,不支持大數(shù)據(jù)量存儲(chǔ)
Redis 布隆過(guò)濾器
可擴(kuò)展性 Bloom 過(guò)濾器:一旦 Bloom 過(guò)濾器達(dá)到容量,就會(huì)在其上創(chuàng)建一個(gè)新的過(guò)濾器,不存在重啟即失效或者定時(shí)任務(wù)維護(hù)的成本:基于 Google 實(shí)現(xiàn)的布隆過(guò)濾器需要啟動(dòng)之后初始化布隆過(guò)濾器
缺點(diǎn):需要網(wǎng)絡(luò) IO,性能比 Google 布隆過(guò)濾器低
0x03:緩存擊穿,熱點(diǎn) key 重建緩存問(wèn)題
可能造成的原因
緩存擊穿是指緩存中沒(méi)有但數(shù)據(jù)庫(kù)中有的數(shù)據(jù)(一般是緩存時(shí)間到期),這時(shí)由于大量的并發(fā)訪問(wèn)特別多,同時(shí)讀緩存沒(méi)讀到數(shù)據(jù),又同時(shí)去數(shù)據(jù)庫(kù)去取數(shù)據(jù),引起數(shù)據(jù)庫(kù)壓力瞬間增大,造成過(guò)大壓力。
危害
我們知道,使用緩存,如果獲取不到,才會(huì)去數(shù)據(jù)庫(kù)里獲取。但是如果是熱點(diǎn) key,訪問(wèn)量非常的大,數(shù)據(jù)庫(kù)在重建緩存的時(shí)候,會(huì)出現(xiàn)很多線程同時(shí)重建的情況。因?yàn)楦卟l(fā)導(dǎo)致的大量熱點(diǎn)的 key 在重建還沒(méi)完成的時(shí)候,不斷被重建緩存的過(guò)程,由于大量線程都去做重建緩存工作,導(dǎo)致服務(wù)器拖慢的情況。
解決方案
互斥鎖
第一次獲取緩存的時(shí)候,加一個(gè)鎖,然后查詢(xún)數(shù)據(jù)庫(kù),接著是重建緩存。這個(gè)時(shí)候,另外一個(gè)請(qǐng)求又過(guò)來(lái)獲取緩存,發(fā)現(xiàn)有個(gè)鎖,這個(gè)時(shí)候就去等待,之后都是一次等待的過(guò)程,直到重建完成以后,鎖解除后再次獲取緩存命中。
public String getKey(String key){
String value = redis.get(key);
if(value == null){
// 設(shè)置互斥鎖的key
String mutexKey = "mutex:key:"+key;
// 給這個(gè)key上一把鎖,ex 表示只有一個(gè)線程能執(zhí)行,過(guò)期時(shí)間為 180 秒
if(redis.set(mutexKey,"1","ex 180","nx")){
value = db.get(key);
redis.set(key,value);
redis.delete(mutexKety);
}else{
// 其他的線程休息100毫秒后重試
Thread.sleep(100);
getKey(key);
}
}
return value;
}
互斥鎖的優(yōu)點(diǎn)是思路非常簡(jiǎn)單,具有一致性,但是互斥鎖也有一定的問(wèn)題,就是大量線程在等待的問(wèn)題。存在死鎖的可能性。
0x04: 緩存雪崩,批量失效問(wèn)題
可能造成的原因
緩存雪崩是指機(jī)器宕機(jī)或在我們?cè)O(shè)置緩存時(shí)采用了相同(近似)的過(guò)期時(shí)間,導(dǎo)致緩存在某一時(shí)刻大批量緩存數(shù)據(jù)同時(shí)(批量)失效
危害
請(qǐng)求全部轉(zhuǎn)發(fā)到 DB,DB 瞬時(shí)壓力過(guò)重,導(dǎo)致雪崩。
解決方案
在緩存失效后,通過(guò)加鎖或者隊(duì)列來(lái)控制讀數(shù)據(jù)庫(kù)寫(xiě)緩存的線程數(shù)量。比如對(duì)某個(gè) key 只允許一個(gè)線程查詢(xún)數(shù)據(jù)和寫(xiě)緩存,其他線程等待
做二級(jí)緩存,A1 為原始緩存,A2 為拷貝緩存,A1 失效時(shí),可以訪問(wèn) A2,A1 緩存失效時(shí)間設(shè)置為短期,A2 設(shè)置為長(zhǎng)期
不同的 key,設(shè)置不同的過(guò)期時(shí)間,讓緩存失效的時(shí)間點(diǎn)盡量均勻
如果緩存數(shù)據(jù)庫(kù)是分布式部署,將熱點(diǎn)數(shù)據(jù)均勻分布在不同的緩存數(shù)據(jù)庫(kù)中
source:https://www.yuque.com/nashihuakai/qlwgtg/bwcko4
喜歡,在看
