技術提升(1) -- 緩存專題
大將生來膽氣豪,腰橫秋水雁翎刀,
風吹和鼓山河動,電閃旌旗日月高,
天上麒麟原有種,穴中螻蟻豈能逃,
太平待詔歸來日,朕與將軍解戰(zhàn)袍。
最近由于公司要求考技術經(jīng)理考試,所以一直在刷視頻。本來就打算隨便刷刷,像以前的技能考試似的,刷過就得。但是真正開始看的時候發(fā)現(xiàn),這次技術經(jīng)理考試涉及的課程真是干貨滿滿,有些東西我雖然有涉獵,但是沒有這次課程這么系統(tǒng)。甚至有好多東西,我自己干脆就沒有關注過。學習過程中我不禁想到,我邊學習邊記筆記,這不整理一下就是一個很好的技能提升系列么。說干就干??!
今天第一部分我學習的是緩存專題,緩存這東西肯定大家都知道,但是完整的涉及緩存方方面面的問題,想必大家肯定考慮的沒有那么全面。來吧,讓我們一起study一下。
1. 緩存的基本使用方式
1)? 應用系統(tǒng)如何使用緩存讀寫數(shù)據(jù)

查詢過程:
a. 首先從緩存中獲取數(shù)據(jù),如果獲取成功,則返回數(shù)據(jù)
b. 如果獲取失敗,則從數(shù)據(jù)庫中獲取
c. 從數(shù)據(jù)庫獲取完成后,將數(shù)據(jù)重新寫入緩存中
更新過程(也有可能這兩個過程順序相反,都存在相應的問題,后續(xù)會介紹):
a. 更新數(shù)據(jù)庫
b. 更新緩存
2)? 如何應對緩存空間不足的問題
緩存系統(tǒng)大多使用內(nèi)存進行存儲,內(nèi)存相對于硬盤空間來說,成本更高,空間也有限,在空間不足的情況下,面對越來越多的數(shù)據(jù),如何提升命中率是一個非常重要的問題。
究其本質(zhì),主要是以下兩個問題:
i. 緩存中的數(shù)據(jù)存儲多久?
ii. 如果空間已滿,再來數(shù)據(jù)需要如何處理?
解決方案:
i. 緩存過期時間
設置有緩存過期時間的數(shù)據(jù),在過期時間到達時,將被緩存自動清除
ii. 緩存淘汰策略
就是字面意思,當緩存已滿,而且緩存內(nèi)的數(shù)據(jù)都沒有達到過期時間的時候,采用哪種策略刪除緩存。
常見的緩存淘汰策略算法有很多,這里介紹兩種比較常用而且好用的策略:
LRU(Least recently used,最近最少使用)
LRU算法會將近期最不會訪問的數(shù)據(jù)(即長時間沒有被使用的數(shù)據(jù))淘汰掉。
LFU(Least Frequently Used 最不經(jīng)常使用算法)
LFU算法會將一段時間內(nèi)使用次數(shù)最少的數(shù)據(jù)淘汰掉。
具體這兩種算法的實現(xiàn)方式,大家可以參考我CSDN的這篇博文,主要是介紹XXL-JOB中執(zhí)行器路由選擇策略,
https://blog.csdn.net/whq789456/article/details/121282132
其中就有介紹這兩種算法具體的實現(xiàn)方式。

3) 查詢機制與更新機制帶來的挑戰(zhàn)
數(shù)據(jù)查詢流程如下:

基于以上查詢過程的流程展示,我們可以看到查詢過程中可能存在兩個比較嚴重的問題:
如果短時間內(nèi)出現(xiàn)大量未命中緩存的請求,將發(fā)生什么?
如果未命中緩存的請求,在數(shù)據(jù)庫中同樣無法查找到數(shù)據(jù),又會怎樣?
具體這兩個問題怎么解決呢?我們繼續(xù)看后面的介紹

