圖解|搞定分散式?程式設計師進階之路

MageByte 發表於 2021-03-04

程式設計是一門藝術,它的魅力在於創造。

65 哥已經工作兩年了,一直做著簡單重複的程式設計工作,活活熬成了一個只會 CRUD 的打工 boy。

65 哥:總是聽大佬講分散式分散式,什麼才是分散式系統呢?

分散式系統是一個硬體或軟體系統分佈在不同的網路計算機上,彼此之間僅僅通過訊息傳遞進行通訊和協調的系統。在一個分散式系統中,一組獨立的計算機展現給使用者的是一個統一的整體,就好像是一個系統似的。系統擁有多種通用的物理和邏輯資源,可以動態的分配任務,分散的物理和邏輯資源通過計算機網路實現資訊交換。

65 哥:巴拉巴拉,能不能講點人話。俺聽不懂。

那好,下面我們從平常最熟悉的事物開始理解分散式系統如何出現,發展的,並經過實踐總結通用的理論,這些理論成為指導我們如何設計更完善的分散式系統的基礎。

從此篇文章,你將學習到以下知識:

圖解|搞定分散式?程式設計師進階之路

Web 應用的擴充套件

為什麼會出現分散式應用?

65 哥:這個我也不清楚啊,以前我寫的 web 應用都是直接扔進 Tomcat 中,啟動 Tomcat 就可以訪問了,這肯定不是分散式應用。

圖解|搞定分散式?程式設計師進階之路

嗯,我們就從大家最熟悉 web 後臺應用講起,以前我們的系統訪問量小,業務也不復雜,一臺伺服器一個應用就可以處理所有的業務請求了,後來我們公司發達了,訪問量上去了,業務也擴充了,雖然老闆依舊沒有給我們加工資,確總是埋怨我們系統不穩定,扛不住大併發,是可忍孰不可也,加錢,我們要升級。

如果我們的伺服器可以無限新增配置,那麼一切效能問題都不是問題。

為提高系統處理能力,我們首先想到的擴充套件方式就是升級系統配置,8 核 cpu 升級為 32 核,64 核,記憶體 64G 升級為 128G,256G,頻寬上萬兆,十萬兆,這就叫做垂直擴充套件。但這樣的擴充套件終將無法持續下去,原因如下。

  1. 單機系統的處理能力最終會達到瓶頸
  2. 單機升級的邊際成本將越來越大

worth

沒有什麼可以攔住我們程式設計打工人的步伐。

俗話說,系統撐不住了,就加伺服器,一臺不行就加兩臺。

垂直擴充套件到達技術瓶頸或投入產出比超過預期,我們可以考慮通過增加伺服器數量來提高併發能力,這種方式就是水平擴充套件

圖解|搞定分散式?程式設計師進階之路

系統拆分

65 哥:哦,這就是分散式系統了?這麼簡單的麼。

我勒個呵呵,哪有那麼簡單,在水平擴充套件中,我們增加了伺服器數量,但是如何讓這些伺服器像一個整體一樣對外提供穩定有效的服務才是關鍵。既然已經有了多臺伺服器,我們就要考慮如何將系統部署到到不同的節點上去。

65 哥:這還不簡單,我將我的 SpringBoot 專案部署到多臺伺服器上,前面加個 nginx 就可以了,現在我們的系統都是這樣的,穩定高效 perfect。給你畫個架構圖(小聲,這個我在學校時就會了。)

圖解|搞定分散式?程式設計師進階之路

哪有什麼歲月靜好,只不過是有人在為你負重前行。上面你所認為的簡單,其實有兩個原因:

  1. 系統分離不徹底,很重要的一點,就是依然在共享一個資料庫
  2. 在這個成熟的體系中,有太多成熟的中介軟體在為我們服務,比如上面提到的 nginx

系統拆分也有兩種方式,垂直拆分水平拆分,注意,這裡和上面提到的垂直擴充套件水平擴充套件不是處理同一個問題的。(65 哥:哈哈,我知道,世間萬物不外乎縱橫二字)。

系統的垂直拆分,就是將相同的系統部署多套,所有的節點並沒有任何不同,角色和功能都一樣,它們各自分擔一部分功能請求,這樣整個系統的處理能力的上升了。

從處理 web 請求上來看,垂直拆分的每個節點都處理一個完整的請求,每個節點都承擔一部分請求量;

從資料儲存的角度看,每個資料節點都儲存相同的業務資料,每個節點儲存一部分資料。

圖解|搞定分散式?程式設計師進階之路

系統的水平拆分,就是將系統按不同模組或角色拆分,不同的模組處理不同的事情。

從 web 請求上來看,需要多個相互依賴的系統配合完成一個請求,每個節點處理的需求不一致;

從資料儲存角度上來看,每個資料節點都儲存著各自業務模組相關的資料,它們的資料都不一樣。

圖解|搞定分散式?程式設計師進階之路

上面垂直拆分之後各個節點組成的就是一個叢集,而水平拆分各個節點就是分散式。這就是叢集分散式的區別。叢集除了上面提到的可以提高併發處理能力外,還可以保證系統的高可用,當一部分節點失效後,整個系統依舊可以提供完整的服務。分散式也一樣,除了提高併發能力,解耦系統,使系統邊界更清晰,系統功能更內聚也是其一大好處,所以在實際的系統中我們往往這兩種方式同時都在使用,而且我們常常提及的分散式系統其實是包含著叢集的概念在裡面的。

split

分散式目標

歸納和演繹是人類理性的基石,學習和思考就是不斷的歸納過去的經驗,從而得到普遍的規律,然後將得之的規律演繹於其他事物,用於指導更好的實踐過程。

圖解|搞定分散式?程式設計師進階之路

上面我們講解了分散式系統的由來,現在我們回顧和總結一下這個過程。我們引入分散式系統必然是基於現實的需求和目標而來的。

65 哥:那分散式的目標什麼呢?

分散式就是為了滿足以下目標而設計的:

  • Transparency: 透明性,即使用者是不關心繫統背後的分散式的,無論系統是分散式的還是單機的,對使用者來說都應該是透明的,使用者只需要關心繫統可用的能力。這裡的透明性就包括以下方面:
    • 訪問透明性:固定統一的訪問介面和方式,不因為分散式系統內部的變動而改變系統的訪問方式。
    • 位置透明性:外部訪問者不需要知道分散式系統具體的地址,系統節點的變動也不會影響其功能。
    • 併發透明性:幾個程式能併發的使用共享資源而不互相干擾。
    • 複製透明性:使用資源的多個例項提升可靠性和效能,而使用者和程式設計師無需知道副本的相關資訊。
    • 故障透明性:分散式系統內部部分節點的故障不影響系統的整體功能。
    • 移動透明性:資源和客戶能夠在系統內移動而不受影響。
    • 效能透明性:負載變化時,系統能夠被重新配置以提高效能。
    • 伸縮透明性:系統和應用能夠進行擴充套件而不改變系統結構和應用演算法。
  • Openness: 開放性,通用的協議和使用方式。
  • Scalability: 可伸縮性,隨著資源數量的增加和使用者訪問的增加,系統仍然能保持其有效性,該系統就被稱為可伸縮的。分散式系統應該在系統大小,系統管理方面都可擴充套件。
  • Performance: 效能,相對於單體應用,分散式系統應該用更加突出的效能。
  • Reliability: 可靠性,與單體系統相比,分散式系統應具有更好安全性,一致性和掩蓋錯誤的能力。

分散式挑戰

分散式的挑戰來源於不確定性。想一想,分散式系統相對於單體應用,多了哪些東西?

65 哥:有了更多的服務節點,還有就是服務之間的網路通訊。

是的,看來 65 哥同學已經懂得思考和分析系統了。分散式系統的所有挑戰就來源於這兩者的不確定性。

  1. 節點故障:

節點數量越多,出故障的概率就變高了。分散式系統需要保證故障發生的時候,系統仍然是可用的,這就需要系統能夠感知所有節點的服務狀態,在節點發生故障的情況下將該節點負責的計算、儲存任務轉移到其他節點。

  1. 不可靠的網路:

節點間通過網路通訊,我們都知道網路是不可靠的。可能的網路問題包括:網路分割、延時、丟包、亂序。相比單機過程呼叫,網路通訊最讓人頭疼的是超時已經雙向通行的不確定性。出現超時狀態時,網路通訊發起方是無法確定當前請求是否被成功處理的。

