国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

緩存一致性設(shè)計(jì)思路

這篇具有很好參考價值的文章主要介紹了緩存一致性設(shè)計(jì)思路。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

目錄

  1. Spring注解使用,控制Redis緩存更新
  2. 緩存一致性問題是如何產(chǎn)生的?
  3. 雙更新模式:操作不合理,導(dǎo)致數(shù)據(jù)一致性問題
  4. “后刪緩存”,能解決多數(shù)不一致
  5. 大廠高并發(fā),“后刪緩存”依舊不一致
  6. 如何解決高并發(fā)的不一致問題?延遲雙刪與閃電緩存
  7. 如何解決緩存擊穿?讀操作互斥與集中更新

Redis 是現(xiàn)在互聯(lián)網(wǎng)中使用最廣泛的分布式緩存系統(tǒng),幾乎每家公司都在用。它的 qps 可以達(dá)到10萬每秒,吞吐量還是非常可觀的,對于一般體量的互聯(lián)網(wǎng)公司,一臺機(jī)器就夠了。但不論是什么業(yè)務(wù),都不得不面對一個棘手的問題:那就是Redis和源數(shù)據(jù)的一致性問題

一、Spring注解使用,控制Redis緩存更新

使用 SpringBoot 可以很容易地對 Redis 進(jìn)行操作。Java 的 Redis 的客戶端常用的有三個:jedis、redisson、lettuce。其中,Spring 默認(rèn)使用的是 lettuce。
很多人喜歡使用 Spring 抽象的緩存包 spring-cache,它可以使用注解,非常方便。它的注解采用 AOP 的方式,對 Cache 層進(jìn)行了抽象,可以在各種堆內(nèi)緩存框架和分布式框架之間進(jìn)行切換。

我們來看一下它的 maven 坐標(biāo):

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-cache</artifactId> 
</dependency>

使用spring-cache有三個步驟:

  1. 在啟動類上加上@EnableCaching 注解;
  2. 使用 CacheManager 初始化要使用的緩存框架,使用 @CacheConfig 注解注入要使用的資源;
  3. 使用@Cacheable等注解對資源進(jìn)行緩存。而針對緩存操作的注解有三個:

@Cacheable 表示如果緩存系統(tǒng)里沒有這個數(shù)值,就將方法的返回值緩存起來;
@CachePut 表示每次執(zhí)行該方法,都把返回值緩存起來;
@CacheEvict 表示執(zhí)行方法的時候,清除某些緩存值。

二、緩存一致性問題是如何產(chǎn)生的?

在說緩存一致性問題如何產(chǎn)生的前,我們先看下緩存的API操作,緩存操作和數(shù)據(jù)庫的CRUD結(jié)合起來,大致可以抽象成以下幾個方法:

  • getFromDB(key)
  • getFromRedis(key)
  • putToDB(key,value)
  • putToRedis(key,value)
  • deleteFromDB(key)
  • deleteFromRedis(key)

把Redis當(dāng)緩存使用,就說明Redis是不合適作為落地存儲的。
一般我們是把最終的數(shù)據(jù)存放在數(shù)據(jù)庫中的,,一般情況下,Redis 的操作速度比數(shù)據(jù)庫的操作速度快得多。畢竟是 10wQPS 和上千 QPS 的對比。

上面這些 API 很簡單,但把它們的順序調(diào)整一下,一致性就會出現(xiàn)問題。一致性,簡單說就是“數(shù)據(jù)庫里的數(shù)據(jù)”與“Redis 中的數(shù)據(jù)”不一樣了。
對于讀的過程,一般是沒什么異議的。

  • 首先,讀緩存;
  • 如果緩存沒有值,那就讀取數(shù)據(jù)庫的值;
  • 同時把這個值寫進(jìn)緩存中;

我們下面主要看一下寫模式。

三、雙更新模式:操作不合理,導(dǎo)致數(shù)據(jù)一致性問題

我們來看下常見的一個錯誤編碼方式,這些是代碼 review 時要著重看的點(diǎn),也是常出問題的地方。

public void putValue(key,value){
    putToRedis(key,value);
    putToDB(key,value);//操作失敗了
}

比如我們需要更新一個值,首先刷了緩存,然后把數(shù)據(jù)庫也更新了。但更新數(shù)據(jù)庫過程中出現(xiàn)了異常,發(fā)生了回滾。所以,最后“緩存里的數(shù)據(jù)”和“數(shù)據(jù)庫的數(shù)據(jù)”就不一樣了,也就是出現(xiàn)了數(shù)據(jù)不一致的問題。

