溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何解決項(xiàng)目實(shí)際應(yīng)用中redis緩存與數(shù)據(jù)庫(kù)一致性問(wèn)題

發(fā)布時(shí)間:2021-11-03 11:21:26 來(lái)源:億速云 閱讀:182 作者:小新 欄目:編程語(yǔ)言

這篇文章將為大家詳細(xì)講解有關(guān)如何解決項(xiàng)目實(shí)際應(yīng)用中redis緩存與數(shù)據(jù)庫(kù)一致性問(wèn)題,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

一、       問(wèn)題解決思路

能否做到先發(fā)出的請(qǐng)求一定先執(zhí)行完成呢?常見(jiàn)的思路是“串行化”

如何解決項(xiàng)目實(shí)際應(yīng)用中redis緩存與數(shù)據(jù)庫(kù)一致性問(wèn)題

上圖是一個(gè)service服務(wù)的上下游及服務(wù)內(nèi)部詳細(xì)展開(kāi),細(xì)節(jié)如下:

1service的上游是多個(gè)業(yè)務(wù)應(yīng)用,上游發(fā)起請(qǐng)求對(duì)同一個(gè)數(shù)據(jù)并發(fā)的進(jìn)行讀寫(xiě)操作,上例中并發(fā)進(jìn)行了一個(gè)uid=1的余額修改(寫(xiě))操作與uid=1的余額查詢(讀)操作

2service的下游是數(shù)據(jù)庫(kù)DB,假設(shè)只讀寫(xiě)一個(gè)DB

3)中間是服務(wù)層service,它又分為了這么幾個(gè)部分

3.1)最上層是任務(wù)隊(duì)列

3.2)中間是工作線程,每個(gè)工作線程完成實(shí)際的工作任務(wù),典型的工作任務(wù)是通過(guò)數(shù)據(jù)庫(kù)連接池讀寫(xiě)數(shù)據(jù)庫(kù)

3.3)最下層是數(shù)據(jù)庫(kù)連接池,所有的SQL語(yǔ)句都是通過(guò)數(shù)據(jù)庫(kù)連接池發(fā)往數(shù)據(jù)庫(kù)去執(zhí)行的

 

工作線程的典型工作流是這樣的:

void work_thread_routine(){

    Task t = TaskQueue.pop(); // 獲取任務(wù)

    // 任務(wù)邏輯處理,生成sql語(yǔ)句

    DBConnection c = CPool.GetDBConnection(); // DB連接池獲取一個(gè)DB連接

    c.execSQL(sql); // 通過(guò)DB連接執(zhí)行sql語(yǔ)句

    CPool.PutDBConnection(c); // DB連接放回DB連接池

}

 

提問(wèn):任務(wù)隊(duì)列其實(shí)已經(jīng)做了任務(wù)串行化的工作,能否保證任務(wù)不并發(fā)執(zhí)行?

答:不行,因?yàn)?span lang="EN-US">

11個(gè)服務(wù)有多個(gè)工作線程,串行彈出的任務(wù)會(huì)被并行執(zhí)行

21個(gè)服務(wù)有多個(gè)數(shù)據(jù)庫(kù)連接,每個(gè)工作線程獲取不同的數(shù)據(jù)庫(kù)連接會(huì)在DB層面并發(fā)執(zhí)行

 

提問(wèn):假設(shè)服務(wù)只部署一份,能否保證任務(wù)不并發(fā)執(zhí)行?

答:不行,原因同上

 

提問(wèn):假設(shè)1個(gè)服務(wù)只有1條數(shù)據(jù)庫(kù)連接,能否保證任務(wù)不并發(fā)執(zhí)行?

答:不行,因?yàn)?span lang="EN-US">

11個(gè)服務(wù)只有1條數(shù)據(jù)庫(kù)連接,只能保證在一個(gè)服務(wù)器上的請(qǐng)求在數(shù)據(jù)庫(kù)層面是串行執(zhí)行的

2)因?yàn)榉?wù)是分布式部署的,多個(gè)服務(wù)上的請(qǐng)求在數(shù)據(jù)庫(kù)層面仍可能是并發(fā)執(zhí)行的

 

提問(wèn):假設(shè)服務(wù)只部署一份,且1個(gè)服務(wù)只有1條連接,能否保證任務(wù)不并發(fā)執(zhí)行?

答:可以,全局來(lái)看請(qǐng)求是串行執(zhí)行的,吞吐量很低,并且服務(wù)無(wú)法保證可用性

 

完了,看似無(wú)望了,

1)任務(wù)隊(duì)列不能保證串行化

2)單服務(wù)多數(shù)據(jù)庫(kù)連接不能保證串行化

3)多服務(wù)單數(shù)據(jù)庫(kù)連接不能保證串行化

4)單服務(wù)單數(shù)據(jù)庫(kù)連接可能保證串行化,但吞吐量級(jí)低,且不能保證服務(wù)的可用性,幾乎不可行,那是否還有解?

 

退一步想,其實(shí)不需要讓全局的請(qǐng)求串行化,而只需要“讓同一個(gè)數(shù)據(jù)的訪問(wèn)能串行化”就行。

在一個(gè)服務(wù)內(nèi),如何做到“讓同一個(gè)數(shù)據(jù)的訪問(wèn)串行化”,只需要“讓同一個(gè)數(shù)據(jù)的訪問(wèn)通過(guò)同一條DB連接執(zhí)行”就行。

