天天看點

緩存與資料庫一緻性保證

本文主要讨論這麼幾個問題:

(1)啥時候資料庫和緩存中的資料會不一緻

(2)不一緻優化思路

(3)如何保證資料庫與緩存的一緻性

一、需求緣起

上一篇《緩存架構設計細節二三事》(點選檢視)引起了廣泛的讨論,其中有一個結論:當資料發生變化時,“先淘汰緩存,再修改資料庫”這個點是大家讨論的最多的。

上篇文章得出這個結論的依據是,由于操作緩存與操作資料庫不是原子的,非常有可能出現執行失敗。

緩存與資料庫一緻性保證

假設先寫資料庫,再淘汰緩存:第一步寫資料庫操作成功,第二步淘汰緩存失敗,則會出現DB中是新資料,Cache中是舊資料,資料不一緻【如上圖:db中是新資料,cache中是舊資料】。

緩存與資料庫一緻性保證

假設先淘汰緩存,再寫資料庫:第一步淘汰緩存成功,第二步寫資料庫失敗,則隻會引發一次Cache miss【如上圖:cache中無資料,db中是舊資料】。

結論:先淘汰緩存,再寫資料庫。

引發大家熱烈讨論的點是“先操作緩存,在寫資料庫成功之前,如果有讀請求發生,可能導緻舊資料入緩存,引發資料不一緻”,這就是本文要讨論的主題。

二、為什麼資料會不一緻

回顧一下上一篇文章中對緩存、資料庫進行讀寫操作的流程。

寫流程:

(1)先淘汰cache

(2)再寫db

讀流程:

(1)先讀cache,如果資料命中hit則傳回

(2)如果資料未命中miss則讀db

(3)将db中讀取出來的資料入緩存

什麼情況下可能出現緩存和資料庫中資料不一緻呢?

緩存與資料庫一緻性保證

在分布式環境下,資料的讀寫都是并發的,上遊有多個應用,通過一個服務的多個部署(為了保證可用性,一定是部署多份的),對同一個資料進行讀寫,在資料庫層面并發的讀寫并不能保證完成順序,也就是說後發出的讀請求很可能先完成(讀出髒資料):

(a)發生了寫請求A,A的第一步淘汰了cache(如上圖中的1)

(b)A的第二步寫資料庫,發出修改請求(如上圖中的2)

(c)發生了讀請求B,B的第一步讀取cache,發現cache中是空的(如上圖中的步驟3)

(d)B的第二步讀取資料庫,發出讀取請求,此時A的第二步寫資料還沒完成,讀出了一個髒資料放入cache(如上圖中的步驟4)

即在資料庫層面,後發出的請求4比先發出的請求2先完成了,讀出了髒資料,髒資料又入了緩存,緩存與資料庫中的資料不一緻出現了

三、不一緻優化思路

能否做到先發出的請求一定先執行完成呢?常見的思路是“串行化”,今天将和大家一起探讨“串行化”這個點。

先一起細看一下,在一個服務中,并發的多個讀寫SQL一般是怎麼執行的

緩存與資料庫一緻性保證

上圖是一個service服務的上下遊及服務内部詳細展開,細節如下:

(1)service的上遊是多個業務應用,上遊發起請求對同一個資料并發的進行讀寫操作,上例中并發進行了一個uid=1的餘額修改(寫)操作與uid=1的餘額查詢(讀)操作

(2)service的下遊是資料庫DB,假設隻讀寫一個DB

(3)中間是服務層service,它又分為了這麼幾個部分

(3.1)最上層是任務隊列

(3.2)中間是工作線程,每個工作線程完成實際的工作任務,典型的工作任務是通過資料庫連接配接池讀寫資料庫

(3.3)最下層是資料庫連接配接池,所有的SQL語句都是通過資料庫連接配接池發往資料庫去執行的

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

void work_thread_routine(){

Task t = TaskQueue.pop(); // 擷取任務

// 任務邏輯處理,生成sql語句

DBConnection c = CPool.GetDBConnection(); // 從DB連接配接池擷取一個DB連接配接

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

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

}           

提問:任務隊列其實已經做了任務串行化的工作,能否保證任務不并發執行?

答:不行,因為

(1)1個服務有多個工作線程,串行彈出的任務會被并行執行

(2)1個服務有多個資料庫連接配接,每個工作線程擷取不同的資料庫連接配接會在DB層面并發執行

提問:假設服務隻部署一份,能否保證任務不并發執行?

答:不行,原因同上

提問:假設1個服務隻有1條資料庫連接配接,能否保證任務不并發執行?

(1)1個服務隻有1條資料庫連接配接,隻能保證在一個伺服器上的請求在資料庫層面是串行執行的

(2)因為服務是分布式部署的,多個服務上的請求在資料庫層面仍可能是并發執行的

提問:假設服務隻部署一份,且1個服務隻有1條連接配接,能否保證任務不并發執行?

答:可以,全局來看請求是串行執行的,吞吐量很低,并且服務無法保證可用性

完了,看似無望了,

1)任務隊列不能保證串行化

