自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

探索 Redis 與 MySQL 的雙寫問題

數(shù)據(jù)庫
在實際應(yīng)用過程中,如何保證Redis和MySQL雙寫時的數(shù)據(jù)一致性問題成為了開發(fā)者們面臨的重要挑戰(zhàn),本文即將針對這個問題進行深入探討。

在日常的應(yīng)用開發(fā)中,我們經(jīng)常會遇到需要使用多種不同類型的數(shù)據(jù)庫管理系統(tǒng)來滿足各種業(yè)務(wù)需求。其中最典型的就是Redis和MySQL的組合使用。

這兩者擁有各自的優(yōu)點,例如Redis為高性能的內(nèi)存數(shù)據(jù)庫提供了極快的讀寫速度,而MySQL則是非常強大的關(guān)系型數(shù)據(jù)庫,支持事務(wù)處理,并且提供了很好的數(shù)據(jù)一致性。

然而,在實際應(yīng)用過程中,如何保證Redis和MySQL雙寫時的數(shù)據(jù)一致性問題成為了開發(fā)者們面臨的重要挑戰(zhàn)。本文即將針對這個問題進行深入探討,希望能為廣大開發(fā)者們提供一些有價值的思路和解決方案。

一、雙寫一致問題

雙寫一致性問題主要是指當(dāng)我們同時向Redis和MySQL寫數(shù)據(jù)時,由于網(wǎng)絡(luò)延遲、服務(wù)器故障等原因,可能導(dǎo)致數(shù)據(jù)在兩個系統(tǒng)之間產(chǎn)生不一致。

例如,你可能已經(jīng)更新了MySQL中的數(shù)據(jù),但是Redis中的數(shù)據(jù)還未來得及更新,或者反過來。這樣的結(jié)果就可能導(dǎo)致用戶讀到的是舊的、不正確的數(shù)據(jù)。

比如在現(xiàn)實生活中的購物網(wǎng)站場景:假設(shè)用戶A在購買一件庫存僅剩1件的商品,系統(tǒng)在接收到請求后,先將MySQL中的庫存減少1,然后出現(xiàn)了網(wǎng)絡(luò)延遲或系統(tǒng)故障,Redis中的庫存沒有減少。此時,用戶B看到的是還有1件商品,也發(fā)起了購買請求,如果系統(tǒng)又首先更改了MySQL,那么就會出現(xiàn)超賣的情況,即實際庫存已經(jīng)沒有,但因為緩存中的信息不準(zhǔn)確,導(dǎo)致系統(tǒng)銷售了更多的商品。

嚴(yán)格意義上任何非原子操作都不可能保證一致性,除非用阻塞讀寫實現(xiàn)強一致性,所以對于緩存架構(gòu)我們追求的目標(biāo)是最終一致性。

實際上,緩存就是通過犧牲強一致性來提高性能的。這是由CAP理論決定的。緩存系統(tǒng)適用的場景就是非強一致性的場景,它屬于CAP中的AP。

二、緩存讀寫策略

解決這種問題的常見策略就是“緩存讀寫策略”。這個策略用于處理先更新數(shù)據(jù)庫還是先更新緩存等場景。

接下來,我們將探討三種緩存讀寫策略。這些策略各有優(yōu)劣,沒有絕對的最佳選擇。請根據(jù)具體的應(yīng)用場景選擇最合適的策略。

1.Cache-Aside Pattern(旁路緩存模式)

Cache-Aside Pattern,即旁路緩存模式,它的提出是為了盡可能地解決緩存與數(shù)據(jù)庫的數(shù)據(jù)不一致問題。旁路緩存模式中服務(wù)端需要同時維護DB和Cache,并且是以DB的結(jié)果為準(zhǔn)。

讀 :從緩存讀取數(shù)據(jù),讀到直接返回。如果讀取不到的話,從數(shù)據(jù)庫加載,寫入緩存后,再返回響應(yīng)。

