快取是現在系統中必不可少的模組,並且已經成為了高併發高效能架構的一個關鍵元件。這篇部落格我們來分析一下使用快取的正確姿勢。
快取能解決的問題
提升效能
絕大多數情況下,select 是出現效能問題最大的地方。一方面,select 會有很多像 join、group、order、like 等這樣豐富的語義,而這些語義是非常耗效能的;另一方面,大多數應用都是讀多寫少,所以加劇了慢查詢的問題。
分散式系統中遠端呼叫也會耗很多效能,因為有網路開銷,會導致整體的響應時間下降。為了挽救這樣的效能開銷,在業務允許的情況(不需要太實時的資料)下,使用快取是非常必要的事情。
緩解資料庫壓力
當使用者請求增多時,資料庫的壓力將大大增加,通過快取能夠大大降低資料庫的壓力。
快取的適用場景
對於資料實時性要求不高
對於一些經常訪問但是很少改變的資料,讀明顯多於寫,適用快取就很有必要。比如一些網站配置項。
對於效能要求高
比如一些秒殺活動場景。
快取三種模式
一般來說,快取有以下三種模式:
Cache Aside 更新模式
Read/Write Through 更新模式
Write Behind Caching 更新模式
通俗一點來講就是,同時更新快取和資料庫(Cache Aside 更新模式);先更新快取,快取負責同步更新資料庫(Read/Write Through 更新模式);先更新快取,快取定時非同步更新資料庫(Write Behind Caching 更新模式)。這三種模式各有優劣,可以根據業務場景選擇使用。
Cache Aside 更新模式
這是最常用的快取模式了,具體的流程是:
- 失效:應用程式先從 cache 取資料,沒有得到,則從資料庫中取資料,成功後,放到快取中。
- 命中:應用程式從 cache 中取資料,取到後返回。
- 更新:先把資料存到資料庫中,成功後,再讓快取失效。
注意我們上面所提到的,快取更新時先更新資料庫,然後在讓快取失效。那麼為什麼不是直接更新快取呢?這裡有一些快取更新的坑,我們需要避免入坑。
避坑指南一
先更新資料庫,再更新快取。這種做法最大的問題就是兩個併發的寫操作導致髒資料。如下圖(以Redis和Mysql為例),兩個併發更新操作,資料庫先更新的反而後更新快取,資料庫後更新的反而先更新快取。這樣就會造成資料庫和快取中的資料不一致,應用程式中讀取的都是髒資料。
避坑指南二
先刪除快取,再更新資料庫。這個邏輯是錯誤的,因為兩個併發的讀和寫操作導致髒資料。如下圖(以Redis和Mysql為例)。假設更新操作先刪除了快取,此時正好有一個併發的讀操作,沒有命中快取後從資料庫中取出老資料並且更新回快取,這個時候更新操作也完成了資料庫更新。此時,資料庫和快取中的資料不一致,應用程式中讀取的都是原來的資料(髒資料)。
避坑指南三
先更新資料庫,再刪除快取。這種做法其實不能算是坑,在實際的系統中也推薦使用這種方式。但是這種方式理論上還是可能存在問題。如下圖(以Redis和Mysql為例),查詢操作沒有命中快取,然後查詢出資料庫的老資料。此時有一個併發的更新操作,更新操作在讀操作之後更新了資料庫中的資料並且刪除了快取中的資料。然而讀操作將從資料庫中讀取出的老資料更新回了快取。這樣就會造成資料庫和快取中的資料不一致,應用程式中讀取的都是原來的資料(髒資料)。
但是,仔細想一想,這種併發的概率極低。因為這個條件需要發生在讀快取時快取失效,而且有一個併發的寫操作。實際上資料庫的寫操作會比讀操作慢得多,而且還要加鎖,而讀操作必需在寫操作前進入資料庫操作,又要晚於寫操作更新快取,所有這些條件都具備的概率並不大。但是為了避免這種極端情況造成髒資料所產生的影響,我們還是要為快取設定過期時間。
Read/Write Through 更新模式
在上面的 Cache Aside 更新模式中,應用程式碼需要維護兩個資料儲存,一個是快取(Cache),一個是資料庫(Repository)。而在Read/Write Through 更新模式中,應用程式只需要維護快取,資料庫的維護工作由快取代理了。
Read/Write Through 更新模式流程圖
Read Through
Read Through 模式就是在查詢操作中更新快取,也就是說,當快取失效的時候,Cache Aside 模式是由呼叫方負責把資料載入入快取,而 Read Through 則用快取服務自己來載入。
Write Through
Write Through 模式和 Read Through 相仿,不過是在更新資料時發生。當有資料更新的時候,如果沒有命中快取,直接更新資料庫,然後返回。如果命中了快取,則更新快取,然後由快取自己更新資料庫(這是一個同步操作)。
Write Behind Caching 更新模式
Write Behind Caching 更新模式就是在更新資料的時候,只更新快取,不更新資料庫,而我們的快取會非同步地批量更新資料庫。這個設計的好處就是直接操作記憶體速度快。因為非同步,Write Behind Caching 更新模式還可以合併對同一個資料的多次操作到資料庫,所以效能的提高是相當可觀的。
但其帶來的問題是,資料不是強一致性的,而且可能會丟失。另外,Write Behind Caching 更新模式實現邏輯比較複雜,因為它需要確認有哪些資料是被更新了的,哪些資料需要刷到持久層上。只有在快取需要失效的時候,才會把它真正持久起來。
總結
三種快取模式的優缺點:
- Cache Aside 更新模式實現起來比較簡單,但是需要維護兩個資料儲存,一個是快取(Cache),一個是資料庫(Repository)。
- Read/Write Through 更新模式只需要維護一個資料儲存(快取),但是實現起來要複雜一些。
- Write Behind Caching 更新模式和Read/Write Through 更新模式類似,區別是Write Behind Caching 更新模式的資料持久化操作是非同步的,但是Read/Write Through 更新模式的資料持久化操作是同步的。優點是直接操作記憶體速度快,多次操作可以合併持久化到資料庫。缺點是資料可能會丟失,例如系統斷電等。
快取是通過犧牲強一致性來提高效能的。所以使用快取提升效能,就是會有資料更新的延遲。這需要我們在設計時結合業務仔細思考是否適合用快取。然後快取一定要設定過期時間,這個時間太短太長都不好,太短的話請求可能會比較多的落到資料庫上,這也意味著失去了快取的優勢。太長的話快取中的髒資料會使系統長時間處於一個延遲的狀態,而且系統中長時間沒有人訪問的資料一直存在記憶體中不過期,浪費記憶體。
-----END-----
喜歡本文的朋友們,歡迎掃一掃下圖關注公眾號擼碼那些事,收看更多精彩內容