那如果先更新數(shù)據(jù)庫,再更新緩存呢?如代碼:

public void putValue(key,value){
    putToDB(key,value);
    putToRedis(key,value);
}

這依然會有問題。

考慮到下面的場景:操作 A 更新 a 的值為 1,操作 B 更新 a 的值為 2。由于數(shù)據(jù)庫和 Redis 的操作,并不是原子的,它們的執(zhí)行時長也不是可控制的。當(dāng)兩個請求的時序發(fā)生了錯亂,就會發(fā)生緩存不一致的情況。

放到實(shí)操中來說:A 操作在更新數(shù)據(jù)庫成功后,再更新 Redis;但在更新 Redis 之前,另外一個更新操作 B 執(zhí)行完畢。那么操作 A 的這個 Redis 更新動作,就和數(shù)據(jù)庫里面的值不一樣了。
其實(shí)雙更新模式的問題,主要不是體現(xiàn)在并發(fā)的一致性上,而是業(yè)務(wù)操作的合理性上。

我們大多數(shù)業(yè)務(wù)代碼并沒有經(jīng)過良好的設(shè)計(jì)。一個緩存的值,可能是多條數(shù)據(jù)庫記錄拼湊或計(jì)算得出來的。比如一個余額操作,可能是“錢包里的值”加上“基金里的值”計(jì)算得出來的。

要是采用“更新”的方式,那這個計(jì)算代碼就分散在項(xiàng)目的多個地方,這就不合理了。

那么怎么辦呢?其實(shí),我們把“緩存更新”改成“刪除”就好了。

四、“后刪緩存”,能解決多數(shù)不一致

因?yàn)槊看巫x取時,如果判斷 Redis 里沒有值,就會重新讀取數(shù)據(jù)庫,這個邏輯是沒問題的。唯一的問題是:我們是先刪除緩存?還是后刪除緩存?

答案是后者!

1、如果先刪緩存

我們來看一下先刪除緩存會有什么問題:

public void putValue(key,value){
    deleteFromRedis(key);
    putToDB(key,value);
}

操作 B 刪除了某個 key 的值,這時候有另外一個請求 A 到來,那么它就會擊穿到數(shù)據(jù)庫,讀取到舊的值。無論操作 B 更新數(shù)據(jù)庫的操作持續(xù)多長時間,都會產(chǎn)生不一致的情況。

2、如果后刪緩存

而把刪除的動作放在后面,就能夠保證每次讀到的值都是新鮮的,從數(shù)據(jù)庫里面拿到最新的。

public void putValue(key,value){
    putToDB(key,value);
    deleteFromRedis(key);
}

這就是我們通常說的Cache-Aside Pattern,也是我們平常使用最多的模式。我們看一下它的具體方式。
先看一下數(shù)據(jù)的讀取過程,規(guī)則是“先讀 cache,再讀 db”,詳細(xì)步驟如下:

  1. 每次讀取數(shù)據(jù),都從 cache 里讀;
  2. 如果讀到了,則直接返回,稱作 cache hit;
  3. 如果讀不到 cache 的數(shù)據(jù),則從 db 里面撈一份,稱作 cache miss;
  4. 將讀取到的數(shù)據(jù)塞入到緩存中,下次讀取時,就可以直接命中。

再來看一下寫請求,規(guī)則是“先更新 db,再刪除緩存”,詳細(xì)步驟如下:

  1. 將變更寫入到數(shù)據(jù)庫中;
  2. 刪除緩存里對應(yīng)的數(shù)據(jù)。

為什么說最常用呢?因?yàn)?Spring cache 就是默認(rèn)實(shí)現(xiàn)了這個模式。

五、大廠高并發(fā),“后刪緩存”依舊不一致

所以在高并發(fā)情況下,Cache Aside Pattern會不夠用。下面就描述一個“先更新再刪除”這種場景下,依然會產(chǎn)生不一致的情況。場景很好理解、很極端,但在高并發(fā)多實(shí)例的情況下很常見。

有一系列的高并發(fā)操作,一直執(zhí)行著更新、刪除的動作。某個時刻,它更新數(shù)據(jù)庫的值為 1,然后刪除了緩存。

public void proccess(key,value){
    N:putToDB(key,1);
    N:deleteFromRedis(key);

    A:getFromRedis(key);
    A:getFromDB(key)=1;
    B:putToDB(key,2);
    B:deleteFromRedis(key);
    A:putToRedis(key,1);

    //DB=2,Redis=1
}