2.?如何應對緩存讀取機制帶來的挑戰(zhàn)
1) 緩存失效的場景:
a. 緩存穿透
查詢的是緩存中不存在,同時數(shù)據(jù)庫中也不存在的數(shù)據(jù),即所有查詢緩存失效,那么所有的查詢將會集中訪問數(shù)據(jù)庫,極大的增大數(shù)據(jù)庫壓力,降低訪問效率,增大了時延,甚至導致數(shù)據(jù)庫崩潰。
這種情況對于系統(tǒng)不一定是威脅,但是對系統(tǒng)有極大的安全影響。
b. 緩存擊穿(熱點數(shù)據(jù),新浪微博某個熱點過去,但是大批量請求來)
緩存擊穿是指緩存中沒有但數(shù)據(jù)庫中有的數(shù)據(jù)(一般是緩存時間到期),這時由于并發(fā)用戶特別多,同時讀緩存沒讀到數(shù)據(jù),又同時去數(shù)據(jù)庫去取數(shù)據(jù),引起數(shù)據(jù)庫壓力瞬間增大,造成過大壓力。
eg. 這種場景可以舉一個例子,這些數(shù)據(jù)常常被稱為“熱點數(shù)據(jù)”,比如新浪微博中某一個明星公布戀情的消息,這條消息在緩存中設置了過期時間,如果這條數(shù)據(jù)在緩存中已經(jīng)過期,但是仍然有大量客戶訪問,就會出現(xiàn)這種情況。
c. 緩存雪崩
緩存雪崩是指緩存中數(shù)據(jù)大批量到過期時間或數(shù)據(jù)尚未加載到緩存中,而查詢數(shù)據(jù)量巨大,引起數(shù)據(jù)庫壓力過大甚至down機。和緩存擊穿不同的是,緩存擊穿指并發(fā)查同一條數(shù)據(jù),緩存雪崩是不同數(shù)據(jù)都過期了,很多數(shù)據(jù)都查不到從而去查數(shù)據(jù)庫。
2) 解決方案:
i. 緩存穿透:
查詢的是緩存中不存在,同時數(shù)據(jù)庫中也不存在的數(shù)據(jù)
a. 空值緩存
對于某條在數(shù)據(jù)庫中也未查詢到值的key,也將其放到緩存中,只不過將對應的值置為空。此時再訪問,將會從緩存中取出空值并返回。
缺陷:
關注點一:這種情況下,肯定會占用緩存空間? 《—??我們可以設置較短過期時間
關注點二:不一致性? 《—? ?我們可以先更新數(shù)據(jù)庫后刪除緩存
b. 布隆過濾器
該過濾器由一個位數(shù)組(斷句是 一個,位數(shù)組,也就是數(shù)組中只存0和1)和一系列哈希函數(shù)組成,能夠快速判斷出一個元素是否存在于元素池中。布隆過濾器工作在應用查詢緩存前,當應用有查詢請求時,先去過濾器查看該數(shù)據(jù)是否存在,如果不存在,說明數(shù)據(jù)庫中沒有該數(shù)據(jù),直接返回即可;若存在,再去查詢緩存,緩存中沒有的話才查詢數(shù)據(jù)庫,極大地減輕了數(shù)據(jù)庫的壓力。
可以使用Google的Guava來實現(xiàn)布隆過濾器,該過濾器相比于空值緩存代碼維護更加復雜,但占用緩存的空間較少。適合數(shù)據(jù)命中不高、數(shù)據(jù)相對固定的場景
ii. 緩存擊穿:
應用大量請求緩存中不存在但數(shù)據(jù)庫中存在的熱點數(shù)據(jù)
預防措施:
預防擊穿需要選用合適的緩存淘汰策略,采用LRU(最近最少使用),LFU緩存淘汰算法,來決定哪些緩存該淘汰,能很大程度地在緩存中保留“熱點”數(shù)據(jù),防止緩存擊穿情況的發(fā)生。常見的緩存如:redis、ignite、memcache等均提供了LRU 緩存刪除策略的選擇。
應對方案:采用限流機制
通過加鎖(分布式鎖,本地同步鎖等)的方式保證同一時間只有一個線程能夠拿到鎖并去訪問數(shù)據(jù)庫,其他線程如果沒有拿到鎖則重新訪問緩存看數(shù)據(jù)是否存在,如果沒有存在則繼續(xù)爭搶鎖,直到搶到鎖或查詢到結(jié)果為止。
iii.緩存雪崩:
緩存雪崩是指緩存中數(shù)據(jù)大批量到過期時間,同時伴隨大量的查詢請求這些數(shù)據(jù)(和緩存擊穿不同的是,緩存擊穿指并發(fā)查同一條數(shù)據(jù))
預防措施:差別失效時間
給不同的緩存設置不同的失效時間,可以使用在一定范圍內(nèi)的隨機數(shù),這一也能夠在一定程度上避免所有緩存同時失效這一情況的發(fā)生,降低緩存雪崩發(fā)生的概率。
應對方案:限流機制
緩存設計人員可以通過使用隊列的方式來保證同一時間最多只有固定數(shù)目的查詢請求線程來訪問數(shù)據(jù)庫和更新緩存,其他查詢請求線程需要在隊列中等待。
iv. 特殊的緩存雪崩模式:
新啟用緩存引起緩存雪崩問題
緩存雪崩:數(shù)據(jù)尚未加載到緩存中,同時伴隨大量查詢請求(常見于緩存初始后或重啟后),導致請求直接訪問數(shù)據(jù)庫
解決方案:緩存預熱
緩存啟動后在對外提供服務前,從后端存儲介質(zhì)中導入部分應用使用頻率較高的數(shù)據(jù),避免突然出現(xiàn)大量請求直接訪問后端存儲介質(zhì)。
3.??如何應對緩存更新機制帶來的挑戰(zhàn)
前兩種策略就是直接更新緩存,區(qū)別在于先更新數(shù)據(jù)庫,還是先更新緩存
1)先更新緩存,再更新數(shù)據(jù)庫
2)先更新數(shù)據(jù)庫,再更新緩存

