聊透分布式系统一致性
一、強一致性
?
一致性大家庭中,雖然細分種類很多,但是實際上只有兩大類,其中之一就是強一致性,其具體包含了嚴格一致性(也叫原子一致性或者線性一致性)和順序一致性。
-
嚴格(原子/線性)一致性
嚴格一致性代表著,當數據更新后,所有Client的讀寫都是在數據更新的基礎上。如下圖所示,我們假設每份數據有三個副本,分別落到三個節點上。當Client1嘗試將X的值置為1時,嚴格一致性要求當Client1完成更新操作以后,所有Client都要在最新值的基礎上進行讀寫,這里的Client10讀取到的值是x=1,在同一時刻Client100的更新操作也是在x=1的基礎上進行x+=1操作,在下一個時刻Client1000讀到的任意一個副本,X的值都會是2。
此時你會發現一切似乎都是很完美的。但是仔細想想,嚴格一致性的背后有什么潛臺詞呢?
1)數據同步復制
嚴格一致性代表著,所有數據在寫入操作的時候是同步復制的,即寫多副本都成功,才算寫入成功,HDFS是不是就是最好的例子。并且具有原子性,對于寫入操作來說,結果要么寫成功,要么寫失敗,不存在中間狀態。這也是為什么稱為原子一致性的原因。
2)嚴格一致性不考慮客戶端
在網上有很多人在解釋一致性時,嘗試從客戶端和服務端分別解析;但是在上一篇我們分析CAP的時候,也有提到不要帶Client玩,那么究竟誰對誰錯呢?
這里我們再來看下,在考慮分布式系統的一致性時我們更關注什么?是多個Client發送讀寫請求到達后端的時間和先后順序嘛?不,我們真正關注的是每個請求,對應服務端完成時間點的先后順序。還是參考上面的例子,Client1000讀取到x=2這個結果,實際上是以Client100這個寫操作完成為基礎的,如果Client100寫操作一直不完成,那么強一致性要求Client1000讀取到的X是1而不是2。因此我們更需要關注的是完成操作的具體時間點,而不是操作發起的時間點,對于一致性來說考慮Client的意義就不大了。當然在同一時刻多個Client操作的冪等性還是一定要保證的。
換個角度,Client才是一致性需求的甲方,不是嘛。而分布式系統端作為乙方,只能滿足甲方需求,或者拒絕甲方需求,而不是要求甲方作出任何改變!
3)?基于嚴格的全局時鐘
上面我們提到操作行為完成時間點的順序是十分重要的。再仔細看一下上面舉例的內容,相信你會發現一切的行為都在時間這個維度上,行為順序是:Client1更新x=1 -> Client10讀取x=1/Client100在x=1的基礎上更新x+=1 -> Client1000讀取x=2。所以每個操作都是在前一個操作完成的基礎上進行的,在分布式服務中,需要有一個基準時間來衡量每個操作行為的順序。此時你會問了,機器上不是都有NTP做時間校準嘛?現在的問題就是無法保證每一臺機器的時間都是絕對相同的。
舉個例子,數據D2所在的節點相比D1節點時間提前了幾秒,當Client1的更新請求完成后(用時500ms),Client10的請求開始執行并完成,如果將機器時間作為基準就會發現Client10的讀取操作竟然在Client1的更新操作之前,這顯然是違背強一致性的。
我們一般我們會如何保證全局時鐘?這里簡單聊聊三種種常見解法。
混合邏輯時鐘 Hybrid Logic Clock
在混合邏輯時鐘中同時比較了節點本地的物理時間、邏輯時間和其他節點發送消息中的物理時間。kudu和Cockroachdb也都是使用的這個方法,HLC雖然加上了物理時間,但是仍然強依賴于機器的NTP,并不是嚴格意義上精確的時鐘,在HLC中需要為時鐘定義一個邊界,比如kudu中定義了maximum check error(最大時鐘錯誤),如果本地NTP沒啟動,kudu在啟動的時候就直接失敗了;如果誤差超過了maximum check error,依舊會報錯,這也就意味著當超過HLC所設定的偏差邊界,HLC就不能正常工作了。
在看HLC的實現邏輯時,發現步驟比較多,邏輯時間存在的意義就是在時間比對時,當作中間值或者備份值。這里由于不是本篇重點,不再贅述了,感興趣的小伙伴可以看下論文:https://cse.buffalo.edu/tech-reports/2014-04.pdf。
True Time
上一篇文章中也有講到,谷歌依賴強大的基建實力降低了網絡分區發生的概率,而面全局時鐘問題,Google的Spanner采用的是GPS + Atomic Clock(原子時鐘的含義可以百度一下)這種純硬件方式來對集群的機器進行校時,其精度在ms級別,這里我們用ε來表示時間精度的誤差,時間的精度誤差的范圍也就是[t-ε,t+ε]這個范圍之間。此時回到上面的操作中,按照此種方式Client10的機器時間相比Client1的機器時間最多提前或者滯后2個ε的時間,因此Spanner引入了commit wait time這個方案,說白了就是操作執行完成后多等一會,等過了這個精度誤差的范圍自然就全局有序了,Google將精度誤差控制在幾ms級別,當然對于Spanner這種全球性、跨地域的分布式系統來說,多等個幾ms問題也不大。
但很遺憾,Google的這套硬件解決方案,并沒有開源出來,適用性有限,我們就望梅止渴吧。
授時中心 TimeStamp Oracle
在生活中也有”授時中心“的存在,貌似在陜西,具體位置可以查查,他的作用是什么呢?為中國各種基建、系統提供了一個準確的時間,避免誤差。(個人YY:萬一打起仗,總不能因為其他國家干擾了基準時間,咱們所有基建就癱瘓吧,因此授時中心的意義巨大。)?
在分布式服務中,實際上也有類似的方案。這里以Tidb舉例,Tidb為了校準時間,就是采用了TSO這個方案,對于Tidb來說所有行為事件統一由PD節點分配時間,雖然這種方案會產生非常高頻的互相調用,但是按照Tidb官方介紹,在同IDC網絡環境下網絡傳輸開銷非常低,只有0.xms。當然如果面對跨IDC的網絡,就可以嘗試將PD節點和Tidb節點混部(Tikv依然需要獨立部署,為的是存儲計算分離)。這就不需要走網絡的開銷了,當然如果是Client端跨IDC的話,還是沒有太好的方法。
-
順序一致性
上面我們說到了嚴格一致性(線性/原子),想做到全局時鐘下的全局絕對有序是有難度的,HLC實現比較復雜,谷歌的原子鐘+GPS又沒有開源出來,TSO又增加了系統的復雜度。想實現全局時鐘好難!
這里我們是否可以退一步,舍棄“時間”這個有序的計數器,嘗試構造一個更好維護的計數器,不保證全局行為絕對有序,只保證分布式服務全局相對有序?
如下圖所示,D1先后更新了x=1,x=2,D3先后更新了a=1,a=2。當Client讀取到D2節點時,按照順序一致性要求,所有節點的操作相對順序都是相同的,一定是x=1在x=2之前,a=1在a=2之前,下圖舉例的是順序一致性的其中一種情況。
1)邏輯時鐘 Logic Clock
邏輯時鐘Logic Clock,這個名字你陌生的話,或許他的另一個名字Lamport Timestamp會讓你浮想連連,如果你還是沒啥印象的話,那么Paxos你是否知道呢?(如果做大數據的你不知道paxos,那你需要好好補習下基礎了😄。)Lamport Timestamp和Paxos的作者實際就是同一個人——Lamport,Paxos在分布式系統一致性算法中是教義般的存在,由此可見邏輯時鐘Logic Clock也不會差到哪里。
如上圖所示,在Logic Clock算法中,每臺機器內部都會記錄一個時間戳,以A、B舉例 ,其初始值是0。每當機器A、B執行了一個事件,那么他們各自的時間戳就會+1,當A向B發起通信的時候,A會附帶自己的時間戳,比如<message,timestamp>,這時B會比較消息中的時間戳和本地時間戳,選取最大值max(local timestamp,message timestamp)更改本地的時間戳。通過這種方式構建了一個新的計數器,實現了全局相對的順序性即本地:timestamp +=1 && 遠端:選擇max timestamp,即使各個節點時間不同,但是操作執行是有序的;但是問題也很明顯,就是新的計數器無法和實際時間做匹配。
2)ZooKeeper是怎樣的一致性?
這里我們暫時不談共識算法Paxos(請持續關注后續文章)。我們直接來說說ZooKeeper的一致性,網上很多資料都說zk是最終一致性,很抱歉,zk是強一致性的,并且是強一致性中的順序一致性。為什么不說zk是最終一致性的原因呢?
<1> 就好比你考試考了99分,你非得說考了60分。這不止代表著分數,最終一致性拉低zk的檔次,直接從強力檔拉到了弱雞檔。
<2> 此外你參考本篇中最開頭的圖來看,zk是CP系統。從CAP反向來推導,如果zk是最終一致性,那么意味著是AP系統,但是zk在選舉的時候實際上是不可用的,也就是A達不到,此時就發生矛盾了。
ZooKeeper中的Zxid實際上就是邏輯時鐘Logic Clock中自造的計數器,可以發現,以Zxid為基準可以做到所有節點識別到的操作順序都是相同的。就像上面說的Zxid是是無法跟實際時間相對應的。網上很多資料說,ZooKeeper的寫入是線性一致性,對此我是不認同的,涉及到的兩段式提交(后面的文章會講到),不帶回滾卻會主動同步,某種意義上講是線性的,但是當commmit階段時,發生了網絡分區了,這時數據就不會同步到異常節點上,如果此時再有一個Client訪問到這個節點,此時讀到的就是舊數據了。其寫失敗意味著所有節點都寫失敗,而寫成功卻意味著不一定所有節點都寫成功。因此個人認為只能算順序一致性,而不能算線性一致性。
3)舉個栗子
如果對順序一致性還沒有什么概念,那么你可以理解為分布式系統就是微信朋友圈,當我我發了一個朋友圈之后,周杰倫看到我的朋友圈后開始評論,緊接著王力宏又評論了😄。順序一致性代表著當我們共同的朋友林俊杰看朋友圈時,一定是先看到周杰倫的評論,再看到王力宏的評論,當朋友吳亦凡看到時,可能只能看到周杰倫的,但是王力宏的評論也許會遲到,但永遠不會缺席。并且永遠不會存在王力宏的評論在前,而周杰倫的評論在后的情況。
三、弱一致性
一致性家族中的另一大類就是弱一致性了,相比強一致性,弱一致性在保證可用性的基礎上,允許出現數據不一致的情況。
-
為什么一致性會分強弱?
按照上一篇CAP的理論,CAP中的一致性。而在實際需求中,會發現越來越多的分布式系統都更看重可用性A,而不是一致性C。高可用性要求我們的系統,面對Client的讀寫請求在規定時間內必須返回結果,并且不會報錯。
面對不同場景,各類AP系統也只能在弱一致性方面下大功夫。也因此出現了非常多的弱一致性模型,下面我們就逐個來分析下。
-
最終一致性
上一篇我們在BASE中所提到的就是最終一致性,其并不保證在任意時刻、任意節點上的同一份數據都是完全一致的,但是隨著時間的遷移,不同節點上的同一份數據總是在向一致的方向變化。其中數據不一致的時間段,稱為非一致性窗口。簡單說,就是數據寫入的一段時間后,各節點的數據最終會達到一致狀態。如下圖所示。
-
因果一致性
因果一致性強調了數據之間的因果關系,初始狀態X的值為1,當D1將X的更新為2后,這時D1會和D2節點進行通信,將<D1,D2,X,2>這條消息,傳遞給D2,后續D2節點所有的讀寫都是在新值基礎上進行的。此時D3節點還是會在非一致窗口內,讀到X的舊值1。因此D1更新數據,并通信D2為因,D2接受通信,修改本地狀態為果,此為因果一致性。
-
“讀你所寫”一致性
什么是讀你所寫一致性呢?顧名思義,讀到你所寫的數據,在因果一致性中,定義的是集群節點間更新通知的機制,而對于“讀你所寫”一致性來說,也是沿用這個思路,不過這里通信的是本節點,影響的也是本機后續的的所有讀寫請求,因此讀你所寫一致性,其實是因果一致性的特殊場景,由于原理相似,下面直接放圖。
-
會話一致性
會話一致性以會話為基礎,通常應用于類數據庫系統的場景。每一個會話相當于一個獨立的訪問鏈接,在這個會話中,可以執行很多具體的讀寫操作,且會話之間是相對獨立的。
如下圖所示,會話一致性要求,只要會話1還存在,會話內就保證“讀你所寫”一致性。如果D1的會話1終止,那么當重新建立會話2時,在不一致窗口內即使是同一個節點的會話,也不保證數據一致。因此會話一致性又是讀你所寫一致性的特例(是不是感覺有點亂,一個特例又一個特例的,沒關系,最后的時候我們聊下各種一致性的關系,加油!)
-
單調一致性
單調一致性,分為讀、寫兩個層面,即單調讀一致性和單調寫一致性。實際上很好理解,單調讀保證了整個系統讀必須有序的,假設X的值遞增,讀X=2一定是在讀X=1之后。而單調寫保證了寫必須有序的,對于整個系統來說寫X=2一定是在寫X=1之后。這里比較好理解,我就不正圖了(畫圖畫吐了!)。個人認為單調寫一致性應該是大部分分布式服務的基礎。否則如果你寫入順序無法保證,你想想都會感到痛苦。
三、各種一致性的關系和常見誤區
-
各種一致性的關系
如下圖所示,一圖厘清一致性之間的關系,如果此時相關概念有忘記,可以再重新看下上面的解析~
-
誤區一:強一致性=線性一致性?
網上非常多的資料動不動就強一致性就是線性一致性,或者把順序一致性放到和強一致性相同的級別來比對,這些其實都是錯誤的,正如我們上面看到的,強一致性包含線性一致性和順序一致性。也因此強一致性不一定是線性一致性,但線性一致性一定是強一致性。
-
誤區二:一致性并不是非黑即白的
強弱一致性之間并不是涇渭分明的,一個分布式系統可能同時滿足其中的一種或者多種。咱們還是回到ZooKeeper上,其同時符合順序一致性,最終一致性。一致性種類滿足的越多,符合的場景也就越多,當然其復雜度也就越高。從這一點我們就可以看出,沒有一個能滿足所有場景的分布式系統,如果真的有,那么他的數據一致性邏輯必然十分復雜。
總結
以上是生活随笔為你收集整理的聊透分布式系统一致性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MYSQL 常用命令大全整理
- 下一篇: 高大上必备!D3.js对产品的贡献度剖析