閑聊
緩存通用查詢3部曲
- redis 中數(shù)據(jù),返回redis 中的數(shù)據(jù)
- redis 中沒有,查詢數(shù)據(jù)庫并返回
- 完成第二部的同時(shí),將數(shù)據(jù)庫查詢結(jié)果寫到redis,redis和數(shù)據(jù)庫數(shù)據(jù)一致.
談?wù)勲p寫一致性的理解
1.如果redis 中有數(shù)據(jù):需要和數(shù)據(jù)庫中的相同
2.如果redis 中無數(shù)據(jù): 數(shù)據(jù)庫中的值如果是最新的,則要寫入到redis
redis 緩存種類
- 只讀(通過命令的方式寫入,不是由我們的java程序,不常用)
- 讀寫(常用)
redis 讀寫緩存的策略
- 同步直寫策略
寫數(shù)據(jù)后,也同步寫到redis,緩存和數(shù)據(jù)庫中的數(shù)據(jù)一致。
對(duì)于讀寫緩存來說,要想保證緩存和數(shù)據(jù)庫中的數(shù)據(jù)一致,就要采用同步直寫策略 - 異步緩存策略
正常業(yè)務(wù)運(yùn)行中,一定時(shí)間后才將數(shù)據(jù)寫到redis(比如下單后的快遞信息)
異常情況出現(xiàn)后,需要將失敗的動(dòng)作進(jìn)行修補(bǔ),有可能需要借助mq等消息隊(duì)列。
問題
1. 如果使用redis,那就回涉及到redis 緩存與數(shù)據(jù)庫雙寫問題。是先動(dòng)redis呢,還是先動(dòng)mysql?
首先我們的目的是明確的:就是保證redis和mysql的一致性。
給緩存設(shè)置過期時(shí)間,定期清理緩存并會(huì)寫,是最終保證一致性的解決方案。
我們可以對(duì)緩存數(shù)據(jù)設(shè)置過期時(shí)間,所有的寫以數(shù)據(jù)庫為準(zhǔn),對(duì)緩存操作盡最大
努力即可。也就是說數(shù)據(jù)庫寫入成功,而緩存寫入失敗,那么只要達(dá)到過期時(shí)間,
則后面的請(qǐng)求會(huì)從數(shù)據(jù)庫中讀取新值回寫緩存。從而達(dá)到最終一致性。
在了解一下4中更新策略
-
先更新數(shù)據(jù)庫,在更新緩存
如果更新mqsql 成功,redis 失敗,則數(shù)據(jù)不一致
緩存中使錯(cuò)誤的數(shù)據(jù),數(shù)據(jù)不一致,且高并發(fā)下問題更是嚴(yán)重 -
線程緩存,在更新數(shù)據(jù)庫
如果redis 成功,mqsql失敗,則數(shù)據(jù)不一致
3. 先刪除緩存,在更新數(shù)據(jù)庫
如果redis 刪除成功, 數(shù)據(jù)庫刪除失敗,則數(shù)據(jù)不一致
下面有問題的代碼
A線程執(zhí)行下面的代碼,刪除緩存后,正在更新數(shù)據(jù)庫,
線程B要獲取這個(gè)緩存 ,沒有查到,線程B就把數(shù)據(jù)回寫到緩存,
然后線程A更新完數(shù)據(jù)庫后,發(fā)現(xiàn)緩存還在
導(dǎo)致緩存中一直都是錯(cuò)的數(shù)據(jù)。
處理辦法1 延遲雙刪除
這個(gè)方案在第一次刪除之后,延遲一段時(shí)間再次進(jìn)行刪除,所以我們把它叫做“延遲雙栓”
- 先更新數(shù)據(jù)庫,在刪除緩存 (可用,也有問題,但是比上面的3種更好)
如果mqsql成功,redis 失敗,只要是mysql 是正確的,下次通過回寫到reids,保證一致性
ali的cannel也是這個(gè)思想, myssql有個(gè)中間件canel,可以完成biglog日志訂閱功能。 先更新數(shù)據(jù)庫,在更新緩存。
如果上述的問題也不能容忍(在通過在get一次,以取得正確的數(shù)據(jù)),那我們只能借助mq與mysql的binlog日志了
1.我們可以監(jiān)聽 binlog 日志, 但我們知道那些key 和那些表的關(guān)系的時(shí)。我們便可以篩選出我們要監(jiān)聽的sql語句了。
2.當(dāng)監(jiān)聽到 執(zhí)行刪除緩存關(guān)聯(lián)的sql語句時(shí),便把這個(gè)key放到消息隊(duì)列。
3.通過mq 中消息執(zhí)行刪除操作。刪除成功則已出,刪除失敗重試。如果重試次數(shù)較多,加入死信隊(duì)列,后續(xù)處理,排查故障。
但是這樣做有一定的延遲性。比如充值話費(fèi),高峰期;1分鐘后到賬。
2.了解延遲雙刪嗎
延遲雙刪,
是指在第一次刪除之后,延遲一段時(shí)間再次進(jìn)行刪除,所以我們把它叫做“延遲雙栓”。 目的是 在第一次刪除和第二次刪除之前,防止有別的線程將值回寫到reids.
線程A sleep的時(shí)間,需要大于線程B讀取數(shù)據(jù)在寫入緩存的時(shí)間。
這個(gè)時(shí)間該怎么確定呢?
第一種方法:
在業(yè)務(wù)運(yùn)行的時(shí)候統(tǒng)計(jì)下線程讀取數(shù)據(jù)與寫回?cái)?shù)據(jù)的時(shí)間 T1。結(jié)合自己業(yè)務(wù),只要保證雙刪之間的時(shí)間 大于T1百毫秒即可。
第二種方法:
新啟動(dòng)后臺(tái)監(jiān)控程序,后面將會(huì)提到 watchDog監(jiān)控程序。
這種同步刪除降低吞吐量怎么辦?
因?yàn)橐獎(jiǎng)h除2次,所以吞吐量會(huì)降低,另起一個(gè)線程,異步刪除。
3. 微服務(wù)查詢r(jià)edis無,mysql有。為了保證雙寫一致性,會(huì)寫redis 時(shí)需要注意什么?,了解雙檢加鎖嗎,如何避免緩存擊穿?
一般情況下,使用上面的緩存三部曲即可,但如果是高并發(fā)就不行了。想想看,
統(tǒng)一時(shí)刻有1000個(gè)請(qǐng)求過來,代碼都執(zhí)行 判斷redis中有無數(shù)據(jù),也就是要
1000次,接著因?yàn)閞edis中沒有,又是1000次訪問數(shù)據(jù)庫,再接著又是1000次
的會(huì)寫redis. 這個(gè)在高并發(fā)下,明顯是不可取的。
所有出現(xiàn) 雙檢加鎖。
那什么是雙檢加鎖呢?文章來源:http://www.zghlxwxcb.cn/news/detail-464122.html
public String doubleCheckCache() {
// 先從redis 中查詢
String key = "user:1000";
String s = stringRedisTemplate.opsForValue().get(key);
if (!StringUtils.hasLength(s)) {
// 加鎖,防止qps高時(shí),同時(shí)查到redis中無數(shù)據(jù)的情況下,繼續(xù)訪問數(shù)據(jù)庫。防止緩存擊穿。
synchronized (this) {
// 再次檢查redis,第一個(gè)進(jìn)入同步的線程,已經(jīng)將數(shù)據(jù)寫回了redis
s = stringRedisTemplate.opsForValue().get(key);
if (!StringUtils.hasLength(s)) {
//模擬查詢數(shù)據(jù)庫
s = "我是小明";
// 會(huì)寫redis
stringRedisTemplate.opsForValue().setIfAbsent(key,s);
// 這里要考慮數(shù)據(jù)庫如果為空的時(shí)候,要懷疑這ke能時(shí)惡意的,續(xù)作我們將其加入黑明單。
}
}
}
return s;
}
5. redis 和mysql 100%會(huì)出紕漏,做不到強(qiáng)一致性,如何保證最終一致性?
消息隊(duì)列,有延遲,
延遲雙刪除,線程A 時(shí)間不好估算
等待刪除后,執(zhí)行查詢命令,不支持高并發(fā)文章來源地址http://www.zghlxwxcb.cn/news/detail-464122.html
到了這里,關(guān)于redis高級(jí)篇 緩存雙寫一致性之更新策略的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!