厉害了,如何通过双 key 来解决缓存并发问题?
來(lái)源公眾號(hào):IT人的職場(chǎng)進(jìn)階
https://mp.weixin.qq.com/s/qOxTw4vT744ZjsJkc5HsEQ
我們?cè)谑褂镁彺娴臅r(shí)候,不管Redis或者是Memcached,基本上都會(huì)遇到以下3個(gè)問(wèn)題:緩存穿透、緩存并發(fā)、緩存集中失效。這篇文章主要針對(duì)【緩存并發(fā)】問(wèn)題展開(kāi)討論,并給出具體的解決方案。
1.什么是緩存并發(fā)?
在高并發(fā)的訪問(wèn)下,當(dāng)某個(gè)緩存處于過(guò)期失效的時(shí)間點(diǎn)時(shí),極有可能出現(xiàn)多個(gè)進(jìn)程同時(shí)查詢?cè)摼彺?#xff08;該緩存是業(yè)務(wù)場(chǎng)景中非常 "熱點(diǎn)" 的數(shù)據(jù),比如首頁(yè)的緩存數(shù)據(jù))。因?yàn)椴樵僁B并重新緩存需要一定的時(shí)間,而瞬時(shí)并發(fā)非常高,如果此時(shí)緩存失效了,這些并發(fā)請(qǐng)求都會(huì)直接訪問(wèn)DB,從而導(dǎo)致DB服務(wù)器的CPU或者內(nèi)存負(fù)載過(guò)高,服務(wù)能力下降甚至宕機(jī),此問(wèn)題即緩存并發(fā)問(wèn)題。
緩存并發(fā)問(wèn)題在微服務(wù)架構(gòu)下凸顯更加嚴(yán)重,比如某個(gè)基礎(chǔ)服務(wù)A因?yàn)樯鲜鰡?wèn)題出現(xiàn)不可用,進(jìn)而導(dǎo)致依賴A服務(wù)的B、C服務(wù)也不可用,而B(niǎo)服務(wù)的不可用又導(dǎo)致服務(wù)E、F不可用,不可用的服務(wù)就像滾雪球一樣越滾越大,最終導(dǎo)致系統(tǒng)出現(xiàn)嚴(yán)重故障,此現(xiàn)象我們稱之為雪崩效應(yīng)。
注意緩存并發(fā)和緩存集中失效的區(qū)別在于:緩存并發(fā)指的是某一個(gè)熱點(diǎn)key的失效,而緩存集中失效則是一批key同時(shí)失效,兩者都可能導(dǎo)致雪崩問(wèn)題。
2.如何解決?
針對(duì)該問(wèn)題,存在以下三種解決方案:
加鎖:在緩存失效后,通過(guò)加鎖的方式只允許一個(gè)線程查詢數(shù)據(jù)和寫(xiě)緩存,其他線程如果發(fā)現(xiàn)有鎖就等待,等解鎖后再返回?cái)?shù)據(jù)。該方案會(huì)造成部分請(qǐng)求等待。
二級(jí)緩存:A1為原始緩存,A2為拷貝緩存。A1失效時(shí),可以訪問(wèn)A2,其中A1的緩存失效時(shí)間設(shè)置為短期(比如5min),A2的緩存失效時(shí)間設(shè)置為長(zhǎng)期(比如1天)。如果緩存value很大,此方案的緩存空間利用率低。
雙key:思路和方案2類似,不同的是雙key分別緩存過(guò)期時(shí)間(key-time)和緩存數(shù)據(jù)(key-data),其中(key-time)的緩存失效時(shí)間設(shè)置為短期(比如5min),(key-data)的緩存失效時(shí)間設(shè)置為長(zhǎng)期(比如1天)。當(dāng)?shù)谝粋€(gè)線程發(fā)現(xiàn) key-time 過(guò)期不存在時(shí),則先更新key-time,然后去查詢數(shù)據(jù)庫(kù)并更新key-data 的值;當(dāng)其他線程來(lái)獲取數(shù)據(jù)時(shí),雖然第一個(gè)線程還沒(méi)有從數(shù)據(jù)庫(kù)查詢完畢并更新緩存,但發(fā)現(xiàn)key-time存在,會(huì)直接讀取緩存的舊數(shù)據(jù)返回。和二級(jí)緩存的方案對(duì)比,該方案的緩存空間利用率高。
3.雙key方案的示例代碼
1. 寫(xiě)緩存的示例代碼
public?static?boolean?set(String?key,?String?value,?int?seconds)?{Jedis?jedis?=?null;try?{jedis?=?jedisPool.getResource();if?(seconds?>?0){//?添加數(shù)據(jù)緩存,緩存有效時(shí)間?=?真實(shí)時(shí)間?+?1?天jedis.set(key,?seconds?+?60?*?60?*?24,?value);//?添加過(guò)期時(shí)間緩存,緩存有效時(shí)間?=?真實(shí)時(shí)間jedis.set("lock_"?+?key,?seconds,?System.currentTimeMillis()?+?"");}?else?{jedis.set(key,?value);jedis.set("lock_"?+?key,?System.currentTimeMillis()?+?"");}return?true;}?catch?(JedisException?e)?{if?(jedis?!=?null)?{returnBrokenResource(jedis);jedis?=?null;}throw?e;}?finally?{if?(jedis?!=?null)?{returnResource(jedis);}} }2. 讀緩存的示例代碼
public?static?String?get(String?key)?{Jedis?jedis?=?null;try?{jedis?=?jedisPool.getResource();//?緩存過(guò)期?&&?獲取鎖成功,setnx:原子操作if?(jedis.setnx("lock_"?+?key,?System.currentTimeMillis()?+?"")?==?1)?{/***?將鎖的失效時(shí)間設(shè)為60s,在60s內(nèi)若查詢數(shù)據(jù)庫(kù)成功,則更新鎖的失效時(shí)間=緩存時(shí)間*?如果60s內(nèi)出現(xiàn)異常,則60s后第一個(gè)請(qǐng)求又會(huì)去訪問(wèn)數(shù)據(jù)庫(kù)*?返回null表示沒(méi)有查詢到數(shù)據(jù)庫(kù),外層代碼會(huì)通過(guò)數(shù)據(jù)庫(kù)獲取數(shù)據(jù)并設(shè)置緩存*/jedis.expire("lock_"?+?key,?60);return?null;}?else{//?緩存未過(guò)期或者緩存過(guò)期但獲取鎖失敗,?則返回舊數(shù)據(jù)return?jedis.get(key);}}?catch?(JedisException?e)?{if?(jedis?!=?null)?{returnBrokenResource(jedis);jedis?=?null;}?throw?e;}?finally?{if?(jedis?!=?null)?{returnResource(jedis);}} }總結(jié)
以上是生活随笔為你收集整理的厉害了,如何通过双 key 来解决缓存并发问题?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 谈谈 ForkJoin 框架的设计与实现
- 下一篇: 原来不只是fastjson,这个你每天都