2)單服務多資料庫連接配接不能保證串行化

3)多服務單資料庫連接配接不能保證串行化

4)單服務單資料庫連接配接可能保證串行化,但吞吐量級低,且不能保證服務的可用性,幾乎不可行,那是否還有解?

退一步想,其實不需要讓全局的請求串行化,而隻需要“讓同一個資料的通路能串行化”就行。

在一個服務内,如何做到“讓同一個資料的通路串行化”,隻需要“讓同一個資料的通路通過同一條DB連接配接執行”就行。

如何做到“讓同一個資料的通路通過同一條DB連接配接執行”,隻需要“在DB連接配接池層面稍微修改,按資料取連接配接即可”

擷取DB連接配接的CPool.GetDBConnection()【傳回任何一個可用DB連接配接】改為

CPool.GetDBConnection(longid)【傳回id取模相關聯的DB連接配接】

這個修改的好處是:

(1)簡單,隻需要修改DB連接配接池實作,以及DB連接配接擷取處

(2)連接配接池的修改不需要關注業務,傳入的id是什麼含義連接配接池不關注,直接按照id取模傳回DB連接配接即可

(3)可以适用多種業務場景,取使用者資料業務傳入user-id取連接配接,取訂單資料業務傳入order-id取連接配接即可

這樣的話,就能夠保證同一個資料例如uid在資料庫層面的執行一定是串行的

稍等稍等,服務可是部署了很多份的,上述方案隻能保證同一個資料在一個服務上的通路,在DB層面的執行是串行化的,實際上服務是分布式部署的,在全局範圍内的通路仍是并行的,怎麼解決呢?能不能做到同一個資料的通路一定落到同一個服務呢?

四、能否做到同一個資料的通路落在同一個服務上?

上面分析了服務層service的上下遊及内部結構,再一起看一下應用層上下遊及内部結構

緩存與資料庫一緻性保證

上圖是一個業務應用的上下遊及服務内部詳細展開,細節如下:

(1)業務應用的上遊不确定是啥,可能是直接是http請求,可能也是一個服務的上遊調用

(2)業務應用的下遊是多個服務service

(3)中間是業務應用,它又分為了這麼幾個部分

(3.1)最上層是任務隊列【或許web-server例如tomcat幫你幹了這個事情了】

(3.2)中間是工作線程【或許web-server的工作線程或者cgi工作線程幫你幹了線程分派這個事情了】,每個工作線程完成實際的業務任務,典型的工作任務是通過服務連接配接池進行RPC調用

(3.3)最下層是服務連接配接池,所有的RPC調用都是通過服務連接配接池往下遊服務去發包執行的

voidwork_thread_routine(){

Task t = TaskQueue.pop(); // 擷取任務

// 任務邏輯處理,組成一個網絡包packet,調用下遊RPC接口

ServiceConnection c = CPool.GetServiceConnection(); // 從Service連接配接池擷取一個Service連接配接

c.Send(packet); // 通過Service連接配接發送封包執行RPC請求

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

}
           

似曾相識吧?沒錯,隻要對服務連接配接池進行少量改動:

擷取Service連接配接的CPool.GetServiceConnection()【傳回任何一個可用Service連接配接】改為

CPool.GetServiceConnection(longid)【傳回id取模相關聯的Service連接配接】

這樣的話,就能夠保證同一個資料例如uid的請求落到同一個服務Service上。

五、總結

由于資料庫層面的讀寫并發,引發的資料庫與緩存資料不一緻的問題(本質是後發生的讀請求先傳回了),可能通過兩個小的改動解決:

(1)修改服務Service連接配接池,id取模選取服務連接配接,能夠保證同一個資料的讀寫都落在同一個後端服務上

(2)修改資料庫DB連接配接池,id取模選取DB連接配接,能夠保證同一個資料的讀寫在資料庫層面是串行的

六、遺留問題

提問:取模通路服務是否會影響服務的可用性?

答:不會,當有下遊服務挂掉的時候,服務連接配接池能夠檢測到連接配接的可用性,取模時要把不可用的服務連接配接排除掉。

提問:取模通路服務與 取模通路DB,是否會影響各連接配接上請求的負載均衡?

答:不會,隻要資料通路id是均衡的,從全局來看,由id取模擷取各連接配接的機率也是均等的,即負載是均衡的。

提問:要是資料庫的架構做了主從同步,讀寫分離:寫請求寫主庫,讀請求讀從庫也有可能導緻緩存中進入髒資料呀,這種情況怎麼解決呢(讀寫請求根本不落在同一個DB上,并且讀寫DB有同步時延)?

答:下一篇文章和大家分享。

如果你有烘培、攝影、繪畫、音樂、舞蹈、健身、瑜伽、早教、程式設計教育訓練、交友的需求或者技能,長按二維碼關注“美好到家”,點選“達人報名”,成為達人,賺取外快。(不好意思,答應了小馬哥幫他宣傳的,大夥幫忙關注下,幫忙完成KPI哈)

==【完】==

緩存與資料庫一緻性保證