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