偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

更新商家信息后,緩存與DB數(shù)據(jù)不一致導(dǎo)致用戶看到舊數(shù)據(jù),如何解決?

數(shù)據(jù)庫 其他數(shù)據(jù)庫
在當(dāng)今高并發(fā)系統(tǒng)中,緩存(如Redis, Memcached)幾乎是必不可少的組件。它通過將熱點(diǎn)數(shù)據(jù)存放在內(nèi)存中,極大地減輕了數(shù)據(jù)庫的壓力,提升了系統(tǒng)的響應(yīng)速度。然而,引入緩存的同時(shí),我們也引入了新的復(fù)雜性——如何保證緩存里的數(shù)據(jù)和數(shù)據(jù)庫里的數(shù)據(jù)是同步的?這就是經(jīng)典的緩存一致性問題。

作為一名開發(fā)者,我們很可能都遇到過這樣的場景:電商平臺(tái)的運(yùn)營同學(xué)火急火燎地跑過來,說某個(gè)商家的logo、名稱或活動(dòng)信息明明已經(jīng)更新了,但前端APP和頁面上還是顯示著舊數(shù)據(jù),用戶投訴不斷。

你心里“咯噔”一下,立刻去數(shù)據(jù)庫查,發(fā)現(xiàn)數(shù)據(jù)確實(shí)已經(jīng)更新為正確的了。那么問題出在哪?很可能,就是緩存與數(shù)據(jù)庫之間的數(shù)據(jù)不一致了。

在當(dāng)今高并發(fā)系統(tǒng)中,緩存(如Redis, Memcached)幾乎是必不可少的組件。它通過將熱點(diǎn)數(shù)據(jù)存放在內(nèi)存中,極大地減輕了數(shù)據(jù)庫的壓力,提升了系統(tǒng)的響應(yīng)速度。然而,引入緩存的同時(shí),我們也引入了新的復(fù)雜性——如何保證緩存里的數(shù)據(jù)和數(shù)據(jù)庫里的數(shù)據(jù)是同步的?這就是經(jīng)典的緩存一致性問題。

本文將深入探討這個(gè)問題,并從簡單到復(fù)雜,介紹幾種行之有效的解決方案。

一、問題根源:我們?yōu)槭裁磿?huì)需要緩存?

在深入問題之前,我們先達(dá)成一個(gè)共識(shí):緩存是數(shù)據(jù)庫的一個(gè)副本,而不是替代品。 它的核心價(jià)值在于性能

想象一下,一個(gè)熱門商家主頁,每秒有數(shù)萬次請求。如果每次請求都直接去查詢數(shù)據(jù)庫:

1. 數(shù)據(jù)庫的CPU和IO壓力巨大。

2. 查詢速度相對較慢(毫秒級 vs 內(nèi)存的微秒級),用戶體驗(yàn)差。

因此,我們引入緩存。當(dāng)?shù)谝粋€(gè)用戶請求商家信息時(shí),系統(tǒng)會(huì):

1. 檢查緩存中是否存在該數(shù)據(jù)(cacheKey = “shop:123”)。

2. 如果不存在(我們稱之為緩存未命中,Cache Miss),則去數(shù)據(jù)庫中查詢。

3. 將查詢到的數(shù)據(jù)寫入緩存,并設(shè)置一個(gè)過期時(shí)間(TTL)。

4. 返回?cái)?shù)據(jù)給用戶。

后續(xù)的用戶請求,都會(huì)直接在緩存中找到數(shù)據(jù)(緩存命中,Cache Hit),快速返回。這被稱為 “Cache-Aside” 或 “Lazy Loading” 模式。

那么,不一致是如何產(chǎn)生的?

問題出在“更新”操作上。當(dāng)我們更新商家信息時(shí),如果只更新了數(shù)據(jù)庫,而沒有妥善處理緩存,就會(huì)出現(xiàn)不一致。

二、初探解決方案:常見的策略與陷阱

1. 先更新數(shù)據(jù)庫,再刪除緩存(Cache-Aside)

這是最常用、也最被推薦的策略之一。流程如下:

  • 寫請求:更新數(shù)據(jù)庫中的商家信息。
  • 寫請求:刪除緩存中對應(yīng)的key(如 DEL shop:123)。
  • 讀請求:后續(xù)的讀請求發(fā)現(xiàn)緩存中不存在(Cache Miss),于是從數(shù)據(jù)庫讀取最新數(shù)據(jù),并重新寫入緩存。

代碼示例(偽代碼):

public voidupdateShop(Shop shop) {
    // 1. 更新數(shù)據(jù)庫
    shopMapper.updateById(shop);
    // 2. 刪除緩存
    redisClient.del("shop:" + shop.getId());
}

