面試的時候,總會遇到這麼一個場景。 1. 場景分析 面試官:你們的服務的QPS是多少? 我:我們的服務高峰期訪問量還挺大的,大約是3萬吧。 面試官:這麼大的訪問量,你們的伺服器能撐住嗎?有加緩存嗎? 我:有的,我們使用了Redis做緩存,介面優先查詢緩存,緩存不存在,才訪問資料庫。這樣可以減少資料庫 ...
面試的時候,總會遇到這麼一個場景。
1. 場景分析
面試官:你們的服務的QPS是多少?
我:我們的服務高峰期訪問量還挺大的,大約是3萬吧。
面試官:這麼大的訪問量,你們的伺服器能撐住嗎?有加緩存嗎?
我:有的,我們使用了Redis做緩存,介面優先查詢緩存,緩存不存在,才訪問資料庫。這樣可以減少資料庫訪問壓力,加快查詢效率。
面試官:一份數據存儲在兩個地方,更新數據的時候,你們是怎麼保證數據的一致性的?
看到了吧,好的面試官一般不直接問你數據一致性的解決方案,而是循循善誘,結合具體的使用場景,再問你解決方法。如果你沒做過這方面,沒有線上的實戰經驗,一般很難回答的有條理性、有思考性。
保證數據一致性,一般有這4種方法:
- 先更新緩存,再更新資料庫。
- 先更新資料庫,再更新緩存。
- 先刪除緩存,再更新資料庫。
- 先更新資料庫,再刪除緩存。
每種方案都詳細的討論一下:
2. 解決方案
2.1 先更新緩存,再更新資料庫
如果同時來了兩個併發寫請求,執行過程是這樣的:
- 寫請求1更新緩存,設置age為1
- 寫請求2更新緩存,設置age為2
- 寫請求2更新資料庫,設置age為2
- 寫請求1更新資料庫,設置age為1
執行結果就是,緩存里age被設置2,資料庫里的age被設置成1,導致數據不一致,此方案不可行。
2.2 先更新資料庫,再更新緩存
如果同時來了兩個併發寫請求,執行過程是這樣的:
- 寫請求1更新資料庫,設置age為1
- 寫請求2更新資料庫,設置age為2
- 寫請求2更新緩存,設置age為2
- 寫請求1更新緩存,設置age為1
執行結果就是,資料庫里age被設置2,緩存里的age被設置成1,導致數據不一致,此方案不可行。
2.3 先刪除緩存,再更新資料庫
如果同時來了兩個併發讀寫請求,執行過程是這樣的:
- 寫請求刪除了緩存
- 讀請求查詢緩存沒數據,然後查詢資料庫,再把數據寫到緩存中
- 寫請求更新資料庫
執行結果是,緩存中是舊數據,而資料庫里是新數據,導致數據不一致,此方案不可行。
2.4 先更新資料庫,再刪除緩存
這種方案,在併發寫的時候,不會出問題。因為都是先更新資料庫再刪除緩存,不會出現不一致的情況。
但是在併發讀寫的時候,還是有可能出現數據不一致。
- 讀請求查詢緩存沒數據,然後查詢資料庫
- 寫請求更新資料庫,刪除緩存
- 讀請求回寫緩存
執行結果是,緩存中是舊數據,而資料庫里是新數據,導致數據不一致。
其實這種情況出現的概率很低,寫緩存比寫資料庫快出幾個量級,讀寫緩存都是記憶體操作,速度非常快。
遇到了這種極端場景,我們也需要做一下兜底方案,緩存都要設置過期時間。這種方案屬於數據的弱一致性和最終一致性,而不是強一致性。
3. 總結與思考
有讀者可能會好奇,為什麼不在更新緩存和資料庫方法上加上事務註解,實現強一致性,這麼哪種方案都不會有問題。
是的,當我們的服務只在一臺機器上,加本地事務是可行的。但是工作中,我們會把一個服務部署到幾十臺、上百台機器上,有時候為了應對更極端的查詢請求,又在Redis緩存加一層本地緩存,這時候我們再用本地事務是不起作用的。
一份數據在多台機器上,存在多個副本,為了實現強一致性,我們也可以使用分散式事務。這樣一來更新緩存操作將會變得非常複雜,得不償失。
但是在另外的一些場景,比如更新訂單狀態、更新用戶資產,這種場景,我們無論付出多大代價也要實現數據的強一致性,具體實現方案一般有以下幾種:
- 二階段提交
- TCC
- 本地消息表
- MQ事務消息
- 分散式事務中間件
下篇文章咱們再一起詳細的分析這幾種方案優缺點。