1. 程式人生 > >快取與資料庫不一致的解決方案

快取與資料庫不一致的解決方案

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

1)啥時候資料庫和快取中的資料會不一致

2)不一致優化思路

3)如何保證資料庫與快取的一致性

一、需求緣起

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

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


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


假設先淘汰快取,再寫資料庫:第一步淘汰快取成功,第二步寫資料庫失敗,則只會引發一次

Cache miss【如上圖:cache中無資料,db中是舊資料】。

結論:先淘汰快取,再寫資料庫。

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

二、為什麼資料會不一致

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

寫流程:

1)先淘汰cache

2)再寫db

讀流程:

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

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

3)將db中讀取出來的資料入快取

什麼情況下可能出現快取和資料庫中資料不一致呢?


在分散式環境下,資料的讀寫都是併發的

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

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

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

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

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

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

三、不一致優化思路

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

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


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

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

2service的下游是資料庫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連線池

}

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

答:不行,因為

11個服務有多個工作執行緒,序列彈出的任務會被並行執行

21個服務有多個數據庫連線,每個工作執行緒獲取不同的資料庫連線會在DB層面併發執行

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

答:不行,原因同上

提問:假設1個服務只有1條資料庫連線,能否保證任務不併發執行?

答:不行,因為

11個服務只有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取模獲取各連線的概率也是均等的,即負載是均衡的。

相關推薦

快取資料庫一致解決方案

本文主要討論這麼幾個問題: (1)啥時候資料庫和快取中的資料會不一致 (2)不一致優化思路 (3)如何保證資料庫與快取的一致性 一、需求緣起 上一篇《快取架構設計細節二三事》(點選檢視)引起了廣泛的討論,其中有一個結論:當資料發生變化時,“先淘汰快取,再修改資

Redis快取維護方案-考慮資料庫快取雙寫、redis和本地資料庫事務一致性、資料庫主從同步延遲的情況怎麼解決快取資料庫一致

一般常用的快取方案有兩種: 第一種 讀的時候,先讀快取,快取沒有的話,讀資料庫,取出資料後放入快取,同時返回響應。 更新的時候,

c# java base64 一致解決方案

