在大部分分布式應用中,為了提高系統的效率,都會引入緩存,例如使用Redis。與此同時,也會帶來緩存與數據庫數據不一致的問題。
如果對數據一致性要求不是很高的場景,我們正常的操作是,客戶端先去緩存查詢,如果查詢不到再去數據中查找,數據庫查詢到以后,
再在緩存中放一份,最后返回給客戶端。這樣把大多數的請求落在緩存上,減少數據庫的查詢操作。
如果是做數據增刪改的操作,一般也是有兩種情況。
1.先寫緩存,再寫數據庫;
2.先寫數據庫,再寫緩存。
場景 | 描述 | 解決 |
---|---|---|
先寫緩存再寫數據庫,緩存成功,數據庫失敗 | 緩存寫成功,但如果數據庫寫失敗或延遲,則下次高並發讀取緩存時,出現臟數據 | 不推薦這種方式,應該先寫數據庫,把舊的緩存值設置無效。讀取數據時,緩存不存在,先讀數據庫再更新緩存 |
先寫數據庫,再寫緩存,數據庫寫成功,緩存寫失敗 | 數據庫寫成功,緩存寫失敗,下次高並發讀取緩存時,讀不到數據 | 使用緩存時,如果都緩存失敗,先讀數據庫,再寫緩存的方式 |
第一種方式是錯誤的,所以大部分使用第二種場景。
當然這里存在緩存穿透,緩存雪崩,緩存擊穿等情況,在這里不做詳細解釋。
這里舉了例子,蘋果官網預售Apple 12 Pro Max,庫存是200W,然后果粉們准備去搶購,那么這里會存在以下情況:
- 數據庫扣減庫存成功了,但是更新緩存失敗。例如,數據庫里已經剩余100W,但緩存里還有150W,緩存庫存大於數據庫剩余庫存。
這種請求如何解決?其實可以在扣減庫存前,先去查詢剩余數量,如果數量不足,則返回失敗。 - 如果數據庫扣減失敗了,但緩存里的緩存卻更新了,也就是說緩存中庫存數量小於數據庫里的數量,那么就存在部分剩余庫存無法賣出的問題。
當然這種情況很難發生,因為數據庫的讀操作是遠遠快於寫操作的。
但是,如果要保證數據庫與緩存強一致性該如何設計?
這里可以考慮另一種思路,也就是使用MySQL的binlog。
流程圖是這樣的:
這里說一下流程:
1.客戶端從緩存中讀取;
2.如果有變更操作,更新數據庫;
3.數據庫事務提交以后寫入binlog;
4.我們自己寫一個Java應用程序,把自己模擬成一個MySQL的slave,去訂閱MySQL的數據庫的binlog,MySQL會把binlog dump給我們應用程序;
5.因為binlog是二進制文件,這里需要解析,處理insert、update、delete這些操作。
6.解析完以后,把這些數據丟到消息隊列里。這里的消息隊列有兩個用處,第一是做持久化消息,第二是重試機制;
7.客戶端消費消息隊列的消息,然后推到緩存中,如果沒有成功,進行重試。
8.更新緩存完畢。