Redis 和 MySQL 如何保證數據一致性?
我們都知道,在開發過程中,很多時候都會用到我們的緩存,而緩存的種類也是五花八門的,我們今天來了解的就是關于緩存中的一種,那就是 Redis。
Redis
redis是一個key-value存儲系統。
和Memcached類似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。
這些數據類型都支持push/pop、add/remove及取交集并集和差集及更豐富的操作,而且這些操作都是原子性的。
在此基礎上,redis支持各種不同方式的排序。與memcached一樣,為了保證效率,數據都是緩存在內存中。
區別的是redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎上實現了master-slave(主從)同步。
今天我們不說這個 Redis 的主從同步,我們來說說我們最常使用 Redis 的時候,會導致的一些問題。
Redis和 Mysql 如何保持一致
說到這個一致性,了不起就得和大家說道說道了,為什么會出現這種情況呢?實際上就是和 Redis 的使用有很大的關系。
都知道,Redis 是一個 NoSQL 的數據庫,而且他還很快,所以很多數據都會從 Mysql 中把數據取出來,然后放到我們的緩存中,然后下次讀取數據的時候,從 Redis 中直接去讀取,這個時候,我們就會出現問題了。
什么問題呢?
在高并發的業務場景下,數據庫大多數情況都是用戶并發訪問最薄弱的環節。所以,就需要使用redis做一個緩沖操作,讓請求先訪問到Redis,而不是直接訪問MySQL等數據庫。
這個業務場景,主要是解決讀數據從Redis緩存,一般都是按照下圖的流程來進行業務操作。
讀取緩存步驟一般沒有什么問題,但是一旦涉及到數據更新:數據庫和緩存更新,就容易出現緩存Redis和數據庫MySQL間的數據一致性問題。
不管是先寫 MySQL數據庫,再刪除Redis緩存;還是先刪除緩存,再寫庫,都有可能出現數據不一致的情況。
舉一個例子:
1.如果刪除了緩存 Redis,還沒有來得及寫庫 MySQL,另一個線程就來讀取,發現緩存為空,則到數據庫中讀取數據,寫入緩存,此時緩存中為臟數據。
2.如果先寫了庫,在刪除緩存前,寫庫的線程宕機了,沒有刪除掉緩存,則也會出現數據不一致情況。
因為寫和讀是并發的,沒法保證順序,就會出現緩存和數據庫的數據不一致的問題。
其實解決方案也有不是少,今天了不起來給大家分析一下 Redis 和 Mysql 保證數據一致性的實現方案。
緩存和數據庫一致性解決方案
延時雙刪策略
延時雙刪策略步驟如下:
- 1.先刪除緩存
- 2.再寫數據庫
- 3.休眠N毫秒
- 4.再次刪除緩存
其實我們可以來想一下,如果有三個線程,分別是 線程1 ,線程2,線程3,三個線程,
其中,線程1先刪除緩存;
線程2讀取緩存為null,同步db數據到緩存中;
線程1更新db中的數據;
線程3查詢緩存中數據是舊數據;
這樣的話,就會出現 Mysql 和 Redis 中的數據不一致,這時候采用延遲雙刪策略,去保證數據的一致性,
這時候就有人問了,為什么要休眠一段時間,然后再執行呢?
假象一下,如果沒有第三步操作時,有很大概率,在兩次刪除Redis操作執行完畢之后,數據庫的數據還沒有更新,此時若有請求訪問數據,便會出現我們一開始提到的數據不一致的問題。
為什么還要再次刪除緩存呢?
如果我們沒有第二次刪除操作,此時有請求訪問數據,有可能是訪問的之前未做修改的 Redis 數據,刪除操作執行后,Redis為空,有請求進來時,便會去訪問數據庫,此時數據庫中的數據已是更新后的數據,保證了數據的一致性。
因為感覺這種延遲雙刪除可靠性并沒有那么高,因為我們并不能保證刪除 Redis 成功,也不能保證數據庫更新也是成功的,也就是我們所說的原子性,兩個組合起來只是在理想情況下。
比如雙刪失敗我們應該怎么處理呢?
1、設置緩存過期時間
從理論上來說,給緩存設置過期時間,是保證最終一致性的解決方案。所有的寫操作以數據庫為準,只要到達緩存過期時間,則后面的讀請求自然會從數據庫中讀取新值然后回填緩存。
結合雙刪策略+緩存超時設置,這樣最差的情況就是在超時時間內數據存在不一致。
2、重試方案
重試方案有兩種實現,一種在業務層做,另外一種實現中間件負責處理。
然而,該方案有一個缺點,對業務線代碼造成大量的侵入。
流程如下:
1.更新數據庫數據;
2.緩存因為種種問題刪除失敗;
3.將消費消息,獲得需要刪除的key;
4.自己消費消息,獲得需要刪除的key;
5.重試刪除操作,直到成功。
而這個放在業務層去處理的話,侵入太高,所以一般是不太推薦使用來解決這個問題。
但是呢,還有一個就是使用中間件來進行處理。
啟動一個訂閱程序去訂閱數據庫的binlog,獲得需要操作的數據。在應用程序中,另起一段程序,獲得這個訂閱程序傳來的信息,進行刪除緩存操作。
流程如下:
1.更新數據庫數據;
2.數據庫會將操作信息寫入binlog日志當中;
3.訂閱程序提取出所需要的數據以及key;
4.另起一段非業務代碼,獲得該信息;
5.嘗試刪除緩存操作,發現刪除失?。?/p>
6.將這些信息發送至消息隊列;
7.重新從消息隊列中獲得該數據,重試操作。
關于延遲雙刪除策略,你學會了么?