緩存策略


轉載https://juejin.im/post/5af5b2c36fb9a07ac65318bd

緩存是現在系統中必不可少的模塊,並且已經成為了高並發高性能架構的一個關鍵組件。這篇博客我們來分析一下使用緩存的正確姿勢。

緩存能解決的問題

  • 提升性能

    絕大多數情況下,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 中取數據,取到后返回。
  • 更新:先把數據存到數據庫中,成功后,再讓緩存失效

 

mark
           Cache Aside 更新模式流程圖

 

注意我們上面所提到的,緩存更新時先更新數據庫,然后在讓緩存失效。那么為什么不是直接更新緩存呢?這里有一些緩存更新的坑,我們需要避免入坑。

避坑指南一

先更新數據庫,再更新緩存。這種做法最大的問題就是兩個並發的寫操作導致臟數據。如下圖(以Redis和Mysql為例),兩個並發更新操作,數據庫先更新的反而后更新緩存,數據庫后更新的反而先更新緩存。這樣就會造成數據庫和緩存中的數據不一致,應用程序中讀取的都是臟數據。

 

mark
 

 

避坑指南二

先刪除緩存,再更新數據庫。這個邏輯是錯誤的,因為兩個並發的讀和寫操作導致臟數據。如下圖(以Redis和Mysql為例)。假設更新操作先刪除了緩存,此時正好有一個並發的讀操作,沒有命中緩存后從數據庫中取出老數據並且更新回緩存,這個時候更新操作也完成了數據庫更新。此時,數據庫和緩存中的數據不一致,應用程序中讀取的都是原來的數據(臟數據)。

 

mark
 

 

避坑指南三

先更新數據庫,再刪除緩存。這種做法其實不能算是坑,在實際的系統中也推薦使用這種方式。但是這種方式理論上還是可能存在問題。如下圖(以Redis和Mysql為例),查詢操作沒有命中緩存,然后查詢出數據庫的老數據。此時有一個並發的更新操作,更新操作在讀操作之后更新了數據庫中的數據並且刪除了緩存中的數據。然而讀操作將從數據庫中讀取出的老數據更新回了緩存。這樣就會造成數據庫和緩存中的數據不一致,應用程序中讀取的都是原來的數據(臟數據)。

 

mark
 

 

但是,仔細想一想,這種並發的概率極低。因為這個條件需要發生在讀緩存時緩存失效,而且有一個並發的寫操作。實際上數據庫的寫操作會比讀操作慢得多,而且還要加鎖,而讀操作必需在寫操作前進入數據庫操作,又要晚於寫操作更新緩存,所有這些條件都具備的概率並不大。但是為了避免這種極端情況造成臟數據所產生的影響,我們還是要為緩存設置過期時間。

 

Read/Write Through 更新模式

在上面的 Cache Aside 更新模式中,應用代碼需要維護兩個數據存儲,一個是緩存(Cache),一個是數據庫(Repository)。而在Read/Write Through 更新模式中,應用程序只需要維護緩存,數據庫的維護工作由緩存代理了。

 

mark
 

                  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 更新模式實現邏輯比較復雜,因為它需要確認有哪些數據是被更新了的,哪些數據需要刷到持久層上。只有在緩存需要失效的時候,才會把它真正持久起來。

 

mark
                           Write Behind Caching 更新模式流程圖

 

 

總結

三種緩存模式的優缺點:

  • Cache Aside 更新模式實現起來比較簡單,但是需要維護兩個數據存儲,一個是緩存(Cache),一個是數據庫(Repository)。
  • Read/Write Through 更新模式只需要維護一個數據存儲(緩存),但是實現起來要復雜一些。
  • Write Behind Caching 更新模式和Read/Write Through 更新模式類似,區別是Write Behind Caching 更新模式的數據持久化操作是異步的,但是Read/Write Through 更新模式的數據持久化操作是同步的。優點是直接操作內存速度快,多次操作可以合並持久化到數據庫。缺點是數據可能會丟失,例如系統斷電等。

緩存是通過犧牲強一致性來提高性能的。所以使用緩存提升性能,就是會有數據更新的延遲。這需要我們在設計時結合業務仔細思考是否適合用緩存。然后緩存一定要設置過期時間,這個時間太短太長都不好,太短的話請求可能會比較多的落到數據庫上,這也意味着失去了緩存的優勢。太長的話緩存中的臟數據會使系統長時間處於一個延遲的狀態,而且系統中長時間沒有人訪問的數據一直存在內存中不過期,浪費內存。                        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM