1. 程式人生 > >快取穿透、快取併發、快取失效說明及對應處理策略

快取穿透、快取併發、快取失效說明及對應處理策略

我們在用快取的時候,不管是Redis或者Memcached,基本上會通用遇到以下三個問題:

  • 快取穿透
  • 快取併發
  • 快取失效

一、快取穿透

快取穿透是指查詢一個一定不存在的資料,由於快取是不命中時被動寫的,並且出於容錯考慮,如果從儲存層查不到資料則不寫入快取,這將導致這個存在的資料每次請求都要到儲存層去查詢,失去了快取的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。


Paste_Image.png
Paste_Image.png
Paste_Image.png

注:
上面三個圖會有什麼問題呢?

我們在專案中使用快取通常都是先檢查快取中是否存在,如果存在直接返回快取內容,如果不存在就直接查詢資料庫然後再快取查詢結果返回。這個時候如果我們查詢的某一個數據在快取中一直不存在,就會造成每一次請求都查詢DB,這樣快取就失去了意義,在流量大時,可能DB就掛掉了。

那這種問題有什麼好辦法解決呢?

要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
有一個比較巧妙的作法是,可以將這個不存在的key預先設定一個值。
比如,"key" , “&&”。
在返回這個&&值的時候,我們的應用就可以認為這是不存在的key,那我們的應用就可以決定是否繼續等待繼續訪問,還是放棄掉這次操作。如果繼續等待訪問,過一個時間輪詢點後,再次請求這個key,如果取到的值不再是&&,則可以認為這時候key有值了,從而避免了透傳到資料庫,從而把大量的類似請求擋在了快取之中。

解決方案:

1,如果查詢資料庫也為空,直接設定一個預設值存放到快取,這樣第二次到緩衝中獲取就有值了,而不會繼續訪問資料庫,這種辦法最簡單粗暴。

2,根據快取資料Key的規則。例如我們公司是做機頂盒的,快取資料以Mac為Key,Mac是有規則,如果不符合規則就過濾掉,這樣可以過濾一部分查詢。在做快取規劃的時候,Key有一定規則的話,可以採取這種辦法。這種辦法只能緩解一部分的壓力,過濾和系統無關的查詢,但是無法根治。

3,採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的BitSet中,不存在的資料將會被攔截掉,從而避免了對底層儲存系統的查詢壓力。關於布隆過濾器,詳情檢視:基於BitSet的布隆過濾器(Bloom Filter) 

大併發的快取穿透會導致快取雪崩。



二、快取併發(快取雪崩)

快取雪崩可能是因為資料未載入到快取中,或者快取同一時間大面積的失效,從而導致所有請求都去查

資料庫,導致資料庫CPU和記憶體負載過高,甚至宕機

有時候如果網站併發訪問高,一個快取如果失效,可能出現多個程序同時查詢DB,同時設定快取的情況,如果併發確實很大,這也可能造成DB壓力過大,還有快取頻繁更新的問題。

我現在的想法是對快取查詢加鎖,如果KEY不存在,就加鎖,然後查DB入快取,然後解鎖;其他程序如果發現有鎖就等待,然後等解鎖後返回資料或者進入DB查詢。

這種情況和剛才說的預先設定值問題有些類似,只不過利用鎖的方式,會造成部分請求等待。

解決方案:

1,採用加鎖計數,或者使用合理的佇列數量來避免快取失效時對資料庫造成太大的壓力。這種辦法雖然能緩解資料庫的壓力,但是同時又降低了系統的吞吐量。

2,分析使用者行為,儘量讓失效時間點均勻分佈。避免快取雪崩的出現。

3,如果是因為某臺快取伺服器宕機,可以考慮做主備,比如:Redis主備,但是雙快取涉及到更新事務的問題,update可能讀到髒資料,需要好好解決。


三、快取失效

一般在開發的時候,我們總喜歡給快取設定一個有效期,但是,當大量的快取在同一時間失效會造成下一次訪問這些的資料都去同時讀取資料庫

引起這個問題的主要原因還是高併發的時候,平時我們設定一個快取的過期時間時,可能有一些會設定1分鐘啊,5分鐘這些,併發很高時可能會出在某一個時間同時生成了很多的快取,並且過期時間都一樣,這個時候就可能引發一當過期時間到後,這些快取同時失效,請求全部轉發到DB,DB可能會壓力過重。

解決方案:


其中的一個簡單方案就時講快取失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個快取的過期時間的重複率就會降低,就很難引發集體失效的事件。