eg. 這里以第二種策略為例,先更新數(shù)據(jù)庫,然后更新緩存的方式,介紹這種策略的缺陷。
來了兩個寫線程,線程A首先完成了寫數(shù)據(jù)庫的操作(操作1,2),在該線程沒有完成更新緩存操作之前(操作7,8);線程B已經(jīng)完成了更新數(shù)據(jù)庫以及緩存的操作(操作3,4,5,6)??梢钥吹阶罱K的處理結(jié)果數(shù)據(jù)庫中為線程B的更新值,緩存為線程A的更新值,導致緩存不一致(這個不一致的緩存最起碼得保存到這個緩存過期的時候)。
當然,先更新緩存,再更新數(shù)據(jù)庫與這種策略差不多,我這里就不贅述了。
下面處理緩存的方式有了變化,不是采用更新緩存的方式,而是采用刪除緩存的策略。
3)先刪除緩存再更新數(shù)據(jù)庫
第三種策略先刪除緩存再更新數(shù)據(jù)庫解決了策略1和策略2寫-寫場景中的緩存不一致問題

可以看到采用這種策略,前面一二兩種策略,沒有解決 寫-寫 這種場景下,緩存不一致的問題。但是采用第三種策略最終結(jié)果只有數(shù)據(jù)庫中會更新數(shù)據(jù),緩存會被刪除。也就是說最終結(jié)果數(shù)據(jù)庫更新,緩存清空,這樣也就不存在緩存不一致的情況了,大不了下次讀取的時候從數(shù)據(jù)庫中讀取。但是第三種策略也不是萬能的,在下面這種場景下也會出現(xiàn)問題:
異常場景:

可以看到在 寫-讀 這種場景下,第三種策略就不能保證緩存一致性了。此時由于寫在前,會刪除緩存,但是此時寫線程還沒有完成數(shù)據(jù)庫的更新;這時候讀線程讀取的時候由于緩存已經(jīng)清空,會嘗試從數(shù)據(jù)庫中加載緩存,此時由于數(shù)據(jù)庫未更新,導致最終緩存為舊數(shù)據(jù),而寫線程完成更新數(shù)據(jù)庫后,數(shù)據(jù)庫中的數(shù)據(jù)為新數(shù)據(jù)。最終導致緩存不一致。
4)先更新數(shù)據(jù)庫再刪除緩存
第四種策略先更新數(shù)據(jù)庫再刪除緩存部分解決了策略3的數(shù)據(jù)不一致問題