正在這時,有兩個請求發(fā)生了:

  • 一個是讀操作,讀到的當(dāng)然是數(shù)據(jù)庫的舊值 1,我們記作操作 A;

  • 同時,另外一個請求發(fā)起了更新操作,把數(shù)據(jù)庫記錄更新為 2,我們記作操作 B。

一般情況下,讀取操作都是比寫入操作快的,但我們要考慮兩種極端情況:

  • 一種是這個讀取操作 A,發(fā)生在更新操作 B 的尾部;

  • 一種是操作 A 的這個 Redis 的操作時長,耗費(fèi)了非常多的時間。比如,這個節(jié)點(diǎn)正好發(fā)生了 STW。
    那么很容易地,讀操作 A 的結(jié)束時間就超過了操作 B 刪除的動作。就像上圖虛線部分畫的一樣,這個時候,數(shù)據(jù)也是不一致的。

實(shí)際上,你也無法控制它們的執(zhí)行順序。只要發(fā)生這種情況,大概率數(shù)據(jù)庫和 Redis 的值會不一致。

但為什么一般公司不去處理這種情況呢?你仔細(xì)看這張圖,它發(fā)生的條件是非??量痰摹K笤谝幌盗小安l(fā)寫”的同時,還有“并發(fā)讀”的參與。而一般業(yè)務(wù)是達(dá)不到這個量級的,所以一般公司不去處理這種情況,但高并發(fā)業(yè)務(wù)就非常常見了。

六、如何解決高并發(fā)的不一致問題?

大家看上面這種不一致情況發(fā)生的場景,歸根結(jié)底還是“刪除操作”發(fā)生在“更新操作”之前了。

1、延時雙刪

而假如有一種機(jī)制,能夠確保刪除動作一定被執(zhí)行,那就可以解決問題,起碼能縮小數(shù)據(jù)不一致的時間窗口。常用的方法就是延時雙刪,依然是先更新再刪除,唯一不同的是:我們把這個刪除動作,在不久之后再執(zhí)行一次,比如 5 秒之后。

public void putValue(key,value){
    putToDB(key,value);
    deleteFromRedis(key);

    ...deleteFromRedis(key,after5sec);
}

而刪除動作也有多種選擇:

  • 如果放在 DelayQueue 中,會有隨著 JVM 進(jìn)程的死亡,丟失更新的風(fēng)險;

  • 如果放在 MQ 中,會增加編碼的復(fù)雜性。
    所以到了這個時候,并沒有一個能夠行走天下的解決方案。我們得綜合評價很多因素去做設(shè)計(jì),比如團(tuán)隊(duì)的水平、工期、不一致的忍受程度等。

2、閃電緩存

還有一種不太常用的,那就是采用閃電緩存。就是把緩存的失效時間設(shè)置非常短,比如 3~4 秒。一旦失效,就會再次去數(shù)據(jù)庫讀取最新數(shù)據(jù)到緩存。但這種方式,在非常高的并發(fā)下,同一時間對某個 key 的請求擊穿到 DB,會鎖死數(shù)據(jù)庫,所以很少用。

對于一般并發(fā)場景,上面的各種修修補(bǔ)補(bǔ),已經(jīng)把不一致問題降低到很小的概率了。但是它仍然是有問題的,因?yàn)樗肓艘粋€高可用問題:緩存擊穿。

七、如何解決緩存擊穿?

緩存擊穿,指的是緩存中沒有數(shù)據(jù)但數(shù)據(jù)庫中有,由于同一時刻請求量特別大,但是沒有讀到緩存數(shù)據(jù),就會一股腦涌入到數(shù)據(jù)庫中讀取,造成數(shù)據(jù)庫假死。

任何刪除緩存的動作都會造成緩存擊穿。
所以我們上面一直說的是要刪除緩存,但在極高并發(fā)下,你還不能亂刪。
你反過頭去看一下,好像我們一開始雙更的方案比 Cache-Aside Pattern 還要靠譜一些,起碼能用。怎么回事?代碼還能不能寫了?這就是業(yè)務(wù)開發(fā)中的特事特辦,要專門針對這種功能進(jìn)行編碼。場景特殊時,代碼也就不要追求極端優(yōu)雅性了,畢竟也沒有萬能的解決方案。

這時,盤點(diǎn)一下我們手頭上的工具,可以看到有兩種不同的解決方式:

  • 讀操作互斥,使用鎖或者分布式鎖來控制;

  • 更新集中,采用定時或者 binlog 的方式同步更新。

