緩存與數(shù)據(jù)庫一致性保證

全是干貨!本文主要討論這么幾個(gè)問題:

(1)啥時(shí)候數(shù)據(jù)庫和緩存中的數(shù)據(jù)會(huì)不一致

(2)不一致優(yōu)化思路

(3)如何保證數(shù)據(jù)庫與緩存的一致性

****一、需求緣起****

當(dāng)數(shù)據(jù)發(fā)生變化時(shí)柏卤,“先淘汰緩存县貌,再修改數(shù)據(jù)庫”這個(gè)點(diǎn)是大家討論的最多的缸夹。

得出這個(gè)結(jié)論的依據(jù)是痪寻,由于操作緩存與操作數(shù)據(jù)庫不是原子的螺句,非常有可能出現(xiàn)執(zhí)行失敗。

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy

假設(shè)先寫數(shù)據(jù)庫橡类,再淘汰緩存:第一步寫數(shù)據(jù)庫操作成功蛇尚,第二步淘汰緩存失敗,則會(huì)出現(xiàn)DB中是新數(shù)據(jù)顾画,Cache中是舊數(shù)據(jù)取劫,數(shù)據(jù)不一致【如上圖:db中是新數(shù)據(jù),cache中是舊數(shù)據(jù)】亲雪。

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy

假設(shè)先淘汰緩存勇凭,再寫數(shù)據(jù)庫:第一步淘汰緩存成功,第二步寫數(shù)據(jù)庫失敗义辕,則只會(huì)引發(fā)一次Cache miss【如上圖:cache中無數(shù)據(jù),db中是舊數(shù)據(jù)】寓盗。

結(jié)論:先淘汰緩存灌砖,再寫數(shù)據(jù)庫。

這里的討論的點(diǎn)是“先操作緩存傀蚌,在寫數(shù)據(jù)庫成功之前基显,如果有讀請(qǐng)求發(fā)生,可能導(dǎo)致舊數(shù)據(jù)入緩存善炫,引發(fā)數(shù)據(jù)不一致”撩幽,這就是本文要討論的主題。

二箩艺、為什么數(shù)據(jù)會(huì)不一致

回想一下對(duì)緩存窜醉、數(shù)據(jù)庫進(jìn)行讀寫操作的流程。

寫流程:

(1)先淘汰cache

(2)再寫db

讀流程:

(1)先讀cache艺谆,如果數(shù)據(jù)命中hit則返回

(2)如果數(shù)據(jù)未命中miss則讀db

(3)將db中讀取出來的數(shù)據(jù)入緩存

什么情況下可能出現(xiàn)緩存和數(shù)據(jù)庫中數(shù)據(jù)不一致呢榨惰?

640?tp=webp&wxfrom=5&wx_lazy=1

在分布式環(huán)境下,數(shù)據(jù)的讀寫都是并發(fā)的静汤,上游有多個(gè)應(yīng)用琅催,通過一個(gè)服務(wù)的多個(gè)部署(為了保證可用性,一定是部署多份的)虫给,對(duì)同一個(gè)數(shù)據(jù)進(jìn)行讀寫藤抡,在數(shù)據(jù)庫層面并發(fā)的讀寫并不能保證完成順序,也就是說后發(fā)出的讀請(qǐng)求很可能先完成(讀出臟數(shù)據(jù)):

(a)發(fā)生了寫請(qǐng)求A抹估,A的第一步淘汰了cache(如上圖中的1)

(b)A的第二步寫數(shù)據(jù)庫缠黍,發(fā)出修改請(qǐng)求(如上圖中的2)

(c)發(fā)生了讀請(qǐng)求B,B的第一步讀取cache棋蚌,發(fā)現(xiàn)cache中是空的(如上圖中的步驟3)

(d)B的第二步讀取數(shù)據(jù)庫嫁佳,發(fā)出讀取請(qǐng)求挨队,此時(shí)A****的第二步寫數(shù)據(jù)還沒完成,讀出了一個(gè)臟數(shù)據(jù)放入cache(如上圖中的步驟4)

即在數(shù)據(jù)庫層面蒿往,后發(fā)出的請(qǐng)求4比先發(fā)出的請(qǐng)求2先完成了盛垦,讀出了臟數(shù)據(jù),臟數(shù)據(jù)又入了緩存瓤漏,緩存與數(shù)據(jù)庫中的數(shù)據(jù)不一致出現(xiàn)了

三腾夯、不一致優(yōu)化思路

能否做到先發(fā)出的請(qǐng)求一定先執(zhí)行完成呢?常見的思路是“串行化”蔬充,今天將和大家一起探討“串行化”這個(gè)點(diǎn)蝶俱。

先一起細(xì)看一下,在一個(gè)服務(wù)中饥漫,并發(fā)的多個(gè)讀寫SQL一般是怎么執(zhí)行的

640?tp=webp&wxfrom=5&wx_lazy=1

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

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

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

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

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

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

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

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

void work_thread_routine(){

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

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

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

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

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

}

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

答:不行煌贴,因?yàn)?/p>

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

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

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

答:不行,原因同上

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

答:不行,因?yàn)?/p>

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

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

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

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

完了,看似無望了但狭,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

640?tp=webp&wxfrom=5&wx_lazy=1

上圖是一個(gè)業(yè)務(wù)應(yīng)用的上下游及服務(wù)內(nèi)部詳細(xì)展開,細(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ù)是通過服務(wù)連接池進(jìn)行RPC調(diào)用

(3.3)最下層是服務(wù)連接池垢箕,所有的RPC調(diào)用都是通過服務(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); // 通過Service連接發(fā)送報(bào)文執(zhí)行RPC請(qǐng)求

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

}

似曾相識(shí)吧条获?沒錯(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上月匣。

五、總結(jié)

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

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

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

六寓免、遺留問題

提問:取模訪問服務(wù)是否會(huì)影響服務(wù)的可用性癣诱?

答:不會(huì),當(dāng)有下游服務(wù)掛掉的時(shí)候袜香,服務(wù)連接池能夠檢測到連接的可用性撕予,取模時(shí)要把不可用的服務(wù)連接排除掉。

提問:取模訪問服務(wù)****與 ****取模訪問DB****蜈首,是否會(huì)影響各連接上請(qǐng)求的負(fù)載均衡实抡?

答:不會(huì),只要數(shù)據(jù)訪問id是均衡的欢策,從全局來看吆寨,由id取模獲取各連接的概率也是均等的,即負(fù)載是均衡的踩寇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末啄清,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子俺孙,更是在濱河造成了極大的恐慌辣卒,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鼠冕,死亡現(xiàn)場離奇詭異添寺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)懈费,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門计露,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事票罐〔嫒ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵该押,是天一觀的道長疗杉。 經(jīng)常有香客問我,道長蚕礼,這世上最難降的妖魔是什么烟具? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮奠蹬,結(jié)果婚禮上朝聋,老公的妹妹穿的比我還像新娘。我一直安慰自己囤躁,他們只是感情好冀痕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著狸演,像睡著了一般言蛇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宵距,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天腊尚,我揣著相機(jī)與錄音,去河邊找鬼满哪。 笑死跟伏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翩瓜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼携龟,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼兔跌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起峡蟋,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤坟桅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蕊蝗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仅乓,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年蓬戚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夸楣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖豫喧,靈堂內(nèi)的尸體忽然破棺而出石洗,到底是詐尸還是另有隱情,我是刑警寧澤紧显,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布讲衫,位于F島的核電站,受9級(jí)特大地震影響孵班,放射性物質(zhì)發(fā)生泄漏涉兽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一篙程、第九天 我趴在偏房一處隱蔽的房頂上張望枷畏。 院中可真熱鬧,春花似錦房午、人聲如沸矿辽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽袋倔。三九已至,卻和暖如春折柠,著一層夾襖步出監(jiān)牢的瞬間宾娜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工扇售, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留前塔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓承冰,卻偏偏與公主長得像华弓,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子困乒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容