推薦學習:
想要保證快取與資料庫的雙寫一致,一共有4種方式,即4種同步策略:
從這4種同步策略中,我們需要作出比較的是:
更新快取與刪除快取哪種方式更合適?應該先運算元據庫還是先操作快取?
下面,我們來分析一下,應該採用更新快取還是刪除快取的方式。
優點:每次資料變化都及時更新快取,所以查詢時不容易出現未命中的情況。
缺點:更新快取的消耗比較大。如果資料需要經過複雜的計算再寫入快取,那麼頻繁的更新快取,就會影響伺服器的效能。如果是寫入資料頻繁的業務場景,那麼可能頻繁的更新快取時,卻沒有業務讀取該資料。
優點:操作簡單,無論更新操作是否複雜,都是將快取中的資料直接刪除。
缺點:刪除快取後,下一次查詢快取會出現未命中,這時需要重新讀取一次資料庫。從上面的比較來看,一般情況下,刪除快取是更優的方案。
下面,我們再來分析一下,應該先運算元據庫還是先操作快取。
首先,我們將先刪除快取與先更新資料庫,在出現失敗
時進行一個對比:
如上圖,是先刪除快取再更新資料庫,在出現失敗時可能出現的問題:
如上圖,是先更新資料庫再刪除快取,在出現失敗
時可能出現的問題:
經過上面的比較,我們發現在出現失敗
的時候,是無法明確分辨出先刪快取和先更新資料庫哪個方式更好,以為它們都存在問題。後面我們會進一步對這兩種方式進行比較,但是在這裡我們先探討一下,上述場景出現的問題,應該如何解決呢?
實際上,無論上面我們採用哪種方式去同步快取與資料庫,在第二步出現失敗的時候,都建議採用重試機制解決,上面兩幅圖中已經畫了。
下面我們再將先刪快取與先更新資料庫,在沒有出現失敗時
進行對比:
如上圖,是先刪除快取再更新資料庫,在沒有出現失敗時
可能出現的問題:
可見,程序A的兩步操作均成功,但由於存在並行,在這兩步之間,程序B存取了快取。最終結果是,快取中儲存了舊的資料,而資料庫中儲存了新的資料,二者資料不一致。
如上圖,是先更新資料庫再刪除快取,在沒有出現失敗時
可能出現的問題:
可見,最終快取與資料庫的資料是一致的,並且都是最新的資料。但執行緒B在這個過程裡讀到了舊的資料,可能還有其他執行緒也像執行緒B一樣,在這兩步之間讀到了快取中舊的資料,但因為這兩步的執行速度會比較快,所以影響不大。對於這兩步之後,其他程序再讀取快取資料的時候,就不會出現類似於程序B的問題了。
最終結論:
經過對比你會發現,先更新資料庫、再刪除快取是影響更小的方案。如果第二步出現失敗的情況,則可以採用重試機制解決問題。
上面我們提到,如果是先刪快取、再更新資料庫,在沒有出現失敗時可能會導致資料的不一致。如果在實際的應用中,出於某些考慮我們需要選擇這種方式,那有辦法解決這個問題嗎?答案是有的,那就是採用延時雙刪的策略,延時雙刪的基本思路如下:
public void write(String key, Object data) { Redis.delKey(key); db.updateData(data); Thread.sleep(1000); Redis.delKey(key); }
阻塞一段時間之後,再次刪除快取,就可以把這個過程中快取中不一致的資料刪除掉。而具體的時間,要評估你這項業務的大致時間,按照這個時間來設定即可。
如果資料庫採用的是讀寫分離的架構,那麼又會出現新的問題,如下圖:
此時來了兩個請求,請求 A(更新操作) 和請求 B(查詢操作)
此時的解決辦法就是如果是對 Redis 進行填充資料的查詢資料庫操作,那麼就強制將其指向主庫進⾏查詢。
刪除失敗了怎麼辦?
如果刪除依然失敗,則可以增加重試的次數,但是這個次數要有限制,當超出一定的次數時,要採取報錯、記紀錄檔、發郵件提醒等措施。
先更新資料庫,後刪除快取這⼀種情況也會出現問題,比如更新資料庫成功了,但是在刪除快取的階段出錯了沒有刪除成功,那麼此時再讀取快取的時候每次都是錯誤的資料了。
此時解決方案就是利用訊息佇列進行刪除的補償。具體的業務邏輯⽤語⾔描述如下:
但是這個方案會有⼀個缺點就是會對業務程式碼造成大量的侵入,深深的耦合在⼀起,所以這時會有⼀個優化的方法,我們知道對 Mysql 資料庫更新操作後再 binlog 紀錄檔中我們都能夠找到相應的操作,那麼我們可以訂閱 Mysql 資料庫的 binlog 紀錄檔對快取進行操作。
推薦學習:
以上就是如何保證Redis快取與資料庫的一致性的詳細內容,更多請關注TW511.COM其它相關文章!