前言 使用redis作為緩存,必然存在redis緩存和DB數據一致性的問題:某一時刻,redis緩存數據和DB數據不一致 一 緩存更新策略 按照緩存更新的方式大致分為: 記憶體淘汰、過期刪除、主動更新 一) 記憶體淘汰 利用Redis的記憶體淘汰策略,當記憶體不足時自動進行淘汰部分數據,下次查詢時更新緩存, ...
前言
使用redis作為緩存,必然存在redis緩存和DB數據一致性的問題:某一時刻,redis緩存數據和DB數據不一致
一 緩存更新策略
按照緩存更新的方式大致分為: 記憶體淘汰、過期刪除、主動更新
一) 記憶體淘汰
利用Redis的記憶體淘汰策略,當記憶體不足時自動進行淘汰部分數據,下次查詢時更新緩存,一致性差,無維護成本
記憶體淘汰策略詳情請參考:redis記憶體淘汰策略和過期刪除策略
二) 過期刪除
緩存添加過期時間,到期後根據過期刪除策略自動進行刪除緩存,下次查詢時進行更新緩存,一致性一般,維護成本低
過期刪除策略詳情請參考:redis記憶體淘汰策略和過期刪除策略
三) 主動更新
應用程式中修改DB,修改緩存,一致性好,維護成本高
主動更新大致分為: Cache Aside Pattern、Read/Write Through Pattern、Write Behind Caching Pattern
1 Cache Aside Pattern
即旁路緩存模式,旁路路由策略,最經典常用的緩存策略
應用程式負責緩存和DB的讀寫
讀寫操作步驟:
讀操作時,先讀緩存,緩存存在直接返回;緩存不存在則讀DB,然後把讀的DB數據存入緩存,返回
寫操作時,先更新DB,再刪除緩存
讀操作流程圖:
寫操作流程圖:
2 Read/Write Through Pattern
該模式下應用程式直接和緩存管理組件交互,緩存管理組件和DB交互,無需關心緩存一致性問題
應用程式只與緩存管理組件交互,負責緩存的讀寫,緩存管理組件負責DB的讀寫
1) Read Through
讀操作時,緩存管理組件先讀緩存,緩存存在直接返回;緩存不存在則讀DB,然後把讀的DB數據存入緩存,返回
流程圖:
2) Write Through
寫操作時,緩存管理組件同步更數DB和緩存
流程圖:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-057lcnOf-1678262363678)(/Write Through流程圖.png)]
3 Write Behind Caching Pattern
和 Write Through相似,不同點在於Write Through更新DB和更新緩存是同步的,而Write Behind Caching Pattern更新DB和更新緩存是非同步的
應用程式只與緩存管理組件交互操作,負責緩存的讀寫,通過定時或閾值的非同步方式將數據同步到DB,保證最終一致
讀流程圖:
寫流程圖:
優點:
減少了更新DB的頻率,讀寫響應非常快,吞吐量也會有明顯的提升
缺點:
不能時時同步,數據同步DB過程服務不可用,導致數據丟失
4 三種主動更新策略的對比
策略 | 說明 | 優點 | 缺點 |
---|---|---|---|
Cache Aside Pattern | 應用程式負責緩存和DB的讀寫 | 使用簡單,直接操作緩存和DB | 需要編寫對緩存和DB讀寫的代碼 |
Read/Write Through Pattern | 應用程式只與緩存管理組件交互,負責緩存的讀寫,緩存管理組件負責DB的讀寫 | 只需負責緩存的讀寫 | 複雜,需要提供對DB讀寫的handler |
Write Behind Caching Pattern | 應用程式只與緩存管理組件交互,負責緩存的讀寫,緩存管理組件負責DB的讀寫,性能最好,在高併發場景下可以降低資料庫的壓力 | 性能最好;只需負責緩存的讀寫 | 不能時時同步,數據同步DB過程服務不可用,導致數據丟失 |
四) 三種緩存更新策略的對比
策略 | 說明 | 一致性 | 維護成本 |
---|---|---|---|
記憶體淘汰 | 使用Redis的記憶體淘汰策略,當記憶體不足時自動進行淘汰部分數據,下次查詢時更新緩存 | 差 | 無 |
過期刪除 | 緩存添加過期時間,到期後根據過期刪除策略自動進行刪除緩存,下次查詢時進行更新緩存 | 低 | 低 |
主動更新 | 在修改資料庫的時也修改緩存,使用硬編碼方式或者硬編碼+中間件方式在修改資料庫時同步或非同步的修改緩存 | 好 | 高 |
二 更新緩存的兩種方式
1 刪除緩存
更新DB時刪除緩存,查詢時再從DB中讀取數據並更新到緩存
2 更新緩存
更新DB時更新緩存,頻繁更新緩存開銷大,且併發時可能導致請求讀取的緩存數據是舊數據
三 緩存更新策略的實現方式
一) 先更新緩存,再更新DB
1 併發寫場景
所有線程都是先更新緩存再更新DB,在某個寫線程更新緩存和更新DB之間,其他寫線程也更新了緩存和DB,導致緩存和DB數據不一致
流程圖:
具體步驟:
1) 線程1更新緩存
2) 線程2更新緩存
3) 線程2更新DB
4) 線程1更新DB
緩存和DB數據不一致的原因:
理論上先更新緩存的線程也會先更新DB,但是併發場景下線程的執行順序無法保證:
a) 若更新DB的順序是: 線程1再線程2,則不會出現數據不一致問題
b) 若更新DB的順序是: 線程2再線程1,此時緩存是線程2的數據,DB是線程1的數據,導致緩存和DB數據不一致
先刪除緩存再更新DB----併發讀寫場景流程圖
2 併發讀寫場景
在寫線程更新緩存和更新DB之間,讀線程也可以獲取到最新的緩存,不會導致緩存和DB數據不一致
流程圖:
具體步驟:
1) 線程1更新緩存
2) 線程2查詢,命中緩存返回
3) 線程1更新DB
緩存和DB數據不一致的原因:
可以保證緩存和DB數據一致,雖然線程1更新DB的操作還沒有完成,但是更新緩存的操作已經完成了,讀請求可以獲取到最新的緩存
二) 先更新DB,再更新緩存
1 併發寫場景
所有線程都是先更新DB再更新緩存,在某個寫線程更新DB和更新緩存之間,其他寫線程也更新了DB和緩存,導致緩存和DB數據不一致
流程圖:
具體步驟:
1) 線程1更新DB
2) 線程2更新DB
3) 線程2更新緩存
4) 線程1更新緩存
緩存和DB數據不一致的原因:
理論上先更新DB的線程也會先更新緩存,但是併發場景下線程的執行順序無法保證:
a) 若更新緩存的順序是: 先線程1再線程2,則不會出現數據不一致問題
b) 若更新緩存的順序是: 先線程2再線程1,此時緩存是線程1的數據,DB是線程2的數據,導致緩存和DB數據不一致
2 併發讀寫場景
在寫線程更新DB和更新緩存之間,讀線程可以獲取到舊數據,但最終會一致
流程圖:
具體步驟:
1) 線程1更新DB
2) 線程2查詢,命中緩存返回
3) 線程1更新緩存
緩存和DB數據不一致的原因:
線程2獲取的緩存是舊數據,但最終都會一致
三) 先刪除緩存,再更新DB
1 併發寫場景
所有線程都是先刪除緩存再更新DB,無論哪個線程先刪除緩存再更新DB,緩存都會被刪除,不會導致緩存和DB數據不一致
流程圖:
具體步驟:
1) 線程1刪除緩存
2) 線程2刪除緩存
3) 線程2更新DB
4) 線程1更新DB
緩存不一致原因:
無論哪個線程先刪除緩存再更新DB,緩存都會被刪除,不會導致緩存和DB數據不一致
2 併發讀寫場景
在寫線程刪除緩存和更新DB之間,讀線程根據查詢的DB結果更新了緩存,導致緩存和DB數據不一致
流程圖:
具體步驟:
1) 線程1刪除緩存
2) 線程2查詢,未命中
3) 線程2查詢DB
4) 線程2根據查詢的DB結果更新緩存
5) 線程1更新DB
緩存和DB數據不一致的原因:
線程1刪除緩存和更新DB之間,線程2根據查詢的DB結果更新了緩存,導致緩存和DB數據不一致
四) 先更新DB,再刪除緩存
1 併發寫場景
所有線程都是先更新DB再刪除緩存,無論哪個線程先更新DB再刪除緩存,緩存都會被刪除,不會導致緩存和DB數據不一致
流程圖:
具體步驟:
1) 線程1更新DB
2) 線程2更新DB
3) 線程2刪除緩存
4) 線程1刪除緩存
緩存不一致原因:
無論哪個線程先更新DB再刪除緩存,緩存都會被刪除,不會導致緩存和DB數據不一致
2 併發讀寫場景
在寫線程更新DB再刪除緩存之間,讀線程可以獲取到舊數據,但最終會一致
流程圖:
具體步驟:
1) 線程1更新DB
2) 線程2查詢命中緩存返回
3) 線程1刪除緩存
緩存不一致原因:
線程2獲取的緩存是舊數據,但最終都會一致
五) 延遲雙刪
先刪除緩存再更新DB在併發寫場景不會導致數據不一致,但是在併發讀寫場景會導致數據不一致
延遲雙刪是基於先刪除緩存再更新DB的基礎上的改進:在更新DB後延遲一定時間,再次刪除緩存
延時是為了保證第二次刪除緩存前能完成更新DB操作,延時時間根據系統的查詢性能而定
第二次刪除緩存是為了保證後續請求查詢DB(此時資料庫中的數據已是更新後的數據),重新寫入緩存,保證數據一致性
1 併發寫場景
無論哪個線程都會刪除緩存,不會導致緩存和DB數據不一致
流程圖:
具體步驟:
1) 線程1刪除緩存
2) 線程2刪除緩存
3) 線程2更新DB
4) 線程1更新DB
5) 線程1延時刪除緩存
6) 線程2延時刪除緩存
緩存不一致原因:
無論哪個線程都會刪除緩存,不會導致緩存和DB數據不一致
2 併發讀寫場景
流程圖:
具體步驟:
1) 線程1刪除緩存
2) 線程2查詢,未命中
3) 線程2查詢DB
4) 線程2根據查詢的DB結果更新緩存
5) 線程1更新DB
6) 線程1延時刪除緩存
緩存不一致原因:
線程1第一次刪除緩存之後,線程2根據查詢的DB結果更新緩存,此時查詢得到的結果是舊數據,線程1延遲第二次刪除緩存之後,後續查詢DB(此時資料庫中的數據已是更新後的數據),重新寫入緩存,不會導致緩存和DB數據不一致
3 延時雙刪的缺點
1) 需要延時,低延時場景不合適,如秒殺等需要低延時,需要強一致,高頻繁修改數據場景
2) 不能保證強一致性,在更新DB之前,查詢線程查詢得到的結果是舊數據,可但可以減輕緩存和DB數據不一致的問題
3) 延時的時間是一個不可評估的值,延時越久,能規避一致性的概率越大
六) 非同步刪除緩存
先更新DB再刪除緩存在併發寫場景不會導致數據不一致,但是在併發讀寫場景會短暫的導致數據不一致,但是由於刪除緩存失敗不會重試,併發寫場景、併發讀寫場景都可能長時間導致數據不一致
非同步更新緩存是基於先更新DB再刪除緩存的基礎上的改進:更新DB之後,基於消費隊列非同步刪除緩存
根據消費隊列不同大致分為:消息隊列、binlog+消息隊列
1 基於消息隊列的非同步刪除緩存
1) 併發寫場景
無論哪個線程先更新DB再刪除緩存,緩存都會被刪除,不會導致緩存和DB數據不一致
流程圖:
具體步驟:
1) 線程1更新DB
2) 線程2更新DB
3) 線程2把刪除緩存放入消息隊列
4) 線程1把刪除緩存放入消息隊列
5) 非同步:消息隊列消費刪除緩存
緩存不一致原因:
無論哪個線程先更新DB再刪除緩存,緩存都會被刪除,不會導致緩存和DB數據不一致
2) 併發讀寫場景
非同步刪除緩存期間,讀線程獲取的緩存是舊數據,短暫出現數據不一致,非同步刪除緩存後最終會一致
流程圖:
具體步驟:
1) 線程1更新DB
2) 線程2查詢緩存,命中返回
3) 線程1把刪除緩存放入消息隊列
4) 非同步:消息隊列消費刪除緩存
緩存不一致原因:
非同步刪除緩存期間,讀線程獲取的緩存是舊數據,短暫出現數據不一致,非同步刪除緩存後最終會一致
2 基於binlog+消息隊列刪除緩存
1) 併發寫場景
流程圖:
具體步驟:
1) 線程1更新DB
2) 線程2更新DB
3) 非同步:binlog日誌收集中間件定時收集DB的binglog日誌
4) 非同步:binlog日誌收集中間件發送日誌消息到消息隊列
5) 非同步:消息隊列消費刪除緩存
緩存不一致原因:
無論哪個線程先更新DB再刪除緩存,緩存都會被刪除,不會導致緩存和DB數據不一致
2) 併發讀寫場景
流程圖:
具體步驟:
1) 線程1更新DB
2) 線程2查詢緩存,命中返回
3) 非同步:binlog日誌收集中間件定時收集DB的binglog日誌
4) 非同步:binlog日誌收集中間件發送日誌消息到消息隊列
5) 非同步:消息隊列消費刪除緩存
緩存不一致原因:
非同步刪除緩存期間,讀線程獲取的緩存是舊數據,短暫出現數據不一致,非同步刪除緩存後最終會一致
3 非同步刪除緩存的優缺點
優點
1) 刪除緩存的操作與主流程代碼解耦
2) 中間件自帶重試機制,增加了操作緩存的成功率
缺點
引入中間件,提升了系統的複雜度,在高併發場景可能會產生性能問題
七) 幾種實現方式的對比
策略 | 併發場景 | 併發問題 | 數據不一致概率 | 說明 |
先更新緩存,再更新DB | 併發寫 | 所有線程都是先更新緩存再更新DB,在某個寫線程更新緩存和更新DB之間,其他寫線程也更新了緩存和DB,導致緩存和DB數據不一致 | 高 | |
併發讀寫 | 在寫線程更新緩存和更新DB之間,讀線程也可以獲取到最新的緩存,不會導致緩存和DB數據不一致 | 不會出現 | ||
先更新DB,再更新緩存 | 併發寫 | 所有線程都是先更新DB再更新緩存,在某個寫線程更新DB和更新緩存之間 其他寫線程也更新了DB和緩存,此時緩存和DB數據不一致 | 高 | |
併發讀寫 | 在寫線程更新DB和更新緩存之間,讀線程獲取的緩存是舊數據,短暫出現數據不一致,但最終會一致 | 短暫不一致,最終會一致 | ||
先刪除緩存,再更新DB | 併發寫 | 無論哪個線程先刪除緩存再更新DB,緩存都會被刪除,不會導致緩存和DB數據不一致 | 不會出現 | |
併發讀寫 | 在寫線程刪除緩存和更新DB之間,讀線程根據查詢的DB結果更新了緩存,導致緩存和DB數據不一致 | 高 | ||
先更新DB,再刪除緩存 | 併發寫 | 無論哪個線程先更新DB再刪除緩存,緩存都會被刪除,不會導致緩存和DB數據不一致 | 不會出現 | |
併發讀寫 | 在寫線程更新DB和刪除緩存之間,讀線程獲取的緩存是舊數據,短暫出現數據不一致,但最終會一致 | 短暫不一致,最終會一致 | ||
延遲雙刪 | 併發寫 | 無論哪個線程都會刪除緩存,不會導致緩存和DB數據不一致 | 不會出現 | 延遲雙刪基於先刪除緩存再更新DB的基礎上的改進:在更新DB後延遲一定時間,再次刪除緩存 |
併發讀寫 | 在寫線程刪除緩存和更新DB之間,讀線程根據查詢的DB結果更新了緩存,短暫出現數據不一致,但延時再次刪除緩存後數據會一致 | 短暫不一致,最終會一致 | ||
非同步刪除緩存 | 併發寫 | 無論哪個線程先更新DB再刪除緩存,緩存都會被刪除,不會導致緩存和DB數據不一致 | 不會出現 | 非同步更新緩存是基於先更新DB再刪除緩存的基礎上的改進:更新DB之後,基於消費隊列非同步刪除緩存 |
併發讀寫 | 非同步刪除緩存期間,讀線程獲取的緩存是舊數據,短暫出現數據不一致,非同步刪除緩存後最終會一致 | 短暫不一致,最終會一致 |