雲棲號資訊:【點擊查看更多行業資訊】
在這裏您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!
我們在使用緩存的時候,不管Redis或者是Memcached,基本上都會遇到以下3個問題:緩存穿透、緩存併發、緩存集中失效。這篇文章主要針對【緩存併發】問題展開討論,並給出具體的解決方案。
1.什麼是緩存併發?
在高併發的訪問下,當某個緩存處於過期失效的時間點時,極有可能出現多個進程同時查詢該緩存(該緩存是業務場景中非常 "熱點" 的數據,比如首頁的緩存數據)。因爲查詢DB並重新緩存需要一定的時間,而瞬時併發非常高,如果此時緩存失效了,這些併發請求都會直接訪問DB,從而導致DB服務器的CPU或者內存負載過高,服務能力下降甚至宕機,此問題即緩存併發問題。
緩存併發問題在微服務架構下凸顯更加嚴重,比如某個基礎服務A因爲上述問題出現不可用,進而導致依賴A服務的B、C服務也不可用,而B服務的不可用又導致服務E、F不可用,不可用的服務就像滾雪球一樣越滾越大,最終導致系統出現嚴重故障,此現象我們稱之爲雪崩效應。
注意緩存併發和緩存集中失效的區別在於:緩存併發指的是某一個熱點key的失效,而緩存集中失效則是一批key同時失效,兩者都可能導致雪崩問題。
2.如何解決?
針對該問題,存在以下三種解決方案:
1.加鎖:在緩存失效後,通過加鎖的方式只允許一個線程查詢數據和寫緩存,其他線程如果發現有鎖就等待,等解鎖後再返回數據。該方案會造成部分請求等待。
2.二級緩存:A1爲原始緩存,A2爲拷貝緩存。A1失效時,可以訪問A2,其中A1的緩存失效時間設置爲短期(比如5min),A2的緩存失效時間設置爲長期(比如1天)。如果緩存value很大,此方案的緩存空間利用率低。關注公衆號互聯網架構師,回覆關鍵字2T,獲取最新架構視頻
3.雙key:思路和方案2類似,不同的是雙key分別緩存過期時間(key-time)和緩存數據(key-data),其中(key-time)的緩存失效時間設置爲短期(比如5min),(key-data)的緩存失效時間設置爲長期(比如1天)。當第一個線程發現 key-time 過期不存在時,則先更新key-time,然後去查詢數據庫並更新key-data 的值;當其他線程來獲取數據時,雖然第一個線程還沒有從數據庫查詢完畢並更新緩存,但發現key-time存在,會直接讀取緩存的舊數據返回。和二級緩存的方案對比,該方案的緩存空間利用率高。
3.雙key方案的示例代碼
- 寫緩存的示例代碼
1public static boolean set(String key, String value, int seconds) {
2 Jedis jedis = null;
3 try {
4 jedis = jedisPool.getResource();
5 if (seconds > 0){
6 // 添加數據緩存,緩存有效時間 = 真實時間 + 1 天
7 jedis.set(key, seconds + 60 * 60 * 24, value);
8
9 // 添加過期時間緩存,緩存有效時間 = 真實時間
10 jedis.set("lock_" + key, seconds, System.currentTimeMillis() + "");
11 } else {
12 jedis.set(key, value);
13 jedis.set("lock_" + key, System.currentTimeMillis() + "");
14 }
15
16 return true;
17 } catch (JedisException e) {
18 if (jedis != null) {
19 returnBrokenResource(jedis);
20 jedis = null;
21 }
22 throw e;
23 } finally {
24 if (jedis != null) {
25 returnResource(jedis);
26 }
27 }
28}
2. 讀緩存的示例代碼
1public static String get(String key) {
2 Jedis jedis = null;
3 try {
4 jedis = jedisPool.getResource();
5
6 // 緩存過期 && 獲取鎖成功,setnx:原子操作
7 if (jedis.setnx("lock_" + key, System.currentTimeMillis() + "") == 1) {
8 /**
9 * 將鎖的失效時間設爲60s,在60s內若查詢數據庫成功,則更新鎖的失效時間=緩存時間
10 * 如果60s內出現異常,則60s後第一個請求又會去訪問數據庫
11 * 返回null表示沒有查詢到數據庫,外層代碼會通過數據庫獲取數據並設置緩存
12 */
13 jedis.expire("lock_" + key, 60);
14 return null;
15 } else{
16 // 緩存未過期或者緩存過期但獲取鎖失敗, 則返回舊數據
17 return jedis.get(key);
18 }
19 } catch (JedisException e) {
20 if (jedis != null) {
21 returnBrokenResource(jedis);
22 jedis = null;
23 }
24 throw e;
25 } finally {
26 if (jedis != null) {
27 returnResource(jedis);
28 }
29 }
30}
【雲棲號在線課堂】每天都有產品技術專家分享!
課程地址:https://yqh.aliyun.com/zhibo立即加入社羣,與專家面對面,及時瞭解課程最新動態!
【雲棲號在線課堂 社羣】https://c.tb.cn/F3.Z8gvnK
原文發佈時間:2020-07-17
本文作者:互聯網架構師
本文來自:“互聯網架構師”,瞭解相關信息可以關注“互聯網架構師”