1、讀操作互斥

先來看一下鎖操作。我們依然采用 Cache-Aside Pattern,只不過在讀的時候進(jìn)行一下處理。來看一下偽代碼,從 Redis 讀取不到值的時候,我們要上鎖去從數(shù)據(jù)庫中讀這個值。我們這里默認(rèn)這個值是有的,否則就得處理緩存穿透的問題。

get(key){
    res = getFromRedis(key);
    //讀取緩存為null
    if(null == res){
        lock.lock(...);
        //再次讀取緩存為null
        res = getFromRedis(key);
        if(res == null){
            res = getFromDB(key);
            if(null != res){
                //讀取設(shè)值
                putToRedis(key,res);
            }
        }
        lock.unlock();
    }
    return res;
}
getFromDB(key){
    ...
}

使用分布式鎖和非分布式鎖的主要區(qū)別,還是在于數(shù)據(jù)一致性窗口上:

  • 對于多線程鎖來說,可能某些節(jié)點(diǎn)執(zhí)行得非常慢,更新了舊的值到 Redis;

  • 對于分布式鎖來說,肯定又是一個效率上的話題。

2、集中更新

我們再來看一下集中更新。這個很美好,但大多數(shù)業(yè)務(wù)很復(fù)雜,這對業(yè)務(wù)架構(gòu)的前期設(shè)計(jì)要求非常高。比如通過 Binlog 方式,典型的如 Canal。我們不會在代碼里做任何 Redis 更新的操作,而是會設(shè)計(jì)一個服務(wù),訂閱最新的 binlog 更新信息,然后解析它們,主動去更新緩存。這個一般在大并發(fā)大廠才會采用。

還有一種就是弱化數(shù)據(jù)庫。所有的數(shù)據(jù)首先在 Redis 落地,也就是把 Redis 作為數(shù)據(jù)庫使用,把數(shù)據(jù)庫作為備份庫使用。有定時任務(wù),定期把 Redis 中的數(shù)據(jù),保存到數(shù)據(jù)庫或其他地方。

一般,重要業(yè)務(wù)還要配備一個對賬系統(tǒng),定時去掃描,以便快速發(fā)現(xiàn)不一致的情況。文章來源地址http://www.zghlxwxcb.cn/news/detail-416682.html