寫:更新的時候,先「更新數(shù)據(jù)庫,然后再刪除緩存」。

2.Read/Write Through Pattern(讀寫穿透模式)

Read/Write Through Pattern 中服務(wù)端把 cache 視為主要數(shù)據(jù)存儲,從中讀取數(shù)據(jù)并將數(shù)據(jù)寫入其中。cache 服務(wù)負(fù)責(zé)將此數(shù)據(jù)讀取和寫入 DB,從而減輕了應(yīng)用程序的職責(zé)。

因為我們經(jīng)常使用的分布式緩存 Redis 并沒有提供 cache 將數(shù)據(jù)寫入DB的功能,所以使用并不多。

讀:從 cache 中讀取數(shù)據(jù),讀取到就直接返回 。讀取不到的話,先從 DB 加載,寫入到 cache 后返回響應(yīng)。

從流程圖中可以看出,讀寫穿透模式和旁路緩存模式的讀取流程幾乎相同。不過,在旁路緩存模式中,客戶端需要負(fù)責(zé)將數(shù)據(jù)寫入cache。而在讀寫穿透模式中,cache服務(wù)自行寫入緩存,對客戶端來說,這個過程是透明的。

寫:先查 cache,cache 中不存在,直接更新 DB。cache 中存在,則先更新 cache,然后 cache 服務(wù)自己更新 DB(同步更新 cache和DB)。

3.Write Behind Pattern(異步緩存寫入模式)

Write Behind Pattern 和 Read/Write Through Pattern 很相似,兩者都是由 cache 服務(wù)來負(fù)責(zé) cache 和 DB 的讀寫。

但是,兩個又有很大的不同:Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 則是只更新緩存,不直接更新 DB,而是改為異步批量的方式來更新 DB。

很明顯,這種方式對數(shù)據(jù)一致性帶來了更大的挑戰(zhàn),比如cache數(shù)據(jù)可能還沒異步更新DB的話,cache服務(wù)可能就掛掉了,反而會帶來更大的災(zāi)難。

這種策略在我們平時開發(fā)過程中也非常非常少見,但是不代表它的應(yīng)用場景少,比如消息隊列中消息的異步寫入磁盤、MySQL 的 InnoDB Buffer Pool 機制都用到了這種策略。

Write Behind Pattern 下 DB 的寫性能非常高,非常適合一些數(shù)據(jù)經(jīng)常變化又對數(shù)據(jù)一致性要求沒那么高的場景,比如瀏覽量、點贊量等。

三、旁路緩存模式解析

1.Cache Aside Pattern 的一些疑問

旁路緩存模式是我們平時中使用最多的,根據(jù)該模式,我們可能會有以下幾個疑問。

(1) 為什么寫操作是刪除緩存,而不是更新緩存

答:假設(shè)線程A先發(fā)起一個寫操作,第一步先更新數(shù)據(jù)庫。線程B再發(fā)起一個寫操作,緊接著也更新了數(shù)據(jù)庫。由于網(wǎng)絡(luò)等原因,線程B比線程A先更新了緩存,然后線程A更新緩存。

這時候,緩存保存的是A的數(shù)據(jù)(老數(shù)據(jù)),而數(shù)據(jù)庫保存的是B的數(shù)據(jù)(新數(shù)據(jù)),數(shù)據(jù)就不一致了,臟數(shù)據(jù)出現(xiàn)啦。如果是「刪除緩存取代更新緩存」則不會出現(xiàn)這個臟數(shù)據(jù)問題。

實際上要寫操作的時候更新緩存也是可以的,不過我們需要加一個鎖/分布式鎖來保證更新cache的時候不存在線程安全問題。

(2) 在寫數(shù)據(jù)的過程中,為什么要先更新DB再刪除緩存

答:假設(shè)請求1 是寫操作,要是先刪除緩存A,這時候來了請求2,請求2是讀操作,先讀緩存A,發(fā)現(xiàn)緩存被刪除了(被請求1刪除了),然后去讀數(shù)據(jù)庫,但是此時請求1還沒來得及把數(shù)據(jù)及時更新,那么請求2讀的就是舊數(shù)據(jù),并且請求2還會把讀到的舊數(shù)據(jù)放到緩存中,造成了數(shù)據(jù)的不一致。

其實要先刪緩存,再更新數(shù)據(jù)庫也是可以,如采用「延時雙刪策略」。

休眠一段時間,再次淘汰緩存。這么做,可以將這段時間內(nèi)所造成的緩存臟數(shù)據(jù),再次刪除。

注意sleep休眠的時間不能小于修改數(shù)據(jù)庫數(shù)據(jù)的時間小,基本上1秒就夠了。

(3) 在寫數(shù)據(jù)的過程中,先更新DB,后刪除cache就沒有問題了么?

答: 理論上來說還是可能會出現(xiàn)數(shù)據(jù)不一致性的問題,不過概率非常小。

假設(shè)這會有兩個請求,一個請求A做查詢操作,一個請求B做更新操作,那么會有如下情形產(chǎn)生:

  1. 緩存剛好失效。
  2. 請求A查詢數(shù)據(jù)庫,得一個舊值。
  3. 請求B將新值寫入數(shù)據(jù)庫。
  4. 請求B刪除緩存。
  5. 請求A將查到的舊值寫入緩存 ok,如果發(fā)生上述情況,確實是會發(fā)生臟數(shù)據(jù)。

然而,發(fā)生這種情況的概率并不高

發(fā)生上述情況有一個先天性條件,就是步驟(3)的寫數(shù)據(jù)庫操作比步驟(2)的讀數(shù)據(jù)庫操作耗時更短,才有可能使得步驟(4)先于步驟(5)。

可是,仔細(xì)想想,數(shù)據(jù)庫的讀操作的速度遠(yuǎn)快于寫操作的(不然做讀寫分離干嘛,做讀寫分離的意義就是因為讀操作比較快,耗資源少),因此步驟(3)耗時比步驟(2)更短,這一情形很難出現(xiàn)。

(4) 還有其他造成不一致的原因么?

答:  如果刪除緩存過程中失敗了就會造成不一致問題??梢允褂肅anal去訂閱數(shù)據(jù)庫的binlog,獲得需要操作的數(shù)據(jù)。另起一個程序,獲得這個訂閱程序傳來的信息,進行刪除緩存操作。

2.Cache Aside Pattern 的缺陷

Cache Aside Pattern是一種常見的緩存更新策略,主要在讀取數(shù)據(jù)時用于處理緩存的失效和更新。盡管它有很多優(yōu)點,但也存在一些缺陷:

缺陷1:首次請求數(shù)據(jù)一定不在 cache 的問題

解決辦法:可以將熱點數(shù)據(jù)提前放入cache 中。

缺陷2:寫操作比較頻繁的話導(dǎo)致cache中的數(shù)據(jù)會被頻繁被刪除,這樣會影響緩存命中率 。

  • 數(shù)據(jù)庫和緩存數(shù)據(jù)強一致場景 :更新DB的時候同樣更新cache,不過我們需要加一個鎖/分布式鎖來保證更新cache的時候不存在線程安全問題。
  • 可以短暫地允許數(shù)據(jù)庫和緩存數(shù)據(jù)不一致的場景 :更新DB的時候同樣更新cache,但是給緩存加一個比較短的過期時間,這樣的話就可以保證即使數(shù)據(jù)不一致的話影響也比較小。

四、延時雙刪

Redis的延時雙刪策略主要用于解決分布式系統(tǒng)當(dāng)中的緩存與數(shù)據(jù)庫數(shù)據(jù)一致性問題。以下是其基本步驟:

  • 先刪除緩存。
  • 再更新數(shù)據(jù)庫。
  • 最后延時再次刪除緩存。