為了解決第三種策略的不足之處,第四種策略變更了順序,采用先更新數(shù)據(jù)庫,后刪除緩存的策略。這種形式可以一定程度上解決第三種策略的缺陷。具體分析如下:
在寫-讀的場景下,寫線程更新完數(shù)據(jù)庫還沒有刪除緩存的情況下。
(1)如果在上面圖中的第三步如果獲取該緩存沒有獲取到,此時讀線程會去獲取數(shù)據(jù)庫中的數(shù)據(jù),并將數(shù)據(jù)加載到緩存,此時由于寫線程已經(jīng)更新了數(shù)據(jù)庫,所以讀線程加載到緩存中的數(shù)據(jù)也是最新的。但是隨后寫線程又會刪除緩存。最終結(jié)果就是:數(shù)據(jù)庫中已經(jīng)更新,緩存為空。
(2)如果在上面圖中的第三步如果獲取該緩存有數(shù)據(jù),那此時讀線程獲取到的緩存數(shù)據(jù)就是舊數(shù)據(jù)。隨后寫線程又會刪除緩存。最終結(jié)果是:數(shù)據(jù)庫中已經(jīng)更新,緩存為空,但需要注意到的是此次讀線程獲取到的是舊數(shù)據(jù)。也就是說在寫線程刪除緩存之前并且緩存中數(shù)據(jù)未到過期時間,這中間讀線程獲取到的數(shù)據(jù)都是舊數(shù)據(jù)。
可以看到第四種策略在寫-讀這種場景下,部分解決了第三種策略的問題。當然第四種策略還存在一種異常情景。
異常場景

綜合上面的分析,第四種策略存在兩個問題:
1. 先寫后讀的情況下,可能在刪除緩存并且緩存過期之前,讀取到的是緩存中的舊數(shù)據(jù)
2. 先讀后寫的情況下,在讀線程更新緩存之前,寫線程已經(jīng)更新了緩存,會導致緩存被覆蓋成舊數(shù)據(jù),這種情況幾率很小。
分析了這么多種策略,都存在問題,相比于1,2兩種策略,3,4策略已經(jīng)有很大進步,尤其是第四種策略,緩存不一致的情況出現(xiàn)的機率已經(jīng)很小了。
我們可以看到3,4兩種策略最終都是緩存被刷回了老數(shù)據(jù),有沒有可能刪掉這個緩存,進一步減少緩存不一致的概率呢?
5)延遲雙刪
策略3與策略4的結(jié)合體-解決不一致問題

延遲雙刪策略相比于第四種策略可以看到,經(jīng)過一段休眠時間后,會在進行一次刪除緩存的策略??梢钥闯墒?,4兩種策略的結(jié)合。而且經(jīng)過這一步刪除緩存的操作,進一步加快了緩存清理的過程,相當于加快了第四種策略中緩存的失效。
延遲雙刪:先刪除緩存再更新數(shù)據(jù)庫,休眠一段時間后再刪除緩存
休眠時間:一般來說是1s或1s以內(nèi),可使用【業(yè)務邏輯讀數(shù)據(jù)庫的耗時+幾百毫秒】計算
介紹完了這五種策略,可以看到不管采用哪一種形式,都是會出現(xiàn)緩存不一致的情況,并不能完全避免。只是通過特定策略盡可能減少這種緩存不一致的出現(xiàn)。
相比較這五種策略中,第五種策略緩存不一致的出現(xiàn)概率最小,但是設計復雜,其實平常的系統(tǒng)中采用第四種形式幾乎就已經(jīng)可以達到我們的要求,滿足第四種策略緩存不一致的場景概率已經(jīng)很低了。同時需要給大家介紹的是第四種策略就是經(jīng)常提到的 cache-aside 模式,關于緩存的各種形式,我會在后續(xù)其他文章中介紹,這里暫時就挖一個坑吧,哈哈哈哈。。。。
好啦,是不是干貨滿滿呢老板們???今天文章就先寫到這里吧。
-----------------------------end--------------------------------
???歡迎關注我的公眾號:

?? 歡迎想深入學習編程知識,探究底層原理或者了解開發(fā)工程師工作趣事的同學加入我的知識星球,剛剛開通,正在梳理內(nèi)容,后續(xù)保證干貨很多,歡迎關注!
