1. 快取的基本思想
很多朋友,只知道快取可以提高系統性能以及減少請求相應時間,但是,不太清楚快取的本質思想是什么,
快取的基本思想其實很簡單,就是我們非常熟悉的空間換時間,不要把快取想的太高大上,雖然,它的確對系統的性能提升的性價比非常高,
其實,我們在學習使用快取的時候,你會發現快取的思想實際在作業系統或者其他地方都被大量用到, 比如 CPU Cache 快取的是記憶體資料用于解決 CPU 處理速度和記憶體不匹配的問題,記憶體快取的是硬碟資料用于解決硬碟訪問速度過慢的問題, 再比如作業系統在 頁表方案 基礎之上引入了 快表 來加速虛擬地址到物理地址的轉換,我們可以把快表理解為一種特殊的高速緩沖存盤器(Cache),
回歸到業務系統來說:我們為了避免用戶在請求資料的時候獲取速度過于緩慢,所以我們在資料庫之上增加了快取這一層來彌補,
當別人再問你,快取的基本思想的時候,就把上面這段話告訴他,我覺得會讓別人對你刮目相看,
2. 使用快取為系統帶來了什么問題
軟體系統設計中沒有銀彈,往往任何技術的引入都像是把雙刃劍, 你使用的方式得當,就能為系統帶來很大的收益,否則,只是費了精力不討好,
簡單來說,為系統引入快取之后往往會帶來下面這些問題:
ps:其實我覺得引入本地快取來做一些簡單業務場景的話,實際帶來的代價幾乎可以忽略,下面主要是針對分布式快取來說的,
- 系統復雜性增加 :引入快取之后,你要維護快取和資料庫的資料一致性、維護熱點快取等等,
- 系統開發成本往往會增加 :引入快取意味著系統需要一個單獨的快取服務,這是需要花費相應的成本的,并且這個成本還是很貴的,畢竟耗費的是寶貴的記憶體,但是,如果你只是簡單的使用一下本地快取存盤一下簡單的資料,并且資料量不大的話,那么就不需要單獨去弄一個快取服務,
3. 本地快取解決方案
先來聊聊本地快取,這個實際在很多專案中用的蠻多,特別是單體架構的時候,資料量不大,并且沒有分布式要求的話,使用本地快取還是可以的,
常見的單體架構圖如下,我們使用 Nginx 來做負載均衡,部署兩個相同的服務到服務器,兩個服務使用同一個資料庫,并且使用的是本地快取,

那本地快取的方案有哪些呢?且聽 Guide 給你來說一說,
一:JDK 自帶的 HashMap 和 ConcurrentHashMap 了,
ConcurrentHashMap 可以看作是執行緒安全版本的 HashMap ,兩者都是存放 key/value 形式的鍵值對,但是,大部分場景來說不會使用這兩者當做快取,因為只提供了快取的功能,并沒有提供其他諸如過期時間之類的功能,一個稍微完善一點的快取框架至少要提供:過期時間、淘汰機制、命中率統計這三點,
二: Ehcache 、 Guava Cache 、 Spring Cache 這三者是使用的比較多的本地快取框架,
Ehcache的話相比于其他兩者更加重量,不過,相比于Guava Cache、Spring Cache來說,Ehcache支持可以嵌入到 hibernate 和 mybatis 作為多級快取,并且可以將快取的資料持久化到本地磁盤中、同時也提供了集群方案(比較雞肋,可忽略),Guava Cache和Spring Cache兩者的話比較像,Guava相比于Spring Cache的話使用的更多一點,它提供了 API 非常方便我們使用,同時也提供了設定快取有效時間等功能,它的內部實作也比較干凈,很多地方都和ConcurrentHashMap的思想有異曲同工之妙,- 使用
Spring Cache的注解實作快取的話,代碼會看著很干凈和優雅,但是很容易出現問題比如快取穿透、記憶體溢位,
三: 后起之秀 Caffeine,
相比于 Guava 來說 Caffeine 在各個方面比如性能要更加優秀,一般建議使用其來替代 Guava ,并且, Guava 和 Caffeine 的使用方式很像!
本地快取固然好,但是缺陷也很明顯,比如多個相同服務之間的本地快取的資料無法共享,
4. 為什么要有分布式快取?/為什么不直接用本地快取?
本地的快取的優勢非常明顯:低依賴、輕量、簡單、成本低,
但是,本地快取存在下面這些缺陷:
- 本地快取對分布式架構支持不友好,比如同一個相同的服務部署在多臺機器上的時候,各個服務之間的快取是無法共享的,因為本地快取只在當前機器上有,
- 本地快取容量受服務部署所在的機器限制明顯, 如果當前系統服務所耗費的記憶體多,那么本地快取可用的容量就很少,
我們可以把分布式快取(Distributed Cache) 看作是一種記憶體資料庫的服務,它的最終作用就是提供快取資料的服務,
如下圖所示,就是一個簡單的使用分布式快取的架構圖,我們使用 Nginx 來做負載均衡,部署兩個相同的服務到服務器,兩個服務使用同一個資料庫和快取,

