一、什么是一致性問題
為了提升服務(wù)的性能,我們一般會把熱點放進(jìn)緩存,那么這些熱點數(shù)據(jù)就同時存在于數(shù)據(jù)庫和緩存中,緩存中的數(shù)據(jù)和數(shù)據(jù)庫中的數(shù)據(jù)要保持一致,這便是緩存一致性問題。
二、使用緩存存在的問題
加了緩存之后,讀寫流程大概如下:
1. 讀流程

2. 寫流程

3. 寫操作的應(yīng)該更新緩存還是刪除緩存
答案是應(yīng)該刪除緩存,如果是更新,可能會有如下問題:

如上圖所示,如果線程 A 先更新了 DB,接著線程 B 更新 DB,正常情況,緩存中保存的應(yīng)該是線程 B 寫入 DB 的數(shù)據(jù),但因為最后是線程 A 去更新的緩存,因此導(dǎo)致 DB 和緩存中的數(shù)據(jù)不一致。
4. 既然要用刪除,刪除操作在更新DB之前還是之后呢
答案是之前,如果是之后,可能會出現(xiàn)如下問題:

如果先更新 DB,在更新了 DB 之后,還沒來得及刪除緩存之前,線程 B 讀請求進(jìn)來了,那么讀取到的是緩存中的舊數(shù)據(jù),也會出現(xiàn)一致性問題。
5. 先刪除再操作DB就沒問題嗎
答案是也會有問題,可能會出現(xiàn)如下場景:

線程 A 先刪除了緩存,還沒來得及更新 DB 的時候,線程 B 進(jìn)來了,把 DB 中的舊數(shù)據(jù)又讀取到了緩存中,最后線程 A 更新了 DB,數(shù)據(jù)還是不一致。
三、緩存一致性問題的解決方案
1. 雙刪延遲策略
上面說了先刪除緩存還是會存在問題,就是線程 A 更新 DB 之前如果線程 B 把數(shù)據(jù)讀到緩存中了,數(shù)據(jù)也會不一致。雙刪延遲策略就是更新了 DB 后休眠一段時間再次刪除緩存,如下:

為什么要休眠一段時間?
休眠是為了讓線程 B 讀請求能夠執(zhí)行完。如果不休眠,可能會出現(xiàn)第二步的時候線程 B 從 DB 讀取到舊數(shù)據(jù)了,還沒來得及更新到緩存中時,線程 A 執(zhí)行了第四步和第五步,然后線程 B 才執(zhí)行第三步,又把剛才讀取到的舊數(shù)據(jù)更新到緩存中了。因此,休眠的時間應(yīng)該根據(jù)線程 B 執(zhí)行時間而定。存在的問題
問題很明顯,第二次刪除要休眠一段時間,用戶體驗不好,并且對業(yè)務(wù)也有入侵。
2. 使用binlog異步刪除緩存
我們知道,DB 的操作一般都會記錄到日志中,比如 MySQL,所有的寫操作都會記錄到 binlog 中,那么我們可以通過 binlog,去刪除緩存。canal 就是一款用來解析數(shù)據(jù)庫日志的工具,我們可以接入 canal,采集到日志,然后通過 MQ 異步的去刪除緩存,這樣對業(yè)務(wù)代碼就沒有入侵了。異步刪除緩存流程如下:

如果是 DB 有主從庫怎么辦?
主從可能會出現(xiàn)的情況就是主庫的數(shù)據(jù)還沒來得及同步到從庫的時候,消費者已經(jīng)把緩存給刪除了,然后讀請求進(jìn)來,讀取到了從庫的臟數(shù)據(jù),更新到了緩存中,還是有一致性問題。其實解決辦法很簡單,canal 采集從庫的 binlog 就行了。canal 的原理
我們知道 MySQL 主從的原理就是三個線程,一個線程負(fù)責(zé)記錄主庫的寫操作,也就是 binlog 線程;一個線程負(fù)責(zé)把主庫的 binlog 拉取到從庫;還有一個線程就負(fù)責(zé)讀取 binlog,完成從庫數(shù)據(jù)的同步。canal 就是利用這一點,偽裝成一個從庫,從而讀取到 binlog。