在數(shù)據(jù)讀多寫少的情況下作為緩存來(lái)使用,恐怕是Redis使用最普遍的場(chǎng)景了。當(dāng)使用Redis作為緩存的時(shí)候,一般流程是這樣的。
- 如果緩存在Redis中存在,即緩存命中,則直接返回?cái)?shù)據(jù)
- 如果Redis中沒有對(duì)應(yīng)緩存,則需要直接查詢數(shù)據(jù)庫(kù),然后存入Redis,最后把數(shù)據(jù)返回
通常情況下,我們會(huì)為某個(gè)緩存設(shè)置一個(gè)key值,并針對(duì)key值設(shè)置一個(gè)過(guò)期時(shí)間,如果被查詢的數(shù)據(jù)對(duì)應(yīng)的key過(guò)期了,則直接查詢數(shù)據(jù)庫(kù),并將查詢得到的數(shù)據(jù)存入Redis,然后重置過(guò)期時(shí)間,最后將數(shù)據(jù)返回,偽代碼如下:
/**
* 根據(jù)用戶名獲取用戶詳細(xì)信息
* @author 公眾號(hào)【電子發(fā)燒友】
*/
public User getUserInfo(String userName) {
User user = redisCache.getName("user:" + userName);
if (user != null) {
return user;
}
// 從數(shù)據(jù)庫(kù)中直接搜索
user = selectUserByUserName(userName);
// 將數(shù)據(jù)寫入Redis,并設(shè)置過(guò)期時(shí)間
redisCache.set("user:" + userName, user, 30000);
// 返回?cái)?shù)據(jù)
return user;
}
一致性問(wèn)題
但是,在Redis的key值未過(guò)期的情況下,用戶修改了個(gè)人信息,我們此時(shí)既要操作數(shù)據(jù)庫(kù)數(shù)據(jù),也要操作Redis數(shù)據(jù)。現(xiàn)在我們面臨了兩種選擇:
- 先操作Redis的數(shù)據(jù),再操作數(shù)據(jù)庫(kù)的數(shù)據(jù)
- 先操作數(shù)據(jù)庫(kù)的數(shù)據(jù),再操作Redis的數(shù)據(jù)
如論選擇哪種方法,最理想的情況下,兩個(gè)操作要么同時(shí)成功,要么同時(shí)失敗,否則就會(huì)出現(xiàn)Redis和數(shù)據(jù)庫(kù)數(shù)據(jù)不一致的情況。
遺憾的是,目前沒有什么框架能夠保證Redis的數(shù)據(jù)和數(shù)據(jù)庫(kù)的數(shù)據(jù)的完全一致性。我們只能根據(jù)場(chǎng)景和所需要付出的代碼來(lái)采取一定的措施降低數(shù)據(jù)不一致出現(xiàn)的概率,在一致性和性能之間取得一個(gè)折中。
下面我們來(lái)討論一下關(guān)于Redis和數(shù)據(jù)庫(kù)之間數(shù)據(jù)一致性的一些方案。
方案選擇
是刪除緩存還是更新緩存?
當(dāng)數(shù)據(jù)庫(kù)數(shù)據(jù)發(fā)生變化的時(shí)候,Redis的數(shù)據(jù)也需要進(jìn)行相應(yīng)的操作,那么這個(gè)「操作」到底是用「更新」還是用「刪除」呢?
「更新」的話調(diào)用Redis的set方法,新值替換舊值;「刪除」直接刪除原來(lái)的緩存,下次查詢的時(shí)候重新讀取數(shù)據(jù)庫(kù),然后再更新Redis。
結(jié)論:推薦直接使用「刪除」操作 。
因?yàn)槭褂谩父隆共僮鞯脑挘銜?huì)面臨兩種選擇
- 先更新緩存,再更新數(shù)據(jù)庫(kù)
- 先更新數(shù)據(jù)庫(kù),再更新緩存
第1種不用考慮了,下面討論一下「先更新數(shù)據(jù)庫(kù),再更新緩存」這種方案。
如果線程1和線程2同時(shí)進(jìn)行更新操作,但是每個(gè)線程的執(zhí)行順序如上圖所示,此時(shí)就會(huì)導(dǎo)致數(shù)據(jù)不一致,因此從這個(gè)角度上我們推薦直接使用刪除緩存的方式。
此外,推薦使用「刪除緩存」還有兩點(diǎn)原因。
- 如果寫數(shù)據(jù)庫(kù)的場(chǎng)景比讀數(shù)據(jù)場(chǎng)景多,采用這種方案就會(huì)導(dǎo)致緩存就被頻繁寫入,浪費(fèi)性能;
- 如果緩存要經(jīng)過(guò)一系列復(fù)雜的計(jì)算才能得到,那么每次寫入數(shù)據(jù)庫(kù)后,都再次計(jì)算寫入的緩存無(wú)疑也是浪費(fèi)性能的。
明確這個(gè)問(wèn)題之后,擺在我們面前的就只有兩個(gè)選擇了:
- 先更新數(shù)據(jù)庫(kù),再刪除緩存
- 先刪除緩存,再更新數(shù)據(jù)庫(kù)
先更新數(shù)據(jù)庫(kù),再刪除緩存
這種方式可能存在以下兩種異常情況
- 更新數(shù)據(jù)庫(kù)失敗,這時(shí)可以通過(guò)程序捕獲異常,直接返回結(jié)果,不再繼續(xù)刪除緩存,所以不會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題
- 更新數(shù)據(jù)庫(kù)成功,刪除緩存失敗。導(dǎo)致數(shù)據(jù)庫(kù)是最新數(shù)據(jù),緩存中的是舊數(shù)據(jù),數(shù)據(jù)不一致
第2種情況應(yīng)該怎么辦呢?我們有兩種方式:失敗重試和 異步更新 。
失敗重試
如果刪除緩存失敗,我們可以捕獲這個(gè)異常,把需要?jiǎng)h除的 key 發(fā)送到消息隊(duì)列。自己創(chuàng)建一個(gè)消費(fèi)者消費(fèi),嘗試再次刪除這個(gè) key,直到刪除成功為止。
這種方式有個(gè)缺點(diǎn),首先會(huì)對(duì)業(yè)務(wù)代碼造成入侵,其次引入了消息隊(duì)列,增加了系統(tǒng)的不確定性。
異步更新緩存
因?yàn)楦聰?shù)據(jù)庫(kù)時(shí)會(huì)往 binlog 中寫入日志,所以我們可以啟動(dòng)一個(gè)監(jiān)聽 binlog變化的服務(wù)(比如使用阿里的 canal開源組件),然后在客戶端完成刪除 key 的操作。如果刪除失敗的話,再發(fā)送到消息隊(duì)列。
總結(jié)
總之,對(duì)于刪除緩存失敗的情況,我們的做法是不斷地重試刪除操作,直到成功。無(wú)論是重試還是異步刪除,都是最終一致性的思想。
先刪除緩存,再更新數(shù)據(jù)庫(kù)
這種方式可能存在以下兩種異常情況
- 刪除緩存失敗,這時(shí)可以通過(guò)程序捕獲異常,直接返回結(jié)果,不再繼續(xù)更新數(shù)據(jù)庫(kù),所以不會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題
- 刪除緩存成功,更新數(shù)據(jù)庫(kù)失敗。在多線程下可能會(huì)出現(xiàn)數(shù)據(jù)不一致的問(wèn)題
這時(shí),Redis中存儲(chǔ)的舊數(shù)據(jù),數(shù)據(jù)庫(kù)的值是新數(shù)據(jù),導(dǎo)致數(shù)據(jù)不一致。這時(shí)我們可以采用延時(shí)雙刪的策略,即更新數(shù)據(jù)庫(kù)數(shù)據(jù)之后,再刪除一次緩存。
用偽代碼表示就是:
/**
* 延時(shí)雙刪
* @author 公眾號(hào)【蟬沐風(fēng)】
*/
public void update(String key, Object data) {
// 首先刪除緩存
redisCache.delKey(key);
// 更新數(shù)據(jù)庫(kù)
db.updateData(data);
// 休眠一段時(shí)間,時(shí)間依據(jù)數(shù)據(jù)的讀取耗費(fèi)的時(shí)間而定
Thread.sleep(500);
// 再次刪除緩存
redisCache.delKey(key);
}
-
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
7242瀏覽量
91039 -
緩存
+關(guān)注
關(guān)注
1文章
245瀏覽量
27047 -
數(shù)據(jù)庫(kù)
+關(guān)注
關(guān)注
7文章
3901瀏覽量
65787 -
Redis
+關(guān)注
關(guān)注
0文章
385瀏覽量
11325
發(fā)布評(píng)論請(qǐng)先 登錄
使用ADO操作數(shù)據(jù)庫(kù)
使用OLE DB操作數(shù)據(jù)庫(kù)
使用DAO操作數(shù)據(jù)庫(kù)
labview遠(yuǎn)程訪問(wèn)數(shù)據(jù)庫(kù)和操作數(shù)據(jù)庫(kù)
《Dot.NET數(shù)據(jù)庫(kù)開發(fā)技術(shù)》操作數(shù)據(jù)庫(kù).pdf
Labsql不能操作數(shù)據(jù)庫(kù)連接池嗎
使用SQL語(yǔ)句操作數(shù)據(jù)庫(kù)
《Dot.NET數(shù)據(jù)庫(kù)開發(fā)技術(shù)》操作數(shù)據(jù)庫(kù)
PHP的使用教程之操作數(shù)據(jù)庫(kù)的詳細(xì)資料說(shuō)明

分布式系統(tǒng)中的數(shù)據(jù)庫(kù)和緩存操作順序

先操作數(shù)據(jù)庫(kù)還是先操作緩存?

評(píng)論