public Shop getShopById(Long id) {
    // 1. 先查緩存
    StringcacheKey="shop:" + id;
    Shopshop= redisClient.get(cacheKey);
    if (shop != null) {
        return shop; // 緩存命中,直接返回
    }
    // 2. 緩存未命中,查數(shù)據(jù)庫
    shop = shopMapper.selectById(id);
    if (shop != null) {
        // 3. 將數(shù)據(jù)庫數(shù)據(jù)寫入緩存
        redisClient.setex(cacheKey, 300, shop); // 設(shè)置300秒過期
    }
    return shop;
}

這個(gè)策略的優(yōu)點(diǎn):

簡單有效:邏輯清晰,易于理解和實(shí)現(xiàn)。

容錯(cuò)性較好:即使第二步刪除緩存失敗,也只是一個(gè)“臟數(shù)據(jù)”暫時(shí)存在的風(fēng)險(xiǎn),可以通過設(shè)置緩存的過期時(shí)間(TTL)來最終兜底。

但它并非完美,存在一個(gè)經(jīng)典的不一致場景:假設(shè)在并發(fā)極高的情況下:

  • 請求A(讀)查詢緩存,未命中,于是去查數(shù)據(jù)庫(此時(shí)讀到的是舊數(shù)據(jù))。
  • 請求B(寫)更新了數(shù)據(jù)庫。
  • 請求B(寫)刪除了緩存。
  • 請求A(讀)將步驟1中讀到的舊數(shù)據(jù)寫入了緩存。

這樣一來,緩存里就是舊數(shù)據(jù),數(shù)據(jù)庫里是新數(shù)據(jù),不一致發(fā)生了。雖然這個(gè)條件比較苛刻(讀請求必須在寫請求更新數(shù)據(jù)庫之后、刪除緩存之前完成數(shù)據(jù)庫查詢,并且其寫緩存操作還要在最晚發(fā)生),但在理論上是存在的。

2. 先刪除緩存,再更新數(shù)據(jù)庫

這個(gè)策略的目的是解決上述的并發(fā)問題,但同樣會(huì)引入新問題。

  • 寫請求:刪除緩存。
  • 寫請求:更新數(shù)據(jù)庫。

在并發(fā)情況下:

  • 請求A(寫)刪除了緩存。
  • 請求B(讀)發(fā)現(xiàn)緩存不存在,去數(shù)據(jù)庫查詢此時(shí)還是舊數(shù)據(jù),并將舊數(shù)據(jù)寫入緩存。
  • 請求A(寫)才更新數(shù)據(jù)庫。

結(jié)果:緩存是舊數(shù)據(jù),數(shù)據(jù)庫是新數(shù)據(jù),不一致再次發(fā)生。這個(gè)概率比第一種策略的場景要高。

三、進(jìn)階方案:如何應(yīng)對高并發(fā)苛刻場景

對于大多數(shù)業(yè)務(wù),第一種“先更新數(shù)據(jù)庫,再刪除緩存”的策略,配合合理的重試機(jī)制和TTL,已經(jīng)足夠。但如果你的業(yè)務(wù)對一致性要求極高,無法忍受哪怕一瞬間的舊數(shù)據(jù),可以考慮以下方案。

方案一:延遲雙刪

這是在“先更新數(shù)據(jù)庫,再刪除緩存”基礎(chǔ)上做的增強(qiáng)。既然在并發(fā)下有可能在刪除緩存后,又被一個(gè)舊的讀請求塞入臟數(shù)據(jù),那我們再刪一次不就行了?

流程:

1. 寫請求:刪除緩存。

2. 寫請求:更新數(shù)據(jù)庫。

3. 寫請求:休眠一個(gè)短暫的時(shí)間(如500毫秒到1秒),再次刪除緩存。

這第二次刪除,目的就是清除掉在“更新數(shù)據(jù)庫”這個(gè)時(shí)間窗口內(nèi),可能被其他讀請求寫入的臟數(shù)據(jù)。

代碼示例:

public void updateShopWithDoubleDelete(Shop shop) {
    String cacheKey = "shop:" + shop.getId();
    // 1. 先刪除緩存
    redisClient.del(cacheKey);
    // 2. 更新數(shù)據(jù)庫
    shopMapper.updateById(shop);
    // 3. 休眠一段時(shí)間,確保讀請求已經(jīng)完成了“讀數(shù)據(jù)庫 -> 寫緩存”的操作
    Thread.sleep(500);
    // 4. 再次刪除緩存
    redisClient.del(cacheKey);
}