如何做到“讓同一個(gè)數(shù)據(jù)的訪問(wèn)通過(guò)同一條DB連接執(zhí)行”,只需要“在DB連接池層面稍微修改,按數(shù)據(jù)取連接即可”

獲取DB連接的CPool.GetDBConnection()【返回任何一個(gè)可用DB連接】改為

CPool.GetDBConnection(longid)【返回id取模相關(guān)聯(lián)的DB連接】

 

這個(gè)修改的好處是:

1)簡(jiǎn)單,只需要修改DB連接池實(shí)現(xiàn),以及DB連接獲取處

2)連接池的修改不需要關(guān)注業(yè)務(wù),傳入的id是什么含義連接池不關(guān)注,直接按照id取模返回DB連接即可

3)可以適用多種業(yè)務(wù)場(chǎng)景,取用戶數(shù)據(jù)業(yè)務(wù)傳入user-id取連接,取訂單數(shù)據(jù)業(yè)務(wù)傳入order-id取連接即可

這樣的話,就能夠保證同一個(gè)數(shù)據(jù)例如uid在數(shù)據(jù)庫(kù)層面的執(zhí)行一定是串行的

 

稍等稍等,服務(wù)可是部署了很多份的,上述方案只能保證同一個(gè)數(shù)據(jù)在一個(gè)服務(wù)上的訪問(wèn),在DB層面的執(zhí)行是串行化的,實(shí)際上服務(wù)是分布式部署的,在全局范圍內(nèi)的訪問(wèn)仍是并行的,怎么解決呢?能不能做到同一個(gè)數(shù)據(jù)的訪問(wèn)一定落到同一個(gè)服務(wù)呢?

 

能否做到同一個(gè)數(shù)據(jù)的訪問(wèn)落在同一個(gè)服務(wù)上?

 

上面分析了服務(wù)層service的上下游及內(nèi)部結(jié)構(gòu),再一起看一下應(yīng)用層上下游及內(nèi)部結(jié)構(gòu)

如何解決項(xiàng)目實(shí)際應(yīng)用中redis緩存與數(shù)據(jù)庫(kù)一致性問(wèn)題

上圖是一個(gè)業(yè)務(wù)應(yīng)用的上下游及服務(wù)內(nèi)部詳細(xì)展開(kāi),細(xì)節(jié)如下:

1)業(yè)務(wù)應(yīng)用的上游不確定是啥,可能是直接是http請(qǐng)求,可能也是一個(gè)服務(wù)的上游調(diào)用

2)業(yè)務(wù)應(yīng)用的下游是多個(gè)服務(wù)service

3)中間是業(yè)務(wù)應(yīng)用,它又分為了這么幾個(gè)部分

3.1)最上層是任務(wù)隊(duì)列【或許web-server例如tomcat幫你干了這個(gè)事情了】

3.2)中間是工作線程【或許web-server的工作線程或者cgi工作線程幫你干了線程分派這個(gè)事情了】,每個(gè)工作線程完成實(shí)際的業(yè)務(wù)任務(wù),典型的工作任務(wù)是通過(guò)服務(wù)連接池進(jìn)行RPC調(diào)用

3.3)最下層是服務(wù)連接池,所有的RPC調(diào)用都是通過(guò)服務(wù)連接池往下游服務(wù)去發(fā)包執(zhí)行的

 

工作線程的典型工作流是這樣的:

voidwork_thread_routine(){

Task t = TaskQueue.pop(); // 獲取任務(wù)

// 任務(wù)邏輯處理,組成一個(gè)網(wǎng)絡(luò)包packet,調(diào)用下游RPC接口

ServiceConnection c = CPool.GetServiceConnection(); // Service連接池獲取一個(gè)Service連接

c.Send(packet); // 通過(guò)Service連接發(fā)送報(bào)文執(zhí)行RPC請(qǐng)求

CPool.PutServiceConnection(c); // Service連接放回Service連接池

}

 

似曾相識(shí)吧?沒(méi)錯(cuò),只要對(duì)服務(wù)連接池進(jìn)行少量改動(dòng):

獲取Service連接的CPool.GetServiceConnection()【返回任何一個(gè)可用Service連接】改為

CPool.GetServiceConnection(longid)【返回id取模相關(guān)聯(lián)的Service連接】

這樣的話,就能夠保證同一個(gè)數(shù)據(jù)例如uid的請(qǐng)求落到同一個(gè)服務(wù)Service上。

                                                                                

由于數(shù)據(jù)庫(kù)層面的讀寫(xiě)并發(fā),引發(fā)的數(shù)據(jù)庫(kù)與緩存數(shù)據(jù)不一致的問(wèn)題(本質(zhì)是后發(fā)生的讀請(qǐng)求先返回了),可能通過(guò)兩個(gè)小的改動(dòng)解決:

1)修改服務(wù)Service連接池,id取模選取服務(wù)連接,能夠保證同一個(gè)數(shù)據(jù)的讀寫(xiě)都落在同一個(gè)后端服務(wù)上

2)修改數(shù)據(jù)庫(kù)DB連接池,id取模選取DB連接,能夠保證同一個(gè)數(shù)據(jù)的讀寫(xiě)在數(shù)據(jù)庫(kù)層面是串行的

關(guān)于“如何解決項(xiàng)目實(shí)際應(yīng)用中redis緩存與數(shù)據(jù)庫(kù)一致性問(wèn)題”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI