詳解分散式系統本質:“分治”和“冗餘”

站在全域性角度看,分散式系統的本質是什麼?其實說白了,就是兩點:“分治”和“冗餘”。

分治和冗餘使得分散式系統具備了核心價值,那麼它的價值是什麼?

分散式系統的價值

談到分散式系統的價值,可能就得從 1953 年說起了。在這一年,埃布·格羅希(Herb Grosch)提出了一個他觀察得出的規律——Grosch 定律。維基百科中是這樣描述的:

計算機效能隨著成本的平方而增加。如果計算機 A 的成本是計算機 B 的兩倍,那麼計算機 A 的速度應該是計算機 B 的四倍。

這一論斷與當時的大型機技術非常吻合,因而使得許多機構都盡其所能購買最大的單個大型機。其實,這也非常符合慣性思維,簡單粗暴。

然而,1965 年高登·摩爾(Gordon Moore)提出了摩爾定律。經過幾年的發展,人們發現摩爾定律的預測是符合現實的。這就意味著,集中式系統的運算能力每隔一段時間才能提升一倍。

那麼,到底要隔多久呢?這個“時間”有很多版本,比如廣為流傳的 18 個月版本,以及 Gordon Moore 本人堅持的 2 年版本。這裡我們不用太過糾結於實際情況到底是哪個“時間”版本,因為這其中隱含的意思更重要,即:

如果你的系統需承載的計算量的增長速度大於摩爾定律的預測,那麼在未來的某一個時間點,集中式系統將無法承載你所需的計算量

而這只是一個內在因素,真正推動分散式系統發展的催化劑是“經濟”因素。

人們發現,用廉價機器的集合組成的分散式系統,除了可以獲得超過 CPU 發展速度的效能外,花費更低,具有更好的價效比,並且還可以根據需要增加或者減少所需機器的數量。

所以,我們得到一個新結論:

無論是要以低價格獲得普通的效能,還是要以較高的價格獲得極高的效能,分散式系統都能夠滿足

並且受規模效應的影響,系統越大,價效比帶來的收益越高。

之後,進入到網際網路快速發展的時期,我們看到了分散式系統相比集中式系統的另一個更明顯的優勢:更高的可用性。例如,有 10 個能夠承載 10000 流量的相同的節點,如果其中的 2 個掛了,只要實際流量不超過 8000,系統依然能夠正常運轉。

而這一切的價值,都是建立在分散式系統的“分治”和“冗餘”之上的。

分治

分治,字面意思是“分而治之”,和我們的大腦在解決問題時的思考方式是一樣的。我們可以將整個過程分為 3 步:分解 -> 治理 -> 歸併。而分治思想的表現形式多樣,分層、分塊都是它的體現。

詳解分散式系統本質:“分治”和“冗餘”

這麼做的好處是:問題越小越容易被解決,並且,只要解決了所有子問題,父問題就都可以被解決了。但是,這麼做的時候,需要滿足一個最重要的條件:

不同分支上的子問題,不能相互依賴,需要各自獨立

。因為一旦包含了依賴關係,子問題和父問題之間就失去了可以被“歸併”的意義。在軟體開發領域,我們把這個概念稱為“

耦合度”和“內聚度”

,這兩個度量概念非常重要。

耦合度,指的是軟體模組之間相互依賴的程度。比如,每次呼叫方法 A 之後都需要同步呼叫方法 B,那麼此時方法 A 和 B 間的耦合度是高的。

內聚度,指的是模組內的元素具有的共同點的相似程度。比如,一個類中的多個方法有很多的共同之處,都是做支付相關的處理,那麼這個類的內聚度是高的。

內聚度通常與耦合度形成對比。低耦合通常與高內聚相關,反之亦然。

所以,當你打算進行分治的時候,耦合度和內聚度就是需要考慮的重點。

下面我們來看個例子,體會一下耦合度和內聚度的含義。(圖僅用於表達含義,切勿作其他參考)

假設一個電商平臺,為了應對更大的訪問量,需要拆分一個同時包含商品、促銷的系統。如果垂直拆分,是這樣:

詳解分散式系統本質:“分治”和“冗餘”

而如果水平拆分,則是這樣的:

詳解分散式系統本質:“分治”和“冗餘”

假如我們面對的場景僅僅是具體的商品詳情展示頁面,很顯然,用水平拆分的效果會更好。因為傳統的商品展示必然會同時展示促銷,所以,如果用水平拆分,一次請求即可獲取所有資料,內聚度非常高,並且此時模組間完全沒有耦合。而如果是垂直拆分的話,就需要同時請求 2 個節點的資料並進行組合,因此耦合度更高、內聚度更差。

但是,這樣的假設在真實的電商場景中是不存在的。從全域性來看,訂單、購物車、商品列表等許多其他場景也需要促銷資訊。並且這個時候我們發現引入了一些新的主體,諸如訂單、購物車、商品分類等等。這個時候,水平拆分帶來的好處越來越小,因為這樣只解決了多個耦合中的一個,低耦合喪失了。並且隨著商品和促銷與外界的關聯越來越多,必然有些場景僅僅涉及到商品和促銷的其中一個,但是處理的時候,我們還需要避免受到另一個的影響。如此,高內聚也喪失了。

這個時候,反而透過垂直拆分可以獲得更優的耦合度和內聚度,如下圖。

詳解分散式系統本質:“分治”和“冗餘”

這個時候,最高的耦合關係從原先的 6 降到了 4,並且商品和促銷各自的處理相互不受影響。

所以,你會發現隨著業務的變化,耦合度與內聚度也會發生變化。因此,及時地進行梳理和調整,可以避免系統的複雜度快速增長,才能最大程度的發揮“分治”帶來的好處。

綜上,分治可以簡化解題的難度,透過高內聚、低耦合的協作關係達到更好“效能與經濟比”,來承載更大的流量。而“冗餘”則帶來了系統可以 7*24 小時不間斷運作的希望。

冗餘

這裡的冗餘並不等同於程式碼的冗餘、無意義的重複勞動,而是我們有意去做的、人為增加的重複部分。其目的是容許在一定範圍內出現故障,而系統不受影響,如下圖。

詳解分散式系統本質:“分治”和“冗餘”

此時,我們可以將冗餘的節點部署在一個獨立的環境中。這個獨立的環境,可能是處於同一個區域網內的不同主機,也可能是在不同的區域網,還可能是在不同的機房。很顯然,它們能夠應對的故障範圍是逐步遞增的。

但是,像這種單純地為了備用而做的冗餘,最大的弊端是,如果沒有出現故障,那麼冗餘的這部分資源就白白浪費了,不能發揮任何作用。所以,我們才提出了諸如雙主多活、讀寫分離之類的概念,以提高資源利用率。

當然,除了軟體層面,硬體層面的冗餘也是同樣的道理。比如,磁碟陣列可以容忍幾塊之內磁碟損壞,而不會影響整體。

不過也很顯然,當故障影響範圍大於你冗餘的容量時,系統依然會掛。所以,既然你無法預知故障的發生情況,那麼做冗餘的時候需要平衡的另一端就是成本。相比更多的冗餘,追求更好的價效比更合理一些。

在我們生活中的冗餘也到處存在。比如,大部分的飛機和直升機的發動機都是偶數的,汽車中的電子控制系統的冗餘機制等。就好比替身與真身的關係,冗餘的就是替身。它可以和真身同時活動,也可以代替真身活動。

分治和冗餘講究的都是分散化,最終形成一個完整的系統還需要將它們“連線”起來。天下沒有免費的午餐,獲得分散式系統價值的同時,這個“再連線”的過程就是我們相比集中式系統要做的額外工作。

再連線

如何將拆分後的各個節點再次連線起來,從模式上來說,主要是去中心化與中心化之分。

詳解分散式系統本質:“分治”和“冗餘”

前者完全消除了中心節點故障帶來的全盤出錯的風險,卻帶來了更高的節點間協作成本。後者透過中心節點的集中式管理大大降低了協作成本,但是一旦中心節點故障則全盤出錯。

另外,從技術角度來說,如何選擇通訊協議和序列化機制,也是非常重要的。

詳解分散式系統本質:“分治”和“冗餘”

雖然很多通訊協議和序列化機制完全可以承擔任何場景的連線責任,但是不同的協議和序列化機制在適合的場景下才能發揮它最大的優勢。比如,需要更高效能的場景運用 TCP 協議優於 HTTP 協議;需要更高吞吐量的場景運用 UDP 協議優於 TCP 協議,等等。

總結

不管系統的規模發展到多大,合理地拆分,加上合適的連線方式,那麼至少會是一個運轉順暢、協作舒服的系統,至少能夠正常發揮分散式系統應有的價值。

如今,我們發現分散式系統還可以發揮更多的作用。

比如,只要基於一個統一的上層通訊協議,其下層的不同節點可以運用不同的技術棧來發揮不同技術各自的優勢,比如用 Go 來應對高併發場景,用 Python 來做資料分析等。

再比如,提高交付的速度,如下圖。

詳解分散式系統本質:“分治”和“冗餘”

透過分配不同的團隊、人員同時進行多個模組的開發,雖然總的耗時增加了,但是整體的交付速度加快了。

事物最本質的東西是恆定的、不變的,可以指引我們的工作方向。分散式系統的本質也是這樣。例如,這樣的“分治”方案耦合度和內聚度是否最優,這樣做“冗餘”帶來的收益是否成本能夠接受。只要持續帶著這些思考,我們就好像拿著一杆秤,基於它,我們就可以去衡量各種變數影響,然後作權衡。比如成本、時間、人員、效能、易維護等等。也可以基於它去判斷什麼樣的框架、元件、協議更適合當前的環境。

需要不斷的權衡,也意味著分散式系統的設計工作一定不是一步到位,而是循序漸進的。因為過分為未知的未來做更多的考量,最終可能都會打水漂。所以,建議以多考慮 1~2 步為宜。假如以你所在的團隊中對重大技術升級的頻率來作為參考的話,做可供 2 個升級週期的設計,花一個升級週期的時間先實現第一階段,下個階段可以選擇直接實現剩下部分,也可繼續進行 2 個升級週期設計,開啟一個迴圈,持續迭代,並且不斷修正方向以更貼近現實的發展,就如下圖這樣。

詳解分散式系統本質:“分治”和“冗餘”