如何確定休眠時(shí)間?這個(gè)時(shí)間需要根據(jù)你項(xiàng)目的讀請求平均耗時(shí)來估算。目的是確保所有在第一步刪除緩存后、第二步更新數(shù)據(jù)庫前發(fā)起的讀請求,都已經(jīng)完成了它們的“寫緩存”操作。

缺點(diǎn):

? 降低了寫操作的吞吐量,因?yàn)閺?qiáng)行休眠了。

? 時(shí)間難以精確設(shè)定,設(shè)短了可能刪不干凈,設(shè)長了影響性能。

方案二:異步串行化與緩存隊(duì)列

這是更復(fù)雜但也更嚴(yán)謹(jǐn)?shù)囊环N方案,核心思想是讓對同一個(gè)數(shù)據(jù)的讀寫請求串行化。

我們可以使用一個(gè)內(nèi)存隊(duì)列(或分布式消息隊(duì)列)來實(shí)現(xiàn)。

1. 系統(tǒng)為每一個(gè)商家ID(例如 shop:123)維護(hù)一個(gè)隊(duì)列。

2. 所有對這個(gè)商家的寫請求(更新、刪除)和讀請求(在緩存未命中時(shí)),都封裝成任務(wù),按順序放入對應(yīng)的隊(duì)列。

3. 一個(gè)后臺(tái)的工作線程,從隊(duì)列中順序取出任務(wù)并執(zhí)行。

? 如果是寫任務(wù):執(zhí)行 更新DB -> 刪除緩存。

? 如果是讀任務(wù):執(zhí)行 查DB -> 寫緩存。

這樣做,就保證了對于同一個(gè)商家的操作是嚴(yán)格有序的,不可能出現(xiàn)一個(gè)寫操作還在更新數(shù)據(jù)庫,一個(gè)讀操作就去讀了舊數(shù)據(jù)并寫入緩存的情況。

缺點(diǎn):

? 系統(tǒng)復(fù)雜度急劇上升,需要維護(hù)隊(duì)列和 worker。

? 因?yàn)榇谢?,性能?huì)受一定影響。如果某個(gè)商家是超級熱點(diǎn),其隊(duì)列可能會(huì)積壓。

這個(gè)方案通常只在極端場景下使用,比如“秒殺商品”的庫存更新。

四、終極武器:監(jiān)聽數(shù)據(jù)庫Binlog,異步淘汰緩存

上面所有的方案,都要求應(yīng)用層在代碼里顯式地處理緩存刪除邏輯。如果項(xiàng)目很龐大,團(tuán)隊(duì)很多,很難保證每一個(gè)寫數(shù)據(jù)庫的地方都正確地配上了刪緩存的操作。有沒有一種更解耦、更通用的方式?

有!我們可以把自己偽裝成一個(gè)數(shù)據(jù)庫的“從庫”,去監(jiān)聽數(shù)據(jù)庫的二進(jìn)制日志(Binlog,如MySQL)或變更流(Change Stream,如MongoDB)。當(dāng)數(shù)據(jù)庫有任何數(shù)據(jù)變更時(shí),我們都能近乎實(shí)時(shí)地接收到這個(gè)事件,然后根據(jù)變更的內(nèi)容去刪除緩存。

技術(shù)選型:

Canal:阿里巴巴開源的MySQL Binlog增量訂閱&消費(fèi)組件。

Debezium:一個(gè)開源項(xiàng)目,為CDC(Change Data Capture)而生,支持多種數(shù)據(jù)庫。

MaxWell:另一個(gè)輕量級的MySQL Binlog解析工具。

架構(gòu)流程:

1. 你的業(yè)務(wù)應(yīng)用正常更新數(shù)據(jù)庫,完全不用關(guān)心緩存

2. Canal等服務(wù)連接到MySQL,模擬從庫,接收Binlog。

3. Canal解析Binlog,獲取到哪個(gè)表、哪行數(shù)據(jù)、發(fā)生了何種變更(增/刪/改)。

4. Canal的客戶端(你寫的程序)接收到這些變更事件。

5. 客戶端根據(jù)變更的行數(shù)據(jù),生成對應(yīng)的緩存Key,然后調(diào)用Redis進(jìn)行刪除。

// 這是一個(gè)Canal客戶端的示例邏輯
@EventListener
publicvoidonDataChange(DataChangeEvent event) {
    if (event.getTableName().equals("t_shop")) {
        LongshopId= event.getData().getLong("id");
        StringcacheKey="shop:" + shopId;
        redisClient.del(cacheKey);
        log.info("通過Binlog清除緩存: {}", cacheKey);
    }
}

優(yōu)點(diǎn):

