分布式数据一致性的探讨
很多分布式系統都會用緩存,本地緩存或者NoSQL,亦或者兩者都上。在筆者認為,只要用到緩存系統,就必然會存在數據不一致的時間段,這個時間有長有短而已,當然最終數據是要一致的,也就是達成最終一致性(eventually consistency),這里簡單聊聊筆者的理解。
單數據庫
單數據庫的讀取問題,包含了臟讀,臟寫,不可重復讀,幻讀等等。這和數據庫的隔離級別有關,因為這篇文章主要聊的是分布式系統的數據一致性,所以這里先留坑,只要知道單數據庫也可能存在數據不一致的情況。
多份副本
這是最常見的解決方案,一般稱為主從(master/slave)復制。主從復制的基本概念是正常情況下,主庫只接受寫操作,從庫從主庫復制數據,通過拉取預寫日志或者復制行等方式復制數據。有單領導者,多領導者,無無領導者幾種情況,先說一說單主復制的模式,這里先不考慮NoSQL緩存和分區。
我在抖音上更新頭像,向抖音服務器發出請求,服務器收到通知,往主庫更新我的新頭像,這個時候服務器有兩個選擇,一個是主庫寫入數據成功就給我發通知告訴我操作成功,另一個是等待從庫從主庫復制數據成功了再給我發通知。第一個方案速度快,只要寫一個庫就返回,肯定比多個快嘛,但是萬一這時候主庫掛了,我再刷新抖音,發現我剛更新的頭像沒了。這時候作為用戶我就不開心了,抖音你告訴我更新成功了,為什么查看的時候卻沒有,你這個大騙子。這時候第二種方案的優勢就出來了,只有從庫復制成功了才會返回通知,如果主庫在復制的時候掛掉了,從庫沒有復制到數據就返回失敗,作為用戶的我只當網絡問題,再更新一次好了。但是這樣的問題在于返回結果慢,如果從庫很多呢,還遍布全國各地呢,更新一個頭像需要幾分鐘,那這體驗太差了。所以正常應該是一個從庫同步復制,其余從庫異步復制,采取這樣的的折中方案來保證效率以及數據安全。恩,似乎很完美,曾經筆者剛學分布式的時候也這樣認為,直到接下來的問題出現。
我更新完頭像,服務器將我的頭像更新到主庫A,從庫B上,返回結果成功。我隨即去查看頭像,很正常的對吧,向抖音服務器發請求,服務器收到請求將我的讀請求發到了從庫C上!靠,抖音這個騙子,又沒有。這就是讀己之寫一致性(read-your-writes consistency)問題。有幾個解決方案:
緩存
為了減少數據庫的訪問壓力,分布式系統很多都會有一份內存數據庫做緩存,存放熱數據,我們拿微博大V來舉例。
筆者猜一下微博的流程,微博大V發了一個微博,服務器寫完主庫從庫,還會存放一份復制到Redis集群(假設)上,因為follow的人太多,這么多的讀取請求必須分流(好像記得一些熱點數據還會主動push到信息流上,熱門話題應該是這樣)當然筆者這樣的小透明是不需要放到Redis上的。
問題在于數據庫更新后,對緩存的處理上。筆者公司的系統的做法是,更新完數據庫,再將新數據寫入Redis,不關心成功與否,后期當然會在其他地方保證數據一致。那么如果寫入Redis失敗了,其他用戶甚至自己再去查看數據,服務器首先會去Redis拉取數據,因為Redis上有對應的數據,所以服務器讀取并返回給用戶,這個時候數據庫是新數據,Redis是舊數據,數據不一致就發生了。
這個問題筆者看過一個解決方案,就是先刪除緩存,再更新數據庫,這樣即使更新數據庫失敗了,因為緩存沒有數據,其他用戶讀取緩存沒有,去數據庫讀取的也是舊數據,不會造成數據不一致。但是在高并發的情況下,有可能會發生B用戶在A用戶刪除緩存更新數據庫的中間,完成讀取數據庫舊數據,并在A用戶寫新數據回緩存后,再次寫入舊數據到緩存里的情況,也就是數據庫是新數據,Redis是舊數據。
筆者后來想過這個方案還有一個問題,回到微博大V發微博的例子上,這個時候寫操作肯定不是高并發,大V更新了微博,服務器如果按照剛才的方案先刪除緩存再寫數據庫, 如果緩存失效了,那么對數據的讀取都會壓倒數據庫上,認為造成了緩存穿透的嚴重后果,這個時候寧愿不一致,而不要緩存不存在的情況發生。
對于很熱門的數據,服務器還會采取本地緩存的方案,可以采用發布訂閱版本號的方式。如果大V先發了微博,服務器在本地緩存了一份復制并訂閱這條微博。大V編輯微博內容,主服務器在寫入數據庫成功后發布這條微博的新版本號,副服務器就知道自己本地的內容過期了,需要去Redis或者數據庫更新內容。甚至主服務器完全可以發布微博編輯后的內容,這樣比其他服務器還要去拿緩存或者讀取數據庫還要快。
總結
以上是生活随笔為你收集整理的分布式数据一致性的探讨的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 桩基施工市场现状研究分析报告-
- 下一篇: Selenium中的EC模块