在不可靠的網路和節點中,分散式系統依然要保證其可用,穩定,高效,這是一個系統最基本的要求。因此分散式系統的設計和架構充滿了挑戰。

分而治之

分散式系統就是充分利用更多的資源進行並行運算和儲存來提升系統的效能,這就是分而治之的原理。

65 哥:哦,懂了懂了,那 MapReduce 的 map,Elasticsearch 的 sharding,Kafka 的 partition 是不是都是分散式的分而治之原理。

可以啊,65 哥同學不僅能夠歸納,還能夠舉一反三了。不錯,無論是 map,sharding 還是 partition,甚至 請求路由負載均衡 都是在將計算或資料拆分,再分佈到不同的節點計算和儲存,從而提高系統的併發性。

不同叢集型別的分

sharding

同樣是,在不同領域的,甚至不同實現的系統中通常會有不同的說法。sharding 通常是在資料儲存系統中將不同資料分佈到不同節點的方式,中文通常翻譯為資料分片

比如在 MongoDB 中,當 MongoDB 儲存海量的資料時,一臺機器可能不足以儲存資料,也可能不足以提供可接受的讀寫吞吐量。這時,我們就可以通過在多臺機器上分割資料,使得資料庫系統能儲存和處理更多的資料。

圖解|搞定分散式?程式設計師進階之路

比如在 Elasticsearch 中,每個索引有一個或多個分片,索引的資料被分配到各個分片上,相當於一桶水用了 N 個杯子裝。分片有助於橫向擴充套件,N 個分片會被儘可能平均地(rebalance)分配在不同的節點上。

圖解|搞定分散式?程式設計師進階之路

partition

partition的概念經常在 Kafka 中可以看到,在 kafka 中 topic 是一個邏輯概念,從分散式佇列的角度看,topic 對使用者來說就是一個佇列,topic 在 kafka 的具體實現中,由分佈在不同節點上的 partition 組成,每個 partition 就是根據分割槽演算法拆分的多個分割槽,在 kafka 中,同一個分割槽不能被同一個 group 下的多個 consumer 消費,所以一個 topic 有多少 partition 在一定意義上就表示這個 topic 具有多少併發處理能力。

圖解|搞定分散式?程式設計師進階之路

在 Amazing 的分散式資料庫DynamoDB中,一張表在底層實現中也被分割槽為不同的 partition。

load balance

負載均衡是高可用網路基礎架構的關鍵元件,通常用於將工作負載分佈到多個伺服器來提高網站、應用、資料庫或其他服務的效能和可靠性。

比如 nginx 的負載均衡,通過不同的負載均衡分配策略,將 http 請求分發到 web 應用的不同節點之上,從而提高應用的併發處理能力。

比如 dubbo 的客戶端負載能力,可以將 dubbo 請求路由到具體的 producer 提供節點上,負載均衡是一個完善的 RPC 所應該具有的能力。

在 Spring Cloud 的體系中 Robbin 元件可以通過 Spring Cloud 的各微服務之間通訊的負載均衡分配問題,依舊是將請求分發到叢集中的不同節點上去。

分的策略

無論是分割槽還是分片,還是分割槽路由,其實都有一些通用的分割槽演算法,以下的概念可能很多同學都在不同的領域看到過,如上面看到的反向代理伺服器 nginx 中,如分散式訊息佇列 kafka 中,如 RPC 框架 Dubbo 中,這有時候會讓很多同學感到懵。

其實無論在什麼領域中,你只要抓住它在完成的核心功能上就可以理解,它們就是在考慮如何的問題,把處理請求(即計算)如何均勻地分到不同的機器上,把資料如何分配到不同的節點上。

從大的方向看有兩種策略,一種可復刻,一種不可復刻

可復刻,這種策略根據一定演算法分配計算和資料,在相同的條件下,無論什麼時間點得出的結果相同,因此對於相同條件的請求和資料來說是可復刻的,在不同時間點相同的請求和資料始終都在統一節點上。這種策略一般用於有資料狀態在情況。