到了這里,關(guān)于緩存一致性設(shè)計(jì)思路的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Redis之緩存一致性

    Redis之緩存一致性

    按照緩存更新的方式大致分為: 內(nèi)存淘汰、過期刪除、主動更新。 利用 Redis 的內(nèi)存淘汰策略,當(dāng)內(nèi)存不足時自動進(jìn)行淘汰部分?jǐn)?shù)據(jù),下次查詢時更新緩存,一致性差,無維護(hù)成本。 因?yàn)?Redis 是基于內(nèi)存的,如果內(nèi)存超過限定值( Redis 配置文件的 maxmemory 參數(shù)決定 Redis 最大內(nèi)

    2024年02月07日
    瀏覽(29)
  • CPU緩存一致性原理

    CPU緩存一致性原理

    在本站的文章 CPU緩存那些事兒 中, 介紹了cpu的多級緩存的架構(gòu)和cpu緩存行cache line的結(jié)構(gòu)。CPU對于緩存的操作包含讀和寫,讀操作在cache line中有所涉及,在本文中,將重點(diǎn)討論CPU對于緩存進(jìn)行寫時的行為。 CPU對于高速緩存的讀操作的過程在之前的文章中有提到過,這里梳理

    2024年02月12日
    瀏覽(21)
  • 【Redis】之?dāng)?shù)說緩存一致性

    【Redis】之?dāng)?shù)說緩存一致性

    對于使用 Redis 作為緩存來說,如何保證數(shù)據(jù)庫和緩存數(shù)據(jù)一致性是個麻煩的問題。對于緩存和數(shù)據(jù)庫的操作,主要有以下兩種方式: 先刪緩存,再更新數(shù)據(jù)庫; 先更新數(shù)據(jù)庫,再刪除緩存; 這兩種方式都存在緩存一致性問題,下面我們就分析一下如何解決這兩種方式的緩存

    2024年02月13日
    瀏覽(21)
  • 緩存和數(shù)據(jù)庫一致性

    緩存和數(shù)據(jù)庫一致性

    項(xiàng)目的難點(diǎn)是如何保證緩存和數(shù)據(jù)庫的一致性。無論我們是先更新數(shù)據(jù)庫,后更新緩存還是先更新數(shù)據(jù)庫,然后刪除緩存,在并發(fā)場景之下,仍然會存在數(shù)據(jù)不一致的情況(也存在刪除失敗的情況,刪除失敗可以使用異步重試解決)。有一種解決方法是延遲雙刪的策略,先刪

    2024年01月17日
    瀏覽(24)
  • Redis緩存雙寫一致性

    Redis緩存雙寫一致性

    如果redis中有數(shù)據(jù):需要和數(shù)據(jù)庫中的值相同 如果redis中無數(shù)據(jù):數(shù)據(jù)庫中的值要是最新值,且準(zhǔn)備回寫redis 緩存按照操作來分,可細(xì)分為兩種: 只讀緩存和讀寫緩存 只讀緩存很簡單:就是Redis只做查詢,有就是有,沒有就是沒有,不會再進(jìn)一步訪問MySQL,不再需要會寫機(jī)制

    2023年04月17日
    瀏覽(18)
  • Redis緩存(雙寫一致性問題)

    Redis緩存(雙寫一致性問題)

    前言 : 什么是緩存? 緩存就像自行車,越野車的避震器 舉個例子:越野車,山地自行車,都擁有\(zhòng)\\"避震器\\\", 防止 車體加速后因慣性,在酷似\\\"U\\\"字母的地形上飛躍,硬著陸導(dǎo)致的 損害 ,像個彈簧一樣; 同樣,實(shí)際開發(fā)中,系統(tǒng)也需要\\\"避震器\\\",防止過高的數(shù)據(jù)訪問猛沖系統(tǒng),導(dǎo)致其操作線程無法

    2024年02月02日
    瀏覽(22)
  • Redis---緩存雙寫一致性

    Redis---緩存雙寫一致性

    目錄 一、什么是緩存雙寫一致性呢? ?1.1 雙檢加鎖機(jī)制 ?二、數(shù)據(jù)庫和緩存一致性的更新策略 2.1、先更新數(shù)據(jù)庫,后更新緩存 ?2.2 、先更新緩存,后更新數(shù)據(jù)庫 ?2.3、先刪除緩存,在更新數(shù)據(jù)庫 延時雙刪的策略: ?2.4.先更新數(shù)據(jù)庫,在刪除緩存(常用) 2.5、實(shí)際中是不可

    2024年02月15日
    瀏覽(26)
  • 緩存和數(shù)據(jù)庫一致性問題分析

    目錄 1、數(shù)據(jù)不一致的原因 1.1 并發(fā)操作 1.2 非原子操作 1.3 數(shù)據(jù)庫主從同步延遲 2、數(shù)據(jù)不一致的解決方案 2.1 并發(fā)操作 2.2 非原子操作 2.3 主從同步延遲 2.4 最終方案 3、不同場景下的特殊考慮 3.1 讀多寫少的場景 3.2 讀少寫多的場景 導(dǎo)致緩存和數(shù)據(jù)庫數(shù)據(jù)不一致的原因有三個

    2024年02月14日
    瀏覽(25)
  • 28.Netty源碼之緩存一致性協(xié)議

    28.Netty源碼之緩存一致性協(xié)議

    Mpsc 的全稱是 Multi Producer Single Consumer,多生產(chǎn)者單消費(fèi)者。 Mpsc Queue 可以保證多個生產(chǎn)者同時訪問隊(duì)列是線程安全的,而且同一時刻只允許一個消費(fèi)者從隊(duì)列中讀取數(shù)據(jù)。 Netty Reactor 線程中任務(wù)隊(duì)列 taskQueue 必須滿足多個生產(chǎn)者可以同時提交任務(wù),所以 JCTools 提供的 Mpsc Queu

    2024年02月13日
    瀏覽(20)
  • 【Redis】聊一下緩存雙寫一致性

    【Redis】聊一下緩存雙寫一致性

    緩存雖然可以提高查詢數(shù)據(jù)的的性能,但是在緩存和數(shù)據(jù) 進(jìn)行更新的時候 其實(shí)會出現(xiàn)數(shù)據(jù)不一致現(xiàn)象,而這個不一致其實(shí)可能會給業(yè)務(wù)來帶一定影響。無論是Redis 分布式緩存還是其他的緩存機(jī)制都面臨這樣的問題。 數(shù)據(jù)一致性 緩存中有數(shù)據(jù),那么緩存的數(shù)據(jù)和數(shù)據(jù)庫的數(shù)據(jù)

    2024年02月06日
    瀏覽(25)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包