1. 程式人生 > >分散式初探——講透分散式系統一致性模型

分散式初探——講透分散式系統一致性模型

本文始發於個人公眾號:TechFlow

在計算機系統的領域,一致性可以說是一個高頻詞,可能出現的場景很多。從分散式系統到資料庫的事務,都有它的身影。

之前我們在介紹資料庫事務的時候,談到過事務的一致性。在資料庫當中,一致性是一種目的,不是一種手段。資料庫希望控制事務的原子性、隔離性和永續性來保證資料的一致性。這裡的一致性更多的指的是實際和我們觀念的一致。也就是說結果都在我們預期之內。而在分散式系統當中,一致性有另外的含義,一個是多份資料副本之間的一致性問題,另一個是多階段提交的一致性問題。我們今天先來聊聊副本一致性問題。

這個問題出現的原因很簡單,因為分散式系統當中,資料往往會有多個副本。如果是一臺資料庫處理所有的資料請求,那麼通過ACID四原則,基本可以保證資料的一致性。而多個副本就需要保證資料會有多份拷貝。這就帶來了同步的問題,因為我們幾乎沒有辦法保證可以同時更新所有機器當中的包括備份所有資料。尤其是當這些機器分佈在全國各地甚至是世界各地的時候,由於網路延遲,即使我在同一時間給所有機器傳送了更新資料的請求,也不能保證這些請求被響應的時間保持一致。只要存在時間差,就會存在某些機器之間的資料不一致的情況。也就是說,在分散式系統當中的一致性,指的是資料一致性。

這其實是一個兩難問題,為了解決流量過大的壓力問題,我們設計了分散式系統。但是分散式系統又會帶來資料多份拷貝不同步違反一致性的問題,我們既不能容忍資料出錯,也不能放棄分散式系統,唯一的辦法就是採取一些措施,來最大可能地降低這個問題的影響力。

多種多樣的一致性模型,就是這些措施的體現。讓我們從最簡單的嚴格一致性說起。


### 嚴格一致性

嚴格一致性是最理想的情況,如果我們每次請求一個數據,不管什麼情況下,我們都能獲得它的最後一次改動的結果。很遺憾的是,嚴格一致性是不可能實現的。

不可能實現的原因很簡單,因為多臺機器之間的資料同步需要時間,無論這個時間多小,它都是確定存在的。只要存在,就不可能實現嚴格一致性。舉個簡單的例子,我們有A和B兩臺機器。在t時刻,A機器修改了某條資料,在1毫秒之前,B機器收到了一條查詢該資料的請求。當B執行這個查詢的時候,A機器已經修改完成,那麼究竟B查詢到的值應該是什麼呢?是A修改之前的還是修改之後的呢?在A機器看來,B的查詢發生在它修改之後,可是B機器看來卻恰恰相反,A修改值發生了在它收到請求之後。如果我們要保證嚴格一致性,那麼究竟什麼結果才是對的呢?

當然上面這個例子只是最極端的情況,一般只會在理論上發生,但是通過對極端情況的分析,我們也可以看得出來,嚴格一致性是不可能實現的。


強一致性與弱一致性


資料不一致出現的根本原因在於多臺機器更新資料的時間差,我們更新多臺機器,總有先有後,很難保證完全同步。根據同步資料時採用同步還是非同步策略,又可以將一致性分為強一致性與弱一致性。

使用同步策略更新資料時,我們每次請求發給主節點,主節點收到資料之後使用同步更新的策略將資料傳送給從節點。當所有的從節點更新成功之後,主節點會更新資料的狀態,使它生效,之後返回response給使用者,告知更新成功。

顯然,通過同步更新資料的策略下,一致性的保障更強。如果我們在主機上做好隔離措施,比如在更新結束之前,使用者不能發起下一次更新,那麼儘可能地保證資料一致性不出問題。但是這種做法也有很大的弊端,最大的弊端就在於使用同步更新的操作,而且要所有從庫都更新成功才能返回,這樣的時間開銷非常大。最關鍵的一個缺點在於,如果一個從庫宕機,那麼主庫就不可能更新所有的從庫,那麼新來的請求永遠不會更新,這顯然是不能接受的。

和強一致性對應的是弱一致性,我們不採用同步策略來更新資料,而採用非同步更新的方式。好處也很明顯,同步改成了非同步,時間消耗大大縮減。但是問題也很明顯,除了非同步本身帶來的問題之外,由於多個副本之間的資料更新發生不同時,如果連續多次訪問落到了不同的副本上,就會出現多次訪問的結果不一致的情況。

本質上來說分散式系統的一致性模型,只有強弱兩種。只不過在這兩種基礎的模型上,針對許多可能出現的問題還會進行相應的優化。總體上而言,分散式系統對於效能的要求要高於一致性,所以大多分散式系統的一致性模型,還是基於弱一致性設計的。下面就來列舉幾種,比較經典的弱一致性模型的優化方案。


讀寫一致性


讀寫一致性在日常中經常遇見,比如在某論壇當中,使用者回覆了某個帖子。但是當用戶重新整理的時候,可能會出現這個回覆消失的情況。使用者會陷入困擾,不知道這個回覆究竟有沒有成功。

會發生這種情況的原因也很簡單,因為使用者重新整理的時候,訪問的從庫可能還沒有獲取到使用者回覆的資料,所以顯示的結果當中自然就沒有使用者剛剛回復的內容。在這種情況下,我們需要保證讀寫一致性。也就是說使用者讀取自己寫入結果的一致性,保證使用者永遠能夠第一時間看到自己更新的內容。比如我們發一條朋友圈,朋友圈的內容是不是第一時間被朋友看見不重要,但是一定要顯示在自己的列表上。

那如何實現呢?

方案有好幾種,一種方案是對於一些特定的內容我們每次都去主庫讀取。比如我們讀取帖子當中回覆資訊的時候,永遠都去主庫讀取。但是這樣的問題也很明顯,可能會導致主庫的壓力過大。另一種方案是我們設定一個更新時間視窗,在剛剛更新的一段時間內,我們預設都從主庫讀取,過了這個視窗之後,我們會挑選最近有過更新的從庫進行讀取。

還有一種更好的方案是我們直接記錄使用者更新的時間戳,在請求的時候把這個時間戳帶上,凡是最後更新時間小於這個時間戳的從庫都不予以響應。也就是說只有包含使用者寫入這個更新的庫可以響應這個請求,就可以保證實現使用者端的讀寫性一致了。


單調讀


單調讀解決的是最經典的弱一致性的不一致問題,出現的場景也很簡單。由於主從節點更新資料的時間不一致,導致使用者在不停地重新整理的時候,有時候能刷出來,再次重新整理之後會發現資料不見了,再重新整理又可能再刷出來,就好像遇見靈異事件一樣。我記得以前微博或者人人就存在這個問題,單調讀就是針對的這個場景,可以保證不會出現這種情況。

解決的方法其實很簡單,就是根據使用者ID計算一個hash值,再通過hash值對映到機器。同一個使用者不管怎麼重新整理,都只會被對映到同一臺機器上。這樣就保證了不會讀到其他從庫的內容,帶來使用者體驗不好的影響。當然,這只是一種解決方案,其他的解決方法還有很多,這裡不一一討論。


因果一致性


因果一致性針對的資料之間邏輯上的因果問題,舉個例子,比如說使用者在知乎裡提問題和回答問題。想要回答問題,必須要保證有對應的問題。也就是說一定是先有問題,再有的回答。可是問題和回答並不一定儲存在同一個節點上,很有可能出現問題存入節點A,回答存入節點B的情況。因為存在同步延遲,所以就可能出現查詢的使用者只看得到回答,卻找不到對應問題的情況,違反了事物之間的因果性。

為了解決這個問題,一種方案是在寫入的時候遵循某種邏輯順序,那麼在讀取的時候,就可以保證不會出現因果錯亂的情況。但問題是,很多因果性並不想問題和回答這麼明顯,一些隱藏的因果性可能很難被輕易判斷,就需要引入更高深的技術,感興趣的同學可以去搜索一下向量時鐘深入瞭解。


最終一致性


聽名字這種方案似乎很厲害,其實最終一致性是所有分散式一致性模型當中最弱的。可以認為是沒有任何優化的“最”弱一致性,它的意思是說,我不考慮所有的中間狀態的影響,只保證當沒有新的更新之後,經過一段時間之後,最終系統內所有副本的資料是正確的。

有大神打了這麼一個比方,就好像你去點星巴克。你並不知道星巴克什麼時候做好,你在做好之前去拿,拿到的結果是錯的。但是你知道,經過一段時間之後,你一定可以拿到你想要的。至於星巴克做好需要多久,往往沒有定論,可以是幾百分之一秒,也可以是幾個小時。

聽起來這個方案很不靠譜,在確定它可行之前,我們還想問幾個問題,首先,系統能不能保證一段時間是多久?如果天荒地老怎麼辦?其次,因為最終才能收斂,那麼在收斂之前,多個副本之間的值可能都不同,究竟又該以哪個為準?

好在,這兩個問題都能回答。對於第一個問題,答案是系統沒辦法確定究竟需要多久收斂,但是可以確定最大的收斂時間。有點像是物理學上的半衰期,我們不知道一個粒子究竟需要多久衰變,但是可以確定足夠多的粒子當中的一半衰變所需要的時間。第二個問題更好回答,當有多份資料出現的時候,通常的做法是選擇其中時間戳較大的,也就是說出現較晚的值。

雖然最終一致性看起來很不靠譜,但是它最大程度上保證了系統的併發能力,也因此,在高併發的場景下,它也是使用最廣的一致性模型。

到這裡為止,分散式系統當中常見的一致性模型就介紹完了。分散式系統有一個很大的特點,就是我們看專業名詞的時候往往雲裡霧裡,不知所云。但是當我們去了解它背後的設計理念與出現的原因,就能發現它的有趣。衷心希望大家都能從中發現自己的樂趣,都有學有收穫。

如果喜歡本文,請順手點個關注吧,你們的支援是我最大的動力。