1. 8張圖帶你分析Redis與MySQL數(shù)據(jù)一致性問題

        共 445字,需瀏覽 1分鐘

         ·

        2021-01-16 17:10

        前言

        對(duì)于Web來說,并發(fā)量和訪問量增加一定程度上推動(dòng)項(xiàng)目技術(shù)和架構(gòu)的更迭和進(jìn)步??赡軙?huì)有以下的一些狀況:

        1. 頁(yè)面并發(fā)量和訪問量并不多,MySQL足以支撐自己邏輯業(yè)務(wù)的發(fā)展。那么其實(shí)可以不加緩存。最多對(duì)靜態(tài)頁(yè)面進(jìn)行緩存即可。
        2. 頁(yè)面的并發(fā)量顯著增多,數(shù)據(jù)庫(kù)有些壓力,并且有些數(shù)據(jù)更新頻率較低反復(fù)被查詢或者查詢速度較慢。那么就可以考慮使用緩存技術(shù)優(yōu)化。對(duì)高命中的對(duì)象存到key-value形式的Redis中,那么,如果數(shù)據(jù)被命中,那么可以不經(jīng)過效率很低的db。從高效的redis中查找到數(shù)據(jù)。
        3. 當(dāng)然,可能還會(huì)遇到其他問題,你還通過靜態(tài)頁(yè)面緩存頁(yè)面、cdn加速、甚至負(fù)載均衡這些方法提高系統(tǒng)并發(fā)量。這里就不做介紹。


        緩存思想無處不在

        我們從一個(gè)算法問題開始了解緩存的意義。

        問題1:

        • 輸入一個(gè)數(shù)n(n<20),求n!;

        分析1

        • 單單考慮算法,不考慮數(shù)值越界問題。
          當(dāng)然我們知道
          n!=n * (n-1) * (n-2) * … * 1= n * (n-1)!;
          那么我們可以用一個(gè)遞歸函數(shù)解決問題。
        static?long?jiecheng(int?n)
        {
        ????if(n==1||n==0)return?1;
        ????else?{
        ??????return?n*jiecheng(n-1);
        ????}
        }

        這樣每輸入求一次需要執(zhí)行n次。
        問題2:

        • 輸入t組數(shù)據(jù)(可能成百上千),每組一個(gè)xi(xi<20),求xi!;

        分析2

        • 如果使用遞歸,輸入t組數(shù)據(jù),每次輸入為xi,那么每次都要執(zhí)行次數(shù)為:



          當(dāng)每次輸入的Xi過大或者t過大都會(huì)造成不小的負(fù)擔(dān)!時(shí)間復(fù)雜度為O(n2)
        • 那么能否換個(gè)思想的。沒錯(cuò)、是打表。打表常用于ACM算法中,常用于解決多組輸入輸出、圖論搜索結(jié)果、路徑儲(chǔ)存問題。那么,對(duì)于這個(gè)求階乘。我們只需要申請(qǐng)一個(gè)數(shù)組,按照編號(hào)從前往后將在需求的數(shù)存到數(shù)組中,后面再取得時(shí)候直接輸出數(shù)組值就可以,思想很明確吧:
        import?java.util.Scanner;
        public?class?test?{
        public?static?void?main(String[]?args)?{
        ????//?TODO?Auto-generated?method?stub
        ????Scanner?sc=new?Scanner(System.in);
        ????int?t=sc.nextInt();
        ????long?jiecheng[]=new?long[21];
        ????jiecheng[0]=1;
        ????for(int?i=1;i<21;i++)
        ????{
        ????????jiecheng[i]=jiecheng[i-1]*i;
        ????}
        ???for(int?i=0;i????????int?x=sc.nextInt();
        ????????System.out.println(jiecheng[x]);
        ????}
        }??
        }
        • 時(shí)間復(fù)雜度才O(n)。這里的思想就和緩存思想差不多。先將數(shù)據(jù)在jiecheng[21]數(shù)組中儲(chǔ)存。執(zhí)行一次計(jì)算。當(dāng)后面繼續(xù)訪問的時(shí)候就相當(dāng)于問數(shù)組值。每次都為O(1的操作)。

        ?

        緩存的應(yīng)用場(chǎng)景

        緩存適用于高并發(fā)的場(chǎng)景,提升服務(wù)容量。主要是將從經(jīng)常被訪問的數(shù)據(jù)或者查詢成本較高從慢的介質(zhì)中存到比較快的介質(zhì)中,比如從硬盤—>內(nèi)存。我們知道大多數(shù)關(guān)系數(shù)據(jù)庫(kù)是基于硬盤讀寫的,其效率和資源有限,而redis是基于內(nèi)存的,其讀寫速度差別差別很大。當(dāng)并發(fā)過高關(guān)系數(shù)據(jù)庫(kù)性能達(dá)到瓶頸時(shí)候,就可以策略性將常訪問數(shù)據(jù)放到Redis提高系統(tǒng)吞吐和并發(fā)量。

        對(duì)于常用網(wǎng)站和場(chǎng)景,關(guān)系數(shù)據(jù)庫(kù)主要可能慢在兩個(gè)地方:

        • 讀寫IO性能較差
        • 一個(gè)數(shù)據(jù)可能通過較大量計(jì)算得到

        所以使用緩存能夠減少磁盤IO次數(shù)和關(guān)系數(shù)據(jù)庫(kù)的計(jì)算次數(shù)。讀取上速度快也從兩個(gè)方面體現(xiàn):

        • 基于內(nèi)存,讀寫較快
        • 使用哈希算法直接定位結(jié)果不需要計(jì)算

        所以對(duì)于像樣的,有點(diǎn)規(guī)模的網(wǎng)站,緩存是很?necessary的,而Redis無疑是最好的選擇之一。

        ?

        需要注意的問題

        緩存使用不當(dāng)會(huì)帶來很多問題。所以需要對(duì)一些細(xì)節(jié)進(jìn)行認(rèn)真考量和設(shè)計(jì)。當(dāng)然最難得數(shù)據(jù)一致性在下面單獨(dú)分析。

        是否用緩存

        項(xiàng)目不能為了用緩存而用緩存,緩存并一定適合所有場(chǎng)景,如果對(duì)數(shù)據(jù)一致性要求極高,又或者數(shù)據(jù)頻繁更改而查詢并不多,又或者根本沒并發(fā)量的、查詢簡(jiǎn)單的不一定需要緩存,還可能浪費(fèi)資源使得項(xiàng)目變得臃腫難維護(hù),并且使用redis緩存多多少少可能會(huì)遇到數(shù)據(jù)一致性問題需要考慮。

        緩存合理設(shè)計(jì)

        在設(shè)計(jì)緩存的時(shí)候,很可能會(huì)遇到多表查詢,如果遇到多表查詢緩存的鍵值對(duì)就需要合理考慮,是拆分還是合在一起?當(dāng)然如果組合種類多但常出現(xiàn)的不多也可以直接緩存,具體的設(shè)計(jì)要根據(jù)項(xiàng)目業(yè)務(wù)需求來看,并沒有一個(gè)非常絕對(duì)的標(biāo)準(zhǔn)。

        過期策略選擇

        • 緩存裝的是相對(duì)熱點(diǎn)和常用的數(shù)據(jù),Redis資源也是有限,需要選擇一個(gè)合理的策略讓緩存過期刪除。我們學(xué)過操作系統(tǒng)也知道在計(jì)算機(jī)的緩存實(shí)現(xiàn)中有先進(jìn)先出的算法(FIFO);最近最少使用算法(LRU);最佳淘汰算法(OPT);最少訪問頁(yè)面算法(LFR)等磁盤調(diào)度算法。設(shè)計(jì)Redis緩存時(shí)候也可以借鑒。根據(jù)時(shí)間來的FIFO是最好實(shí)現(xiàn)的。且Redis在全局key支持過期策略。
        • 并且過期時(shí)間也要根據(jù)系統(tǒng)情況合理設(shè)置,如果硬件好點(diǎn)當(dāng)前可以稍微久一點(diǎn),但是過期時(shí)間過久或者過短可能都不太好,過短可能緩存命中率不高,而過久很可能造成很多冷門數(shù)據(jù)存儲(chǔ)在Redis中不釋放。

        ?

        數(shù)據(jù)一致性問題★

        上面其實(shí)提到數(shù)據(jù)一致性問題。如果對(duì)一致性要求極高那么不建議使用緩存。下面稍微梳理一下緩存的數(shù)據(jù)。
        在Redis緩存中經(jīng)常會(huì)遇到數(shù)據(jù)一致性問題。對(duì)于一個(gè)緩存,下面羅列幾種情況:

        read:從Redis中讀取,如果Redis中沒有,那么就從MySQL中獲取更新Redis緩存。
        下面流程圖描述常規(guī)場(chǎng)景,沒啥爭(zhēng)議:


        寫1:先更新數(shù)據(jù)庫(kù),再更新緩存(普通低并發(fā))


        更新數(shù)據(jù)庫(kù)信息,再更新Redis緩存。這是常規(guī)做法,緩存基于數(shù)據(jù)庫(kù),取自數(shù)據(jù)庫(kù)。

        但是其中可能遇到一些問題,例如上述如果更新緩存失敗(宕機(jī)等其他狀況),將會(huì)使得數(shù)據(jù)庫(kù)和Redis數(shù)據(jù)不一致。造成DB新數(shù)據(jù),緩存舊數(shù)據(jù)

        寫2:先刪除緩存,再寫入數(shù)據(jù)庫(kù)(低并發(fā)優(yōu)化)


        解決的問題

        這種情況能夠有效避免寫1中防止寫入Redis失敗的問題。將緩存刪除進(jìn)行更新。理想是讓下次訪問Redis為空去MySQL取得最新值到緩存中。但是這種情況僅限于低并發(fā)的場(chǎng)景中而不適用高并發(fā)場(chǎng)景。

        存在的問題

        寫2雖然能夠看似寫入Redis異常的問題??此戚^為好的解決方案但是在高并發(fā)的方案中其實(shí)還是有問題的。我們?cè)?strong style="box-sizing: border-box;color: rgb(233, 105, 0);font-size: inherit;line-height: inherit;">寫1討論過如果更新庫(kù)成功,緩存更新失敗會(huì)導(dǎo)致臟數(shù)據(jù)。我們理想是刪除緩存讓下一個(gè)線程訪問適合更新緩存。問題是:如果這下一個(gè)線程來的太早、太巧了呢?

        image-20201106191042265

        因?yàn)槎嗑€程你也不知道誰(shuí)先誰(shuí)后,誰(shuí)快誰(shuí)慢。如上圖所示情況,將會(huì)出現(xiàn)Redis緩存數(shù)據(jù)和MySQL不一致。當(dāng)然你可以對(duì)key進(jìn)行上鎖。但是鎖這種重量級(jí)的東西對(duì)并發(fā)功能影響太大,能不用鎖就別用!上述情況就高并發(fā)下依然會(huì)造成緩存是舊數(shù)據(jù),DB是新數(shù)據(jù)。并且如果緩存沒有過期這個(gè)問題會(huì)一直存在。

        寫3:延時(shí)雙刪策略


        這個(gè)就是延時(shí)雙刪策略,能過緩解在寫2中在更新MySQL過程中有讀的線程進(jìn)入造成Redis緩存與MySQL數(shù)據(jù)不一致。方法就是刪除緩存->更新緩存->延時(shí)(幾百ms)(可異步)再次刪除緩存。即使在更新緩存途中發(fā)生寫2的問題。造成數(shù)據(jù)不一致,但是延時(shí)(具體時(shí)間根據(jù)業(yè)務(wù)來,一般幾百ms)再次刪除也能很快的解決不一致。

        但是就寫的方案看其實(shí)還是有漏洞的,比如第二次刪除錯(cuò)誤、多寫多讀高并發(fā)情況下對(duì)MySQL訪問的壓力等等。當(dāng)然你可以選擇用MQ等消息隊(duì)列異步解決。其實(shí)實(shí)際的解決很難顧及到萬(wàn)無一失,所以不少大佬在設(shè)計(jì)這一環(huán)節(jié)可能會(huì)因?yàn)橐恍┘劼?huì)被噴。作為菜菜的筆者在這里就更不獻(xiàn)丑了,各位大佬歡迎貢獻(xiàn)你們的方案。

        寫4:直接操作緩存,定期寫入sql(適合高并發(fā))

        當(dāng)有一堆并發(fā)(寫)扔過來的后,前面幾個(gè)方案即使使用消息隊(duì)列異步通信但也很難給用戶一個(gè)舒適的體驗(yàn)。并且對(duì)大規(guī)模操作sql對(duì)系統(tǒng)也會(huì)造成不小的壓力。所以還有一種方案就是直接操作緩存,將緩存定期寫入sql。因?yàn)镽edis這種非關(guān)系數(shù)據(jù)庫(kù)又基于內(nèi)存操作KV相比傳統(tǒng)關(guān)系型要快很多。


        上面適用于高并發(fā)情況下業(yè)務(wù)設(shè)計(jì),這個(gè)時(shí)候以Redis數(shù)據(jù)為主,MySQL數(shù)據(jù)為輔助。定期插入(好像數(shù)據(jù)備份庫(kù)一樣)。當(dāng)然,這種高并發(fā)往往會(huì)因?yàn)闃I(yè)務(wù)對(duì)的順序等等可能有不同要求,可能還要借助消息隊(duì)列以及完成針對(duì)業(yè)務(wù)上對(duì)數(shù)據(jù)和順序可能會(huì)因?yàn)楦卟l(fā)、多線程帶來的不確定性和不穩(wěn)定性,提高業(yè)務(wù)可靠性。

        總之,越是高并發(fā)、越是對(duì)數(shù)據(jù)一致性要求高的方案在數(shù)據(jù)一致性的設(shè)計(jì)方案需要考慮和顧及越復(fù)雜、越多。上述也是筆者針對(duì)Redis數(shù)據(jù)一致性問題的學(xué)習(xí)和自我發(fā)散(胡扯)學(xué)習(xí)。如果有解釋理解不合理或者還請(qǐng)各位大佬指正!


        有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

        歡迎大家關(guān)注Java之道公眾號(hào)


        好文章,我在看??

        瀏覽 56
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 9l视频自拍九色9l视频成人 | 网站在线观看 | 五月天黄色视频 | 有栖花绯av一区二区 | 欧美一级电影在线 |