使用分布式快取之后,快取部署在一臺單獨的服務器上,即使同一個相同的服務部署在再多機器上,也是使用的同一份快取, 并且,單獨的分布式快取服務的性能、容量和提供的功能都要更加強大,
使用分布式快取的缺點呢,也很顯而易見,那就是你需要為分布式快取引入額外的服務比如 Redis 或 Memcached,你需要單獨保證 Redis 或 Memcached 服務的高可用,
5. 快取讀寫模式/更新策略
下面介紹到的三種模式各有優劣,不存在最佳模式,根據具體的業務場景選擇適合自己的快取讀寫模式,
5.1. Cache Aside Pattern(旁路快取模式)
Cache Aside Pattern 是我們平時使用比較多的一個快取讀寫模式,比較適合讀請求比較多的場景,
Cache Aside Pattern 中服務端需要同時維系 DB 和 cache,并且是以 DB 的結果為準,
下面我們來看一下這個策略模式下的快取讀寫步驟,
寫 :
- 先更新 DB
- 然后直接洗掉 cache ,
簡單畫了一張圖幫助大家理解寫的步驟,

讀 :
- 從 cache 中讀取資料,讀取到就直接回傳
- cache中讀取不到的話,就從 DB 中讀取資料回傳
- 再把資料放到 cache 中,
簡單畫了一張圖幫助大家理解讀的步驟,

你僅僅了解了上面這些內容的話是遠遠不夠的,我們還要搞懂其中的原理,
比如說面試官很可能會追問:“在寫資料的程序中,可以先洗掉 cache ,后更新 DB 么?”
答案: 那肯定是不行的!因為這樣可能會造成資料庫(DB)和快取(Cache)資料不一致的問題,為什么呢?比如說請求1 先寫資料A,請求2隨后讀資料A的話就很有可能產生資料不一致性的問題,這個程序可以簡單描述為:
請求1先把cache中的A資料洗掉 -> 請求2從DB中讀取資料->請求1再把DB中的A資料更新,
當你這樣回答之后,面試官可能會緊接著就追問:“在寫資料的程序中,先更新DB,后洗掉cache就沒有問題了么?”
答案: 理論上來說還是可能會出現資料不一致性的問題,不過概率非常小,因為快取的寫入速度是比資料庫的寫入速度快很多!
比如請求1先讀資料 A,請求2隨后寫資料A,并且資料A不在快取中的話也有可能產生資料不一致性的問題,這個程序可以簡單描述為:
請求1從DB讀資料A->請求2寫更新資料 A 到資料庫并把洗掉cache中的A資料->請求1將資料A寫入cache,
現在我們再來分析一下 Cache Aside Pattern 的缺陷,
缺陷1:首次請求資料一定不在 cache 的問題
解決辦法:可以將熱點資料可以提前放入cache 中,
缺陷2:寫操作比較頻繁的話導致cache中的資料會被頻繁被洗掉,這樣會影響快取命中率 ,
解決辦法:
- 資料庫和快取資料強一致場景 :更新DB的時候同樣更新cache,不過我們需要加一個鎖/分布式鎖來保證更新cache的時候不存在執行緒安全問題,
- 可以短暫地允許資料庫和快取資料不一致的場景 :更新DB的時候同樣更新cache,但是給快取加一個比較短的過期時間,這樣的話就可以保證即使資料不一致的話影響也比較小,
5.2. Read/Write Through Pattern(讀寫穿透)
Read/Write Through Pattern 中服務端把 cache 視為主要資料存盤,從中讀取資料并將資料寫入其中,cache 服務負責將此資料讀取和寫入 DB,從而減輕了應用程式的職責,
這種快取讀寫策略小伙伴們應該也發現了在平時在開發程序中非常少見,拋去性能方面的影響,大概率是因為我們經常使用的分布式快取 Redis 并沒有提供 cache 將資料寫入DB的功能,
寫(Write Through):
- 先查 cache,cache 中不存在,直接更新 DB,
- cache 中存在,則先更新 cache,然后 cache 服務自己更新 DB(同步更新 cache 和 DB),
簡單畫了一張圖幫助大家理解寫的步驟,

讀(Read Through):
- 從 cache 中讀取資料,讀取到就直接回傳 ,
- 讀取不到的話,先從 DB 加載,寫入到 cache 后回傳回應,
簡單畫了一張圖幫助大家理解讀的步驟,

Read-Through Pattern 實際只是在 Cache-Aside Pattern 之上進行了封裝,在 Cache-Aside Pattern 下,發生讀請求的時候,如果 cache 中不存在對應的資料,是由客戶端自己負責把資料寫入 cache,而 Read Through Pattern 則是 cache 服務自己來寫入快取的,這對客戶端是透明的,
和 Cache Aside Pattern 一樣, Read-Through Pattern 也有首次請求資料一定不再 cache 的問題,對于熱點資料可以提前放入快取中,
5.3. Write Behind Pattern(異步快取寫入)
Write Behind Pattern 和 Read/Write Through Pattern 很相似,兩者都是由 cache 服務來負責 cache 和 DB 的讀寫,
但是,兩個又有很大的不同:Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 則是只更新快取,不直接更新 DB,而是改為異步批量的方式來更新 DB,
很明顯,這種方式對資料一致性帶來了更大的挑戰,比如cache資料可能還沒異步更新DB的話,cache服務可能就就掛掉了,
這種策略在我們平時開發程序中也非常非常少見,但是不代表它的應用場景少,比如訊息佇列中訊息的異步寫入磁盤、MySQL 的 InnoDB Buffer Pool 機制都用到了這種策略,
Write Behind Pattern 下 DB 的寫性能非常高,非常適合一些資料經常變化又對資料一致性要求沒那么高的場景,比如瀏覽量、點贊量,
作者:Snailclimb
鏈接:關于快取的一些重要概念(Redis 前置菜)
來源:github
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/227139.html
標籤:其它
上一篇:window上, 用命令列為oracle 建立db(簡版,不建立新實體)
下一篇:Redis 常見問題總結