該策略的理念是:如果有其他線程在步驟1和步驟2之間查詢到舊的數(shù)據(jù)并寫入了緩存,那么步驟3可以保證這部分舊的數(shù)據(jù)被清除,從而盡可能維持?jǐn)?shù)據(jù)庫和緩存之間的數(shù)據(jù)一致性。

以下是使用Java實現(xiàn)的樣例代碼:

import redis.clients.jedis.Jedis;

public class RedisDoubleDelStrategy {
    private Jedis jedis;
    private static final long DELAY_MILLIS = 1000L; // 設(shè)置為你需要的延時時間

    public RedisDoubleDelStrategy(String host, int port) {
        this.jedis = new Jedis(host, port);
    }

    public void updateDBAndCache(String key, String value) {
        // Step 1: 刪除緩存
        jedis.del(key);

        // Step 2: 更新數(shù)據(jù)庫,此處以打印輸出代替
        System.out.println("Update DB with: " + value);

        // 延遲任務(wù)來完成第二次刪除
        new Thread(() -> {
            try {
                Thread.sleep(DELAY_MILLIS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // Step 3: 延時后再次刪除緩存
            jedis.del(key);
        }).start();
    }
}

這段代碼實現(xiàn)了延時雙刪策略,但請注意它仍然不能完全保證數(shù)據(jù)庫和緩存之間的一致性。

在某些情況下(比如大量并發(fā)情況下),可能仍然會出現(xiàn)不一致的問題。例如,在步驟3之后,如果還有其他線程查詢到了舊數(shù)據(jù)并寫入了緩存,那么數(shù)據(jù)庫和緩存的數(shù)據(jù)就會不一致。因此,在使用該策略時,需要根據(jù)你的系統(tǒng)特性和一致性需求來進行權(quán)衡。

本篇文章到這就結(jié)束了,在探討Redis與MySQL雙寫問題的過程中,我們分析了各種可能的場景和解決方案。雙寫系統(tǒng)不僅考驗我們對數(shù)據(jù)庫原理的理解,也展示了協(xié)同工作的復(fù)雜性。最終,解決這個問題的關(guān)鍵是理解你的用例并根據(jù)實際需求選擇適當(dāng)?shù)牟呗院凸ぞ摺?/p>

而在實際應(yīng)用中,再完美的方案也可能會遇到挑戰(zhàn)和困難。因此,持續(xù)監(jiān)控,頻繁測試和及時調(diào)整策略都至關(guān)重要。希望本文能為你在處理Redis與MySQL雙寫問題上提供一些思路和靈感,同時,我們也期待在未來看到更多精妙的解決方案誕生。

責(zé)任編輯:趙寧寧 來源: Java隨想錄
相關(guān)推薦

2024-05-08 16:37:17

MySQLRedis數(shù)據(jù)庫

2024-05-23 07:59:42

RedisKey性能

2021-06-04 09:56:12

RedisMySQL美團

2024-08-06 09:42:23

2020-08-12 08:25:43

數(shù)據(jù)庫MySQL技術(shù)

2022-11-16 21:55:51

Redis數(shù)據(jù)庫

2025-01-22 16:00:00

MySQL數(shù)據(jù)庫Binlog

2022-04-28 09:36:47

Redis內(nèi)存結(jié)構(gòu)內(nèi)存管理

2023-10-09 23:01:09

MySQL數(shù)據(jù)庫

2024-04-11 08:01:24

RedisMysql分布式鎖

2022-01-26 00:06:08

Redis分布式客戶端

2011-07-29 15:00:10

ServiceStacRedis

2024-03-26 00:00:02

交易鏈路同城雙活交易

2011-04-29 10:18:34

投影機

2024-12-05 12:01:09

2025-03-06 00:00:05

DPIHD開發(fā)

2021-06-11 09:21:58

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

2022-12-05 08:24:32

mongodb數(shù)據(jù)庫數(shù)據(jù)

2024-12-02 09:10:15

Redis性能優(yōu)化
點贊
收藏

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