天天看點

你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

在業務開發中我們經常會使用緩存來減少服務的響應rt,提升服務性能。除了先讀緩存-miss後讀DB-再寫緩存的套路外,其實還有其他很多套路,本文将從使用模式、對資料一緻性要求等方面為大家解釋其中的細節。

一、緩存模式

1.1 Cache-aside

你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

該模式就是上文中提到的,也是大家用的最多的模式

1.業務先讀緩存,如果命中直接傳回

      2如果未命中,業務加載db資料放入緩存,然後傳回

1.2 Read-through

你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

1.業務讀緩存,如果命中直接傳回

      2.如果未命中,業務等待:緩存中間件去加載db資料到緩存,然後傳回給業務

      和cacheAside的差別是:在緩存失效時,由緩存自己去加載db中資料到緩存,然後再把結果傳回給業務

1.2 Refresh-ahead【或者叫Read-ahead】

你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

業務隻讀緩存中資料,未命中就傳回空;這種模式需要另外任務把資料加載到cache,或者定時重新整理任務把db中最新的資料更新到cache中。

這種模式的好處也是顯而易見的:C端隻是簡單的從cache中讀取資料,既保證了C端代碼的簡單性,也最大限度的提升了服務的性能。

1.3 Write-through

你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

業務直接更新緩存,并等待緩存中間件同步把資料更新到db;

這種方式和readThrough一樣都依賴cache自身的資料同步能力。

在寫資料時會有2中情況:寫資料沒有命中緩存,寫完DB資料要不要寫到緩存中:流程

Write allocate :    表示需要寫cache

      No-write allocate :表示不寫cache

下圖是:

A write-through cache with no-write allocation

流程

你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

1.4 Write-back [或者叫Write-behind]

你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

這種模式一般應用于高性能的處理場景,将上面write-through的同步寫db改成了異步寫db來提升服務性能;但是存在一個弊端就是cache中資料存在丢失的風險,另外因為緩存髒資料(需要被更新到DB中的資料)的存在,導緻了實作邏輯變得更加複雜。

這個地方大家不妨回憶下InnerDB的實作,是不是似成相識。innerDB中就是将修改後的資料記錄通過異步任務刷到磁盤上,同時通過redo/undo log保證資料記錄丢失後可以被恢複。

A write-back cache with write allocation
你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

1.5 Write-around

你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

這種方式就是直接讀寫db,不使用cache。

二、資料一緻問題

業務開發中,對資料的讀操作次數一般都遠遠大于資料的寫操作次數;也就是說我們大部分情況都是為了減少讀資料的時間才引入cache的。是以這就遇到一個不可避免的問題:引入cache後多大程度能忍受cache中資料的不一緻?

2.1 能夠容忍資料的一段時間不一緻

這種情況對于cache的使用,隻要保證在寫緩存時設定能夠容忍的逾時時間基本就不會出大問題。一般業務使用緩存模式配置如下:

CacheAside + WriteAround

這也是最正常的方式:

  • 寫資料時直接寫db,不操作cache;【甚至 db的寫/更新可能是另外不同的團隊負責操作】
  • 寫cache中資料需要設定過期時間t,允許t時間的資料不一緻
  • 最好:在寫db成功後,invalid cache減少不一緻的機率

ReadAhead

  • db資料需要全部加載到cache中
  • 依賴一個任務不斷的對cache中資料進行定時重新整理
  • 要求資料體量不大

2.2 不容忍資料不一緻

分布式事務方式

db和cache中的資料需要時刻保持一緻,要求在db和cache資料必須要同時變化,這樣就可以用分布式事務式來處理這個問題,例如兩階段送出[prepare,commit/rollback]。但是這需要cache支援xa協定。

緩存一緻性協定

緩存一緻性協定是cpu中是用于協調多核緩存中資料的一緻問題,當然也保證了緩存與主存中資料的一緻性。細節大家可以參考如下位址。

MESI協定

https://en.wikipedia.org/wiki/Cache_coherency_protocols_(examples)#MESI_protocol

MOESI協定

https://en.wikipedia.org/wiki/Cache_coherency_protocols_(examples)#MOESI_protocol

2.3 分布式鎖方法

通過引入一個分布式鎖來保證有資料正在更新時候,隻對DB進行讀取。鎖分為讀寫鎖:讀鎖之間共享、讀寫鎖排斥。

      讀應用端:

  1. 讀應用擷取到讀鎖
    1. 讀緩存 (命中則傳回)
    2. 讀db 同時寫cache
    3. 釋放讀鎖
  2. 讀應用未擷取到讀鎖
    1. 直接讀db 傳回

     寫應用端:

  1. 未擷取到寫鎖,等待重試【最好能夠寫搶占】
  2. 擷取到寫鎖 【同時讀應用此時一定是 進入無法擷取讀鎖的情況】
    1. invalid cache
    2. 寫db
    3. 釋放鎖
你真的懂緩存使用麼?一、緩存模式二、資料一緻問題三、緩存穿透

三、緩存穿透

如果db中記錄不存在,cache因為db沒資料是以也沒有緩存資料;結果每次業務通過cache查詢資料都要從db查一遍【但又沒查到資料】,影響了業務查詢的時間。

解決方法

使用布隆過濾器

思路是前置擋掉不合理的請求,不讓這種請求走到上面查緩存資料的地方。将所有存在key的資訊放到布隆過濾器中。業務請求前經過布隆過濾器中,如果不存在,那查詢的記錄一定也不存在db。

正常的業務中基本是不會使用這種方法,有一些費事的地方:

  1. db中如果存在的資料比較大,需要一個較長的初始化流程
    1. 如果資料有更新/删除時,還要維護額外維護布隆過濾器中

緩存空值

這種方式比較簡單,如果db中沒有查到資料時,就存一個特定值【表示沒有資料】放到緩存中