數(shù)據(jù)庫(kù)跟緩存的雙寫一致性
1 關(guān)于一致性
為加速系統(tǒng)性能一般都會(huì)引入緩存機(jī)制,比如 Redis。這種情況下當(dāng)用戶讀數(shù)據(jù)時(shí)一般會(huì)按照如下流程:

讀流程
關(guān)于讀的流程大家是沒有異議的,但是對(duì)于數(shù)據(jù)的更新呢,如何操作才算合理呢?
先更新數(shù)據(jù)庫(kù)再更新緩存。 先刪緩存再更新數(shù)據(jù)庫(kù)。 先更新數(shù)據(jù)庫(kù)再刪緩存。
2 一致性解決方法
2.1 緩存TTL
2.2 先更新數(shù)據(jù)庫(kù) 再更新緩存

可發(fā)現(xiàn)如果出現(xiàn)網(wǎng)絡(luò)震蕩會(huì)導(dǎo)致緩存的數(shù)據(jù)是舊數(shù)據(jù)。因此這種方法不可取。并且如果是如下場(chǎng)景也不合適:
寫場(chǎng)景多而讀場(chǎng)景少的業(yè)務(wù)需求,此時(shí)緩存不是經(jīng)常性的讀,卻被頻繁的更新。 如果緩存的數(shù)據(jù)是經(jīng)過各種復(fù)雜計(jì)算后寫入的,那每次寫入緩存都要運(yùn)算一次,此法不可取。
2.3 先刪緩存 再更新數(shù)據(jù)庫(kù)

上面這種情況如何解決呢?一般可以采用
延時(shí)雙刪策略,他的核心執(zhí)行流程如下:public void write(String key,Object value){
redis.delKey(key);
db.updateValue(value);
Thread.sleep(1000); // 再次刪除
redis.delKey(key);
}

sleep的時(shí)間要根據(jù)業(yè)務(wù)數(shù)據(jù)邏輯耗時(shí)而定,反正目的是
確保讀請(qǐng)求結(jié)束,寫請(qǐng)求可以刪除讀請(qǐng)求造成的緩存臟數(shù)據(jù)。
可是其實(shí)第二次刪除還是有不妥的地方:
二次刪除前面涉及到休眠,可能導(dǎo)致系統(tǒng)性能降低,可以采用異步的方式,再起一個(gè)線程來(lái)進(jìn)行異步刪除。 如果二次刪除失敗了,還是會(huì)導(dǎo)致緩存臟數(shù)據(jù)存在的啊!
2.4 先更新數(shù)據(jù)庫(kù) 再刪緩存
失效:應(yīng)用程序先從緩存取數(shù)據(jù),沒有得到,則從數(shù)據(jù)庫(kù)中取數(shù)據(jù),成功后,放到緩存中。命中:應(yīng)用程序從緩存中取數(shù)據(jù),取到后返回。更新:先把數(shù)據(jù)存到數(shù)據(jù)庫(kù)中,成功后,再讓緩存失效。

但是也有可能別的原因?qū)е伦x比寫還慢,導(dǎo)致我們刪了個(gè)寂寞,雖然這種情況很少發(fā)生。

該方案相比先刪除緩存再更新數(shù)據(jù)庫(kù)還是穩(wěn)妥些的,但是也不是萬(wàn)無(wú)一失的。不管是先刪緩存再更新數(shù)據(jù)庫(kù)還是先更新數(shù)據(jù)庫(kù)再刪緩存,
如果刪除緩存失敗了都會(huì)導(dǎo)致緩存跟數(shù)據(jù)不一致問題!2.5 消息隊(duì)列 確保消息刪除

缺點(diǎn)也很明顯:
對(duì)業(yè)務(wù)線代碼造成大量的侵入,引入了中間件。 消息的延遲刪除也會(huì)造成短暫的不一致。
2.6 專門程序+消息隊(duì)列 確保消息刪除

訂閱binlog程序在mysql中有現(xiàn)成的中間件叫canal,可以完成訂閱binlog日志的功能。
3 總結(jié)
緩存是刪除不是更新,而刪除緩存一般有三種方法:如果緩存數(shù)據(jù)不敏感,直接給緩存設(shè)置TTL即可。 先刪緩存再更新數(shù)據(jù)庫(kù),此時(shí)需配合延時(shí)雙刪技術(shù),但可能導(dǎo)致二次刪除失敗。 先更新數(shù)據(jù)庫(kù)再刪緩存,此時(shí)需配合binlog消費(fèi) + 消息隊(duì)列來(lái)實(shí)現(xiàn)。
4 參考
Java后端:http://rjzheng.cnblogs.com 艾小仙分布式鎖:https://t.1yb.co/jaaA
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
評(píng)論
圖片
表情