不可復刻,這種策略使用全隨機方式,即使在相同的條件下,不同時間點得出的結果也不一致,因此也是不可還原的,如果只是為了可還原,如果通過後設資料記錄已經分配好的資料,之後需要還原時通過後設資料就可以準確的得知資料所在位置了。

65 哥:這麼神奇麼?我想看看不同系統都有什麼策略。

Dubbo 的負載均衡

Dubbo 是阿里開源的分散式服務框架。其實現了多種負載均衡策略。

圖解|搞定分散式?程式設計師進階之路

Random LoadBalance

隨機,可以按權重設定隨機概率。在一個截面上碰撞的概率高,但呼叫量越大分佈越均勻,而且按概率使用權重後也比較均勻,有利於動態調整提供者權重。

RoundRobin LoadBalance

輪詢,按公約後的權重設定輪詢比率。存在慢的提供者累積請求的問題,比如:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上。

LeastActive LoadBalance

最少活躍呼叫數,相同活躍數的隨機,活躍數指呼叫前後計數差。使慢的提供者收到更少請求,因為越慢的提供者的呼叫前後計數差會越大。

ConsistentHash LoadBalance

一致性 Hash,相同引數的請求總是發到同一提供者。 當某一臺提供者掛時,原本發往該提供者的請求,基於虛擬節點,平攤到其它提供者,不會引起劇烈變動。

Kafka 的分割槽分配策略

Kafka 中提供了多重分割槽分配演算法(PartitionAssignor)的實現:

RangeAssignor

圖解|搞定分散式?程式設計師進階之路

RangeAssignor 策略的原理是按照消費者總數和分割槽總數進行整除運算來獲得一個跨度,然後將分割槽按照跨度進行平均分配,以保證分割槽儘可能均勻地分配給所有的消費者。對於每一個 Topic,RangeAssignor 策略會將消費組內所有訂閱這個 Topic 的消費者按照名稱的字典序排序,然後為每個消費者劃分固定的分割槽範圍,如果不夠平均分配,那麼字典序靠前的消費者會被多分配一個分割槽。

RoundRobinAssignor

RoundRobinAssignor 的分配策略是將消費組內訂閱的所有 Topic 的分割槽及所有消費者進行排序後儘量均衡的分配(RangeAssignor 是針對單個 Topic 的分割槽進行排序分配的)。

StickyAssignor

從字面意義上看,Sticky 是“粘性的”,可以理解為分配結果是帶“粘性的”——每一次分配變更相對上一次分配做最少的變動(上一次的結果是有粘性的),其主要是為了實現以下兩個目標:

  1. 分割槽的分配儘量的均衡
  2. 每一次重分配的結果儘量與上一次分配結果保持一致

65 哥:哇,看來優秀的系統都是相通的。

副本

副本是解決分散式叢集高可用問題的。在叢集系統中,每個伺服器節點都是不可靠的,每個系統都有當機的風險,如何在系統中少量節點失效的情況下保證整個系統的可用性是分散式系統的挑戰之一。副本就是解決這類問題的方案。副本同樣也可以提高併發處理能力,比如資料在不同的節點上可以讀寫分離,可以並行讀等。

在這裡其實也有很多說法,如 Master-Salve、Leader-Follower、Primary-Shard、Leader-Replica 等等。

圖解|搞定分散式?程式設計師進階之路

Mysql 的主從架構

目前,大部分的主流關係型資料庫都提供了主從熱備功能,通過配置兩臺(或多臺)資料庫的主從關係,可以將一臺資料庫伺服器的資料更新同步到另一臺伺服器上。這既可以實現資料庫的讀寫分離,從而改善資料庫的負載壓力,也可以提高資料高可用,多份資料備份降低了資料丟失的風險。

圖解|搞定分散式?程式設計師進階之路

Elasticsearch 的副本機制

在 ES 中有主分片和副本分片的概念。副本分片的主要目的就是為了故障轉移,如果持有主分片的節點掛掉了,一個副本分片就會晉升為主分片的角色從而對外提供查詢服務。

圖解|搞定分散式?程式設計師進階之路

CAP 理論

在理論電腦科學中,CAP 定理(CAP theorem),又被稱作布魯爾定理(Brewer's theorem),它指出對於一個分散式計算系統來說,不可能同時滿足分散式系統一致性、可用性和分割槽容錯(即 CAP 中的"C","A"和"P"):

圖解|搞定分散式?程式設計師進階之路

65 哥:什麼是一致性、可用性和分割槽容錯效能?

  • 一致性 (Consistency)

一致性意味著所有客戶端同時看到相同的資料,無論它們連線到哪個節點。要發生這種情況,每當將資料寫入一個節點時,必須立即將資料轉發或複製到系統中的所有其他節點,然後才能將寫入視為"成功"。

  • 可用性 (Availability)

任何客戶端的請求都能得到響應資料,不會出現響應錯誤。換句話說,可用性是站在分散式系統的角度,對訪問本系統的客戶的另一種承諾:我一定會給您返回資料,不會給你返回錯誤,但不保證資料最新,強調的是不出錯。

  • 分割槽容錯

分割槽即分散式系統中的通訊中斷,兩個節點之間的丟失或暫時延遲的連線。分割槽容錯意味著群集必須繼續工作,儘管系統中的節點之間存在的通訊故障。

這三種性質進行倆倆組合,可以得到下面三種情況:

  • CA:完全嚴格的仲裁協議,例如 2PC(兩階段提交協議,第一階段投票,第二階段事物提交)
  • CP:不完全(多數)仲裁協議,例如 Paxos、Raft
  • AP:使用衝突解決的協議,例如 Dynamo、Gossip

CA 和 CP 系統設計遵循的都是強一致性理論。不同的是 CA 系統不能容忍節點發生故障。CP 系統能夠容忍 2f+1 個節點中有 f 個節點發生失敗。

Base 理論

圖解|搞定分散式?程式設計師進階之路

CAP 理論表明,對於一個分散式系統而言,它是無法同時滿足 Consistency(強一致性)、Availability(可用性) 和 Partition tolerance(分割槽容忍性) 這三個條件的,最多隻能滿足其中兩個。

在分散式環境中,我們會發現必須選擇 P(分割槽容忍)要素,因為網路本身無法做到 100% 可靠,有可能出故障,所以分割槽是一個必然的現象。也就是說分割槽容錯性是分散式系統的一個最基本要求。

CAP 定理限制了我們三者無法同時滿足,但我們可以儘量讓 C、A、P 都滿足,這就是 BASE 定理。

BASE 理論是 Basically Available(基本可用),Soft State(軟狀態)和 Eventually Consistent(最終一致性)三個短語的縮寫。即使無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。

基本可用 (Basically Available)

基本可用是指分散式系統在出現故障的時候,允許損失部分可用性,即保證核心可用。

電商大促時,為了應對訪問量激增,部分使用者可能會被引導到降級頁面,服務層也可能只提供降級服務,這就是損失部分可用性的體現。

軟狀態 ( Soft State)

什麼是軟狀態呢?相對於原子性而言,要求多個節點的資料副本都是一致的,這是一種“硬狀態”。

軟狀態指的是:允許系統中的資料存在中間狀態,並認為該狀態不影響系統的整體可用性,即允許系統在多個不同節點的資料副本存在資料延時。

最終一致性 ( Eventual Consistency)

最終一致性是指系統中的所有資料副本經過一定時間後,最終能夠達到一致的狀態。

弱一致性和強一致性相反,最終一致性是弱一致性的一種特殊情況。

BASE 理論面向的是大型高可用、可擴充套件的分散式系統。與傳統 ACID 特性相反,不同於 ACID 的強一致性模型,BASE 提出通過犧牲強一致性來獲得可用性,並允許資料段時間內的不一致,但是最終達到一致狀態。

分散式是系統擴充套件的必然方向,分散式系統所遇到的問題是普遍,隨著大量優秀的專案在分散式的道路上披荊斬棘,前人已經總結了大量豐富的理論。並且不同領域的分散式系統也層出不窮,我們既應該學習好這些好的理論知識,也應該去多看看不同分散式系統的實現,總結它們的共性,發現它們在不同領域獨特的亮點權衡,更重要的,我們應該將所學用於日常專案的實踐當中,也應該在實踐中總結出更多的規律理論。

感謝讀者看完本文,碼哥將為讀者持續輸出高質量的文章,下期我們繼續深入講講分散式一致性的問題和解決方案,敬請關注。

碼哥位元組