在高併發的業務場景下,數據庫大多數情況都是用戶併發訪問最薄弱的環節。所以,就需要使用 redis 做一個緩衝操作,讓請求先訪問到 redis,而不是直接訪問 Mysql 等數據庫。這樣可以大大緩解數據庫的壓力。具體業務流程如下:
讀取緩存步驟一般沒有什麼問題,但是一旦涉及到數據更新:數據庫和緩存更新,就容易出現緩存和數據庫間的數據一致性問題。不管是先寫數據庫,再刪除緩存;還是先刪除緩存,再寫庫,都有可能出現數據不一致的情況。舉個例子:
1. 如果刪除了緩存 Redis,還沒有來得及寫庫 MySQL,另一個線程就來讀取,發現緩存爲空,則去數據庫中讀取數據寫入緩存,此時緩存中爲髒數據。
2. 如果先寫了庫,在刪除緩存前,寫庫的線程宕機了,沒有刪除掉緩存,則也會出現數據不一致情況。
因爲寫和讀是併發的,沒法保證順序,就會出現緩存和數據庫的數據不一致的問題。如何解決?這裏給出兩個解決方案,先易後難,結合業務和技術代價選擇使用。
一、 延時雙刪策略
在寫庫前後都進行 redis.del (key) 操作,並且設定合理的超時時間。具體步驟是:
1)先刪除緩存
2)再寫數據庫
3)休眠 500 毫秒(根據具體的業務時間來定)
4)再次刪除緩存。
那麼,這個 500 毫秒怎麼確定的,具體該休眠多久呢?
需要評估自己的項目的讀數據業務邏輯的耗時。這麼做的目的,就是確保讀請求結束,寫請求可以刪除讀請求造成的緩存髒數據。
當然,這種策略還要考慮 redis 和數據庫主從同步的耗時。最後的寫數據的休眠時間:則在讀數據業務邏輯的耗時的基礎上,加上幾百 ms 即可。比如:休眠 1 秒。
二、設置緩存的過期時間
從理論上來說,給緩存設置過期時間,是保證最終一致性的解決方案。所有的寫操作以數據庫爲準,只要到達緩存過期時間,則後面的讀請求自然會從數據庫中讀取新值然後回填緩存
結合雙刪策略 + 緩存超時設置,這樣最差的情況就是在超時時間內數據存在不一致,而且又增加了寫請求的耗時。
三、如何寫完數據庫後,再次刪除緩存成功?
上述的方案有一個缺點,那就是操作完數據庫後,由於種種原因刪除緩存失敗,這時,可能就會出現數據不一致的情況。這裏,我們需要提供一個保障重試的方案。
1、方案一具體流程
(1)更新數據庫數據;
(2)緩存因爲種種問題刪除失敗;
(3)將需要刪除的 key 發送至消息隊列;
(4)自己消費消息,獲得需要刪除的 key;
(5)繼續重試刪除操作,直到成功。
然而,該方案有一個缺點,對業務線代碼造成大量的侵入。於是有了方案二,在方案二中,啓動一個訂閱程序去訂閱數據庫的 binlog,獲得需要操作的數據。在應用程序中,另起一段程序,獲得這個訂閱程序傳來的信息,進行刪除緩存操作。
2、方案二具體流程
(1)更新數據庫數據;
(2)數據庫會將操作信息寫入 binlog 日誌當中;
(3)訂閱程序提取出所需要的數據以及 key;
(4)另起一段非業務代碼,獲得該信息;
(5)嘗試刪除緩存操作,發現刪除失敗;
(6)將這些信息發送至消息隊列;
(7)重新從消息隊列中獲得該數據,重試操作。