我們討論的第二個問題時針對同一個快取,第三個問題時針對很多快取。

四、快取擊穿


  對於一些設定了過期時間的key,如果這些key可能會在某些時間點被超高併發地訪問,是一種非常“熱點”的資料。這個時候,需要考慮一個問題:快取被“擊穿”的問題,這個和快取雪崩的區別在於這裡針對某一key快取,前者則是很多key

快取在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的併發請求過來,這些請求發現快取過期一般都會從後端DB載入資料並回設到快取,這個時候大併發的請求可能會瞬間把後端DB壓垮。

解決方案:

1.使用互斥鎖(mutex key)

業界比較常用的做法,是使用mutex。簡單地來說,就是在快取失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用快取工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設快取;否則,就重試整個get快取的方法。

SETNX,是「SET if Not eXists」的縮寫,也就是隻有不存在的時候才設定,可以利用它來實現鎖的效果。在redis2.6.1之前版本未實現setnx的過期時間,所以這裡給出兩種版本程式碼參考:

  1. //2.6.1前單機版本鎖
  2. String get(String key) {    
  3.    String value = redis.get(key);    
  4.    if (value  == null) {    
  5.     if (redis.setnx(key_mutex, "1")) {    
  6.         // 3 min timeout to avoid mutex holder crash  
  7.         redis.expire(key_mutex, 3 * 60)    
  8.         value = db.get(key);    
  9.         redis.set(key, value);    
  10.         redis.delete(key_mutex);    
  11.     } else {    
  12.         //其他執行緒休息50毫秒後重試  
  13.         Thread.sleep(50);    
  14.         get(key);    
  15.     }    
  16.   }    
  17. }  
最新版本程式碼:
  1. public String get(key) {  
  2.       String value = redis.get(key);  
  3.       if (value == null) { //代表快取值過期
  4.           //設定3min的超時,防止del操作失敗的時候,下次快取過期一直不能load db
  5.           if (redis.setnx(key_mutex, 13 * 60) == 1) {  //代表設定成功
  6.                value = db.get(key);  
  7.                       redis.set(key, value, expire_secs);  
  8.                       redis.del(key_mutex);  
  9.               } else {  //這個時候代表同時候的其他執行緒已經load db並回設到快取了,這時候重試獲取快取值即可
  10.                       sleep(50);  
  11.                       get(key);  //重試
  12.               }  
  13.           } else {  
  14.               return value;        
  15.           }  
  16.  }  
memcache程式碼:
  1. if (memcache.get(key) == null) {    
  2.     // 3 min timeout to avoid mutex holder crash  
  3.     if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {    
  4.         value = db.get(key);    
  5.         memcache.set(key, value);    
  6.         memcache.delete(key_mutex);    
  7.     } else {    
  8.         sleep(50);    
  9.         retry();    
  10.     }    
  11. }   

2. "提前"使用互斥鎖(mutex key):