不一致的問題不是編碼的問題  而是json字串的問題通常我們會json 巢狀  我們先來看連個字串 {"contentType":"","httpMethod":"POST","paramMap":"{\"keyword\":\"華為\"}","url":"https:\/\/bizapi.jd.com\/a

android studio 開發中啟動android項目報錯sdk版本一致解決方案

技術分享 發現 依賴 adl 目的 clas studio ima 需要 安卓項目開發中新建項目後再run‘的時候發現報錯com.android.support:appcompat-v7依賴報錯 查看下build.gredle所配置的參數: 打開項目的bui

PHP的strtotime()函式轉換的時間戳和實際時間一致解決方案

做一個時間區間搜尋的功能時,遇到一個問題,使用strtotime()轉換時間去比較時,發現搜尋不到,前端傳遞的是   但是PHP轉換後時間戳為1540732715,相當於   與實際時間相差8小時,所以搜尋不到的原因就在此。解決問題很簡單。 修改P

NER 中word數量和tag數量一致解決方案以及tf.string_split用法

句子中有中文空格 而tf.string_split(source, delimiter=’ ‘)預設是英文空格 導致NER 中word數量和tag數量不一致。 tf.string_split(source, delimiter=' ') source是一維陣列,用於將一組字串

Mysql查詢的資料和顯示的資料時區一致解決方案

前言:在設定mysql時區生效後,查詢的資料顯示的時區是正確的,但是navicat中顯示的資料有差別 一、檢視MySQL當前時區、如果不是北京時間-東八區、要重新設定下或者修改Mysql的配置檔案、新增一條北京時區的配置 show variables like "%time_zone%"; se

sql-front部分資料庫顯示解決方案

點選“mysql front ”圖示,進入“開啟登陸資訊”對話方塊,點選要登陸的資料庫(如localhost,這是你填的資料庫資訊)“名稱”,然後點選“屬性”按鈕, 在彈出的“xxxx的配置”(如localhost的配置)對話方塊中,點選“註冊”按鈕,在出現的對話方塊中點選“資料庫”欄目後

Redis和DB資料一致解決方案

大多情況下,我們使用快取都是這樣的策略:先讀快取,讀取不到就讀資料庫然後同步到快取中。 問題出現場景 問題就是在併發訪問中,不論是先寫庫,再刪除快取;還是先刪快取,再寫庫,都有可能出現數據不一致的情況 1、在併發中是無法保證讀寫的先後順序的,如果刪掉了

Redis資料庫資料同步解決方案

資料庫同步到Redis 我們大多傾向於使用這種方式,也就是將資料庫中的變化同步到Redis,這種更加可靠。Redis在這裡只是做快取。 方案1 做快取,就要遵循快取的語義規定: 讀:讀快取redis,沒有,讀mysql,並將mysql的值寫入

spring中@RequestBody,bean中屬性名和json一致解決方案

spring中@RequestBody,對應的bean中屬性名不一致解決方案參考資料:https://blog.csdn.net/renxyz/article/details/44734235sprin

快取資料庫雙寫,一致問題及解決方案

面試題 如何保證快取與資料庫的雙寫一致性? 面試官心理分析 你只要用快取,就可能會涉及到快取與資料庫雙儲存雙寫,你只要是

IE6瀏覽器有哪些常見的bug,缺陷或者標準一致的地方,如何解決

block 自動調整 script font aaa 方向 image ie6 浮動 IE6不支持min-height,解決辦法使用css hack: .target { min-height: 100px; height: auto !import

VMware Workstation Device/Credential Guard 相容 解決方案

win10專業版官方解決方案 https://kb.vmware.com/s/article/2146361 win10家庭版解決方案 win10家庭版本身是不支援Hyper-V服務的,但是如果是“win10預覽體驗家庭版”,會在“服務”中發現有Hyper-V服務。解決方案:

php-m phpinfo 一致解決辦法

  1.檢視PHP當前載入的php.ini檔案地址,在控制檯輸入以下命令: php -i | grep php.ini 2.控制檯返回的資訊 Configuration File (php.ini) Path => /Applications/MAMP/bin/php

Redis快取資料庫一致性方案

使用Redis快取的模式的有很多種,下面就逐一介紹。 一、資料庫和redis分別處理不同的資料型別 資料庫處理要求強一致實時性的資料,例如金融資料、交易資料; redis處理不要求強一致實時性的資料,例如網站最熱貼排行榜; 二、Cache-A

解決快取資料庫一致性問題

1、引用快取的好處         1)提高效能;2)減緩資料庫壓力;3)提高系統併發處理能力 2、引用快取的問題         1)處理邏輯變得複雜;2)使用不當,容易引起快取和資料庫資料不一致的

redis中快取的資料資料庫資料一致性的方案

方式1:資料庫儲存資料,redis不persist redis啟動後,從資料庫載入資料 不要求強一致實時性的讀請求,都由redis處理 要求強一致實時性的讀請求,由資料庫處理 寫請求有2種處理方式,由資料庫處理 - 應用先寫道資料庫,然後更新redis - 應用先寫道資料庫

redis中快取的資料資料庫資料一致性的方案(好)

方式1:資料庫儲存資料,redis不persistredis啟動後,從資料庫載入資料不要求強一致實時性的讀請求,都由redis處理要求強一致實時性的讀請求,由資料庫處理寫請求有2種處理方式,由資料庫處理- 應用先寫道資料庫,然後更新redis- 應用先寫道資料庫,然後其它da

新建VS工程填坑:解決方案專案在同一目錄

A.新建專案->空工程 B.新增依賴庫 1、屬性->C/C++->附加包含目錄 注:新增標頭檔案目錄,必須指向子資料夾 2、屬性->連結器->常規->附加庫目錄 注:新增exp和lib(輸出庫檔案、輸入庫檔案)的目錄 3、屬性->連結器->輸入-&g