如果不是嚴格要求“緩存和數據庫”必須保證一致性的話,最好不要做這個方案:即 讀請求和寫請求串行化,串到一個內存隊列里面去。串行化可以保證一定不會出現不一致的情況,但會導致系統吞吐量大幅度降低。
解決這個問題的最經典的模式,就是Cache Aside Pattern。
Cache Aside Pattern:
(1)讀的時候先讀緩存,如果緩存不存在的話就讀數據庫,取出數據庫后更新緩存;如果存在的話直接讀取緩存的信息。
(2)寫的時候,先更新數據庫,再刪除緩存。
說到這個問題,又會出現很多問題:
(1)為什么是刪除緩存,而不是更新緩存?
(2)為什么是先更新數據庫,再刪除緩存?不是先刪除緩存,再更新數據庫?
寫的時候為什么是刪除緩存不是更新緩存?
很多時候復雜的緩存場景,緩存不是僅僅從數據庫中取出來的值。可能是關聯多張表的數據並通過計算才是緩存需要的值。並且,更新緩存的代價有時候很高。對於需要頻繁寫操作,而讀操作很少的時候,每次進行數據庫的修改,緩存也要隨之更新,會造成系統吞吐的下降,但此時緩存並不會被頻繁訪問到,用到的緩存才去算緩存。
刪除緩存而不是更新緩存,是一種懶加載的思想,不是每次都重復更新緩存,只有用到的時候才去更新緩存,同時即使有大量的讀請求,實際也就更新了一次,后面的請求不會重復讀。
Cache Aside Pattern存在的問題
問題:先更新數據庫,再刪除緩存,如果更新緩存失敗了,導致數據庫中是新數據,緩存中是舊數據,就出現數據不一致的問題。
解決思路:先刪除緩存,再更新數據庫。
-
緩存刪除失敗:如果緩存刪除失敗,那么數據庫信息沒有被修改,保持了數據的一致性;
-
緩存刪除成功,數據庫更新失敗:此時數據庫里的是舊數據,緩存是空的,查詢時發現緩存不存在,就查詢數據庫並更新緩存,數據保持一致。
問題:上面的方案存在不足,如果刪除完緩存更新數據庫時,如果一個請求過來查詢數據,緩存不存在,就查詢數據庫的舊數據,更新舊數據到緩存中。隨后數據更新完成,修改了數據庫的數據,此時緩存和數據庫的數據就會出現不一致了。高並發下會出現這種數據庫+緩存不一致的情況。 如果不采用給緩存設置過期時間策略,該數據永遠都是臟數據。
解決方案:采用雙刪除策略。寫請求先刪除緩存,再去更新數據庫,等待一段時間后異步刪除緩存。這樣可以保證在讀取錯誤數據時能及時被修正過來。
還有一種策略,就是:寫請求先修改緩存為指定值,然后再去更新數據庫,再更新緩存。讀請求過來后,會先讀緩存,判斷是指定值后就進入循環讀取狀態,等到寫請求更新緩存。如果循環超時就去數據庫讀取數據,更新緩存。
這種方案保證了讀寫的一致性,但由於讀請求等待寫請求的完成,會降低系統的吞吐量。