在value內部設定1個超時值(timeout1), timeout1比實際的memcache timeout(timeout2)小。當從cache讀取到timeout1發現它已經過期時候,馬上延長timeout1並重新設定到cache。然後再從資料庫載入資料並設定到cache中。虛擬碼如下:

  1. v = memcache.get(key);    
  2. if (v == null) {    
  3.     if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {    
  4.         value = db.get(key);    
  5.         memcache.set(key, value);    
  6.         memcache.delete(key_mutex);    
  7.     } else {    
  8.         sleep(50);    
  9.         retry();    
  10.     }    
  11. else {    
  12.     if (v.timeout <= now()) {    
  13.         if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {    
  14.             // extend the timeout for other threads  
  15.             v.timeout += 3 * 60 * 1000;    
  16.             memcache.set(key, v, KEY_TIMEOUT * 2);    
  17.             // load the latest value from db  
  18.             v = db.get(key);    
  19.             v.timeout = KEY_TIMEOUT;    
  20.             memcache.set(key, value, KEY_TIMEOUT * 2);    
  21.             memcache.delete(key_mutex);    
  22.         } else {    
  23.             sleep(50);    
  24.             retry();    
  25.         }    
  26.     }    
  27. }   

3. "永遠不過期":  

這裡的“永遠不過期”包含兩層意思:

(1) 從redis上看,確實沒有設定過期時間,這就保證了,不會出現熱點key過期問題,也就是“物理”不過期。

(2) 從功能上看,如果不過期,那不就成靜態的了嗎?所以我們把過期時間存在key對應的value裡,如果發現要過期了,通過一個後臺的非同步執行緒進行快取的構建,也就是“邏輯”過期

        從實戰看,這種方法對於效能非常友好,唯一不足的就是構建快取時候,其餘執行緒(非構建快取的執行緒)可能訪問的是老資料,但是對於一般的網際網路功能來說這個還是可以忍受。

  1. String get(final String key) {    
  2.         V v = redis.get(key);    
  3.         String value = v.getValue();    
  4.         long timeout = v.getTimeout();    
  5.         if (v.timeout <= System.currentTimeMillis()) {    
  6.             // 非同步更新後臺異常執行  
  7.             threadPool.execute(new Runnable() {    
  8.                 publicvoid run() {    
  9.                     String keyMutex = "mutex:" + key;    
  10.                     if (redis.setnx(keyMutex, "1")) {    
  11.                         // 3 min timeout to avoid mutex holder crash  
  12.                         redis.expire(keyMutex, 3 * 60);    
  13.                         String dbValue = db.get(key);    
  14.                         redis.set(key, dbValue);    
  15.                         redis.delete(keyMutex);    
  16.                     }    
  17.                 }    
  18.             });    
  19.         }    
  20.         return value;    
  21. }  

4. 資源保護:

採用netflix的hystrix,可以做資源的隔離保護主執行緒池,如果把這個應用到快取的構建也未嘗不可。

四種解決方案:沒有最佳只有最合適

解決方案 優點 缺點
簡單分散式互斥鎖(mutex key)

 1. 思路簡單

2. 保證一致性

1. 程式碼複雜度增大

2. 存在死鎖的風險

3. 存線上程池阻塞的風險

“提前”使用互斥鎖  1. 保證一致性 同上 
不過期(本文)

1. 非同步構建快取,不會阻塞執行緒池

1. 不保證一致性。

2. 程式碼複雜度增大(每個value都要維護一個timekey)。

3. 佔用一定的記憶體空間(每個value都要維護一個timekey)。

資源隔離元件hystrix(本文)

1. hystrix技術成熟,有效保證後端。

2. hystrix監控強大。

1. 部分訪問存在降級策略。


四種方案來源網路,詳文請連結:http://carlosfu.iteye.com/blog/2269687?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io


總結來看:

1、快取穿透:查詢一個必然不存在的資料。比如文章表,查詢一個不存在的id,每次都會訪問DB,如果有人惡意破壞,很可能直接對DB造成影響。

2、快取失效:如果快取集中在一段時間內失效,DB的壓力凸顯。這個沒有完美解決辦法,但可以分析使用者行為,儘量讓失效時間點均勻分佈。
當發生大量的快取穿透,例如對某個失效的快取的大併發訪問就造成了快取雪崩。

五、大家提問彙總

1、問題1:
如何解決DB和快取一致性問題?
答:當修改了資料庫後,有沒有及時修改快取。這種問題,以前有過實踐,修改資料庫成功,而修改快取失敗的情況,最主要就是快取伺服器掛了。而因為網路問題引起的沒有及時更新,可以通過重試機制來解決。而快取伺服器掛了,請求首先自然也就無法到達,從而直接訪問到資料庫。那麼我們在修改資料庫後,無法修改快取,這時候可以將這條資料放到資料庫中,同時啟動一個非同步任務定時去檢測快取伺服器是否連線成功,一旦連線成功則從資料庫中按順序取出修改資料,依次進行快取最新值的修改。

2、問題2:
問下快取穿透那塊!例如,一個使用者查詢文章,通過ID查詢,按照之前說的,是將快取的KEY預先設定一個值,,如果通過ID插過來,發現是預先設定的一個值,比如說是“&&”,那之後的繼續等待訪問是什麼意思,這個ID什麼時候會真正被附上使用者所需要的值呢?
答:我剛說的主要是咱們常用的後面配置,前臺獲取的場景。前臺無法獲取相應的key,則等待,或者放棄。當在後臺配置介面上配置了相關key和value之後,那麼以前的key &&也自然會被替換掉。你說的那種情況,自然也應該會有一個程序會在某一個時刻,在快取中設定這個ID,再有新的請求到達的時候,就會獲取到最新的ID和value。

3、問題3:
其實用redis的話,那天看到一個不錯的例子,雙key,有一個當時生成的一個附屬key來標識資料修改到期時間,然後快到的時候去重新載入資料,如果覺得key多可以把結束時間放到主key中,附屬key起到鎖的功能。
答:這種方案,之前我們實踐過。這種方案會產生雙份資料,而且需要同時控制附屬key與key之間的關係,操作上有一定複雜度。

4、問題4:
多級快取是什麼概念呢?
答:多級快取就像我今天之前給大家發的文章裡面提到了,將ehcache與redis做二級快取,就像我之前寫的文章 http://www.jianshu.com/p/2cd6ad416a5a 提到過的。但同樣會存在一致性問題,如果我們需要強一致性的話,快取與資料庫同步是會存在時間差的,所以我們在具體開發的過程中,一定要根據場景來具體分析,二級快取更多的解決是,快取穿透與程式的健壯性,當集中式快取出現問題的時候,我們的應用能夠繼續執行。

相關推薦

快取穿透快取併發快取失效說明對應處理策略

我們在用快取的時候,不管是Redis或者Memcached,基本上會通用遇到以下三個問題: 快取穿透快取併發快取失效 一、快取穿透 快取穿透是指查詢一個一定不存在的資料,由於快取是不命中時被動寫的,並且出於容錯考慮,如果從儲存層查不到資料則不寫入快取,這將導致這個存在

【陌上軒客】技術領域:涉獵JavaGoPythonGroovy 等語言,高效能併發、高可用非同步與訊息中介軟體、快取與資料庫分散式與微服務容器和自動化等領域; 興趣愛好:籃球,騎行,讀書,發呆; 職業規劃:勵志成為一名出色的伺服器端系統架構師。

陌上軒客 技術領域:涉獵Java、Go、Python、Groovy 等語言,高效能、高併發、高可用、非同步與訊息中介軟體、快取與資料庫、分散式與微服務、容器和自動化等領域; 興趣愛好:籃球,騎行,讀書,發呆; 職業...

大型網際網路公司必備的架構技術棧——分散式架構高可擴充套件高效能併發效能優化Spring bootRedisActiveMQNginxMycatNettyJvm大型分散式專案實戰

效能優化 JVM調優 Java程式效能優化 Tomcat Mysql Spring IOC Spring AOP Spring MVC Spring 5新特性 Mybatis 分散式架構 架構核心服務層技術 架構關鍵技術設施 分散式訊息通訊 非同

Java高架構師分散式架構高可擴充套件高效能併發效能優化Spring bootRedisActiveMQNginxMycatNettyJvm大型分散式專案實戰學習架構師之路

工作1-5年開發經驗,當你們提出漲工資的時候,或者要offer的時候底氣怎麼樣,是不是底氣十足,不給漲工資就辭職,是不是有自信提出來主管、或者是專案經理都能同意,他們相當設法把你留住。如果這樣你才是成功。什麼技術都沒有何談工資! 給你分析一下這些技術,給大家羅列一些技術,看

什麼是分散式併發叢集負載均衡高可用

網圖 是指將不同的業務分佈在不同的地方(應用伺服器)。 叢集cluster: 一群機器的集合。 負載均衡(叢集):(Load balance cluster, LBC) (負載均衡器)根據規則(平均)分配各請求到一個叢集各個機器。 高可用(叢集):(H

電商例項業務併發網站併發解決辦法

電商例項、業務併發、網站併發及解決方法 一、怎麼防止多使用者同一時間搶購同一商品,防止高併發同時下單同一商品   最近在做搶購系統,但頭疼的是,在多使用者高併發的情況下經常會庫存出現問題。排查到,在同一時間內多使用者同時下單導致查詢和插入不同步了,而查詢中跟插入又有時

系統性能指標:廣義併發狹義併發吞吐量平均響應時間TPSIOPS

最近在學習jmeter壓力測試 整理了如下衡量效能的指標,記錄如下: 1、廣義併發:廣義的併發實際上是在一個時間內操作事務的虛擬使用者,是存在。 對地鐵這個系統而言,每個時間都有新來的人,也有走的人,大家做的事情基本都相同,乘地鐵。假定某個時刻地鐵大廳中有10000人,檢

java高階,併發高可用高效能分散式負載均衡

1、億級流量電商網站的商品詳情頁系統架構 面臨難題:對於每天上億流量,擁有上億頁面的大型電商網站來說,能夠支撐高併發訪問,同時能夠秒級讓最新模板生效的商品詳情頁系統的架構是

快取穿透快取併發快取失效之思路變遷

我們在用快取的時候,不管是Redis或者Memcached,基本上會通用遇到以下三個問題: 快取穿透 快取併發 快取失效 一、快取穿透 注: 上面三個圖會有什麼問題呢? 我們在專案中使用快取通常都是先檢查快取中是否存在,如果存在直接返回快取內容,如果不存在就直接查詢資料庫然後再

快取失效穿透併發雪崩問題解決方法

1 快取失效   引起這個原因的主要因素是高併發下,一般設定一個快取的過期時間時,併發很高時可能會出在某一個時間同時生成很多的快取,並且過期時間在同一時刻,這個時候就可能引發——當過期時間到後,這些快取同時失效,請求全部轉發到DB,DB可能會壓力過重。  

快取失效快取穿透快取併發及其解決方案

1. 快取失效 問題描述:          引起這個原因的主要因素是高併發下,我們一般設定一個快取的過期時間時,可能有一些會設定5分鐘啊,10分鐘這些;併發很高時可能會出在某一個時間同時生成了很多的快取,並且過期時間在同一時刻,這個時候就可能引發——當過期時間到後,這些

關於快取穿透快取併發快取失效的解決方案

一、快取穿透 我們在專案中使用快取通常都是APP先檢查快取中是否存在,如果存在直接返回快取內容,如果不存在就直接查詢資料庫然後再快取查詢結果返回。這個時候如果我們查詢的某一個數據在快取中一直不存在,就會造成每一次請求都查詢DB,這樣快取就失去了意義,在流量大時,可能DB

如何解決常見的快取穿透併發失效問題?

在之前的一篇快取穿透、快取併發、快取失效之思路變遷文章中介紹了關於快取穿透、併發的一些常用思路,但是個人感覺文章中沒有明確一些思路的使用場景,本文將繼續深化與大家共同探討,同時也非常感謝這段時間給我提寶貴建議的朋友們(注:本文中提到的快取可以理解為Redis)。  快取穿

快取穿透快取併發快取失效

一、快取穿透 我們在專案中使用快取通常都是APP先檢查快取中是否存在,如果存在直接返回快取內容,如果不存在就直接查詢資料庫然後再快取查詢結果返回。這個時候如果我們查詢的某一個數據在快取中一直不存在,就會造成每一次請求都查詢DB,這樣快取就失去了意義,在流量大時,可能DB就掛

快取穿透快取併發熱點快取解決方案

快取穿透、快取併發、熱點快取 一、前言 在之前的一篇快取穿透、快取併發、快取失效之思路變遷文章中介紹了關於快取穿透、併發的一些常用思路,但是個人感覺文章中沒有明確一些思路的使用場景,本文繼續將繼續深化與大家共同探討,同時也非常感謝這段時間給我提寶貴建議的朋友們。 說明:本文中提到

Redis快取穿透快取雪崩redis併發問題分析

把redis作為快取使用已經是司空見慣,但是使用redis後也可能會碰到一系列的問題,尤其是資料量很大的時候,經典的幾個問題如下:(一)快取和資料庫間資料一致性問題分散式環境下(單機就不用說了)非常容易

Redis學習總結(10)——快取雪崩快取穿透快取併發快取預熱快取演算法的概念解決思路總結

一、快取雪崩 概念: 可能是因為資料未載入到快取中,或者快取同一時間大面積的失效,從而導致所有請求都去查資料庫,導致資料庫CPU和記憶體負載過高,甚至宕機。 解決思路: 1.1、加鎖計數(即限制併發的數量,可以用semphore)或者起一定數量的佇列來避免快取失效時大

快取穿透快取併發熱點快取之最佳招式

快取穿透與併發方案 • 如何解決穿透 • 如何解決併發   當併發較高的時候,其實一般不建議使用快取過期這個策略的,更希望快取一直存在,通過後臺系統來更新快取系統中的資料達到資料的一致性目的,可能有人會質疑,如果快取系統掛了怎麼辦,這樣資料

快取失效快取穿透問題解決方案

快取失效:   引起這個原因的主要因素是高併發下,我們一般設定一個快取的過期時間時,可能有一些會設定5分鐘啊,10分鐘這些;併發很高時可能會出在某一個時間同時生成了很多的快取,並且過期時間在同一時刻,這個時候就可能引發——當過期時間到後,這些快取同時失效,請求全部轉發到DB,

Redis快取穿透快取併發快取雪崩

一、快取穿透 1.產生原因: 查詢方式是先查詢快取、如果快取不存在則查詢資料庫、將查詢的結果回寫到快取、穿透的概念是快取不存在的情況下查詢資料庫、高併發應用下可能造成資料庫壓力過大 2.解決方案: 2.1:將對應的key為空的值也快取起來,減少資料庫的查詢 2.2: