快取和資料庫資料的一致性
如何保證快取和資料庫資料的一致性
如何保證快取與資料庫的雙寫一致性?
概念:
使用快取的時候,一般情況下要求快取與資料庫中的資料保持一致
在寫入資料,對資料進行修改的時候,需要對快取中的資料和資料庫中的資料進行一起修改
使用快取策略(Cache Aside Pattern)
讀取資料:
首先嚐試從快取讀取,讀到資料則直接返回;如果讀不到,就讀資料庫,並將資料會寫到快取,並返回。
寫入資料:
需要更新資料時,先更新資料庫,然後把快取裡對應的資料失效掉(刪掉)。
為什麼是刪除快取,而不是更新快取?
快取不只是從資料庫中取出來的值,有時候通過計算產生的值也會放在快取中
還有更新快取的代價有時候是很高的。
所以執行寫操作的時候,是刪除快取,等到要訪問這條資料,先去資料庫讀取, 然後寫入快取
資料不一致型別
- 資料庫有資料,快取沒有資料;
- 資料庫有資料,快取也有資料,資料不相等;
在執行寫操作的時候,資料庫修改成功了,但是快取沒有刪除成功 - 資料庫沒有資料,快取有資料。
也是刪除快取沒有成功
解決方案
-
對刪除快取進行重試,資料的一致性要求越高,我越是重試得快。
-
定期全量更新,簡單地說,就是我定期把快取全部清掉,然後再全量載入。
-
給所有的快取一個失效期。
任何不一致,都可以靠失效期解決,失效期越短,資料一致性越高。但是失效期越短,查資料庫就會越頻繁。因此失效期應該根據業務來定。 -
讀請求和寫請求序列化,串到一個記憶體佇列裡去。
序列化可以保證一定不會出現不一致的情況,但是它也會導致系統的吞吐量大幅度降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。
在併發不高的情況:
讀: 讀redis->沒有,讀mysql->把mysql資料寫回redis,有的話直接從redis中取;
寫: 寫mysql->成功,再寫redis;
可能會導致:
如果刪除快取失敗了,那麼會導致資料庫中是新資料,快取中是舊資料,資料就出現了不一致。
解決方案:
先刪除快取,再修改資料庫。如果資料庫修改失敗了,那麼資料庫中是舊資料,快取中是空的,那麼資料不會不一致。因為讀的時候快取沒有,則讀資料庫中舊資料,然後更新到快取中。
併發高的情況:
讀: 讀redis->沒有,讀mysql->把mysql資料寫回redis,有的話直接從redis中取;
寫:非同步話,先寫入redis的快取,就直接返回;定期或特定動作將資料儲存到mysql,可以做到多次更新,一次儲存;
在分散式環境下,資料的讀寫都是併發的,上游有多個應用,通過一個服務的多個部署(為了保證可用性,一定是部署多份的),對同一個資料進行讀寫,在資料庫層面併發的讀寫並不能保證完成順序,也就是說後發出的讀請求很可能先完成(讀出髒資料):
假設這會有兩個請求,一個請求A做查詢操作,一個請求B做更新操作,那麼會有如下情形產生
(1)快取剛好失效
(2)請求A查詢資料庫,得一箇舊值
(3)請求B將新值寫入資料庫
(4)請求B刪除快取
(5)請求A將查到的舊值寫入快取
但是出現這中情況的機率比較小,資料庫的讀操作的速度遠快於寫操作的,
因此(3)耗時比步驟(2)更短,這一情形很難出現。大部分情況都是,(2)比(3)快,所以(5)比(4)先出現
解決方案
(1)設定快取有效時間,快取定時失效
(2)使用非同步延時刪除策略,保證讀請求完成以後,再進行刪除操作。