徹底解耦:應(yīng)用層代碼變得非常干凈,只需關(guān)注業(yè)務(wù)和DB。

通用性強(qiáng):無論通過什么途徑(后臺(tái)管理、API、數(shù)據(jù)庫直接操作)更新的數(shù)據(jù),都能觸發(fā)緩存刪除。

性能優(yōu)秀:異步處理,對主業(yè)務(wù)鏈路幾乎沒有性能影響。

缺點(diǎn):

架構(gòu)復(fù)雜:引入了新的中間件,增加了運(yùn)維成本。

時(shí)效性:雖然近乎實(shí)時(shí),但依然有極短的延遲。

五、總結(jié)與選型建議

沒有放之四海而皆準(zhǔn)的銀彈,選擇哪種方案取決于你的業(yè)務(wù)場景和技術(shù)要求。

方案

優(yōu)點(diǎn)

缺點(diǎn)

適用場景

先更新DB,再刪除緩存

簡單、可靠、容錯(cuò)性好

存在極低概率的不一致

絕大多數(shù)業(yè)務(wù)場景的首選

,配合TTL和重試機(jī)制

延遲雙刪

能解決經(jīng)典策略的并發(fā)問題

休眠時(shí)間難定,犧牲寫性能

對一致性要求較高,且能接受一定延遲的寫操作

異步串行化

強(qiáng)一致性,理論上最嚴(yán)謹(jǐn)

系統(tǒng)復(fù)雜,性能有瓶頸

極端場景,如金融賬戶余額、秒殺庫存

監(jiān)聽Binlog

徹底解耦,通用性強(qiáng)

架構(gòu)復(fù)雜,運(yùn)維成本高

大型項(xiàng)目,多團(tuán)隊(duì)協(xié)作,有專門的基礎(chǔ)架構(gòu)團(tuán)隊(duì)

給你的實(shí)踐建議:

1. 從簡單的開始:首先嘗試 “先更新數(shù)據(jù)庫,再刪除緩存” 。在99%的場景下,它已經(jīng)足夠好。

2. 務(wù)必設(shè)置緩存過期時(shí)間(TTL):這是最后的兜底策略。即使所有刪除方案都失敗了,數(shù)據(jù)最終也會(huì)因過期而消失,然后被正確的數(shù)據(jù)填充。這被稱為 “最終一致性”。

3. 增加刪除失敗的重試機(jī)制:如果刪除緩存這一步失敗了,可以將刪除操作放入一個(gè)重試隊(duì)列(或用消息隊(duì)列),不斷重試直到成功。這能極大提高方案的健壯性。

4. 評估成本:不要為了0.01%的不一致概率,去投入100%的復(fù)雜架構(gòu)。技術(shù)決策永遠(yuǎn)是權(quán)衡的藝術(shù)。

希望這篇文章能幫助你徹底理解并解決緩存一致性問題,讓你的用戶永遠(yuǎn)看到最新的商家信息。

責(zé)任編輯:武曉燕 來源: 程序員秋天
相關(guān)推薦

2021-04-18 15:01:56

緩存系統(tǒng)數(shù)據(jù)

2018-07-15 08:18:44

緩存數(shù)據(jù)庫數(shù)據(jù)

2021-01-19 10:39:03

Redis緩存數(shù)據(jù)

2021-05-27 18:06:30

MySQL編碼數(shù)據(jù)

2011-02-22 14:02:48

vsftpd

2024-05-11 07:37:43

數(shù)據(jù)Redis策略

2024-10-10 09:30:45

2025-04-08 09:00:00

數(shù)據(jù)庫緩存架構(gòu)

2021-12-26 14:32:11

緩存數(shù)據(jù)庫數(shù)據(jù)

2021-01-13 05:23:27

緩存數(shù)據(jù)庫高并發(fā)

2017-06-20 09:42:52

網(wǎng)絡(luò)安全法數(shù)據(jù)隱私法網(wǎng)絡(luò)安全

2024-11-18 08:00:00

數(shù)據(jù)倉庫通用語義層商業(yè)智能

2019-08-07 10:25:41

數(shù)據(jù)庫緩存技術(shù)

2022-12-13 08:15:42

緩存數(shù)據(jù)競爭

2010-06-02 10:53:28

MySQL版本

2018-07-08 07:38:28

數(shù)據(jù)庫緩存數(shù)據(jù)

2020-07-20 14:06:38

數(shù)據(jù)庫主從同步服務(wù)

2022-03-18 10:53:49

數(shù)據(jù)系統(tǒng)架構(gòu)

2013-12-13 14:46:55

OSPFMTU鄰接關(guān)系

2022-03-16 15:54:52

MySQL數(shù)據(jù)format
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)