分佈式架構原理解析,Java開發必修課

1. 分佈式術語

1.1. 異常

服務器宕機

內存錯誤、服務器停電等都會致使服務器宕機,此時節點沒法正常工做,稱爲不可用。程序員

服務器宕機會致使節點失去全部內存信息,所以須要將內存信息保存到持久化介質上。算法

網絡異常

有一種特殊的網絡異常稱爲——網絡分區 ,即集羣的全部節點被劃分爲多個區域,每一個區域內部能夠通訊,可是區域之間沒法通訊。數據庫

磁盤故障

磁盤故障是一種發生機率很高的異常。緩存

使用冗餘機制,將數據存儲到多臺服務器。服務器

1.2. 超時

在分佈式系統中,一個請求除了成功和失敗兩種狀態,還存在着超時狀態。網絡

能夠將服務器的操做設計爲具備 冪等性 ,即執行屢次的結果與執行一次的結果相同。若是使用這種方式,當出現超時的時候,能夠不斷地從新請求直到成功。併發

1.3. 衡量指標

性能

常見的性能指標有:吞吐量、響應時間。負載均衡

其中,吞吐量指系統在某一段時間能夠處理的請求總數,一般爲每秒的讀操做數或者寫操做數;響應時間指從某個請求發出到接收到返回結果消耗的時間。異步

這兩個指標每每是矛盾的,追求高吞吐的系統,每每很難作到低響應時間,解釋以下:分佈式

  • 在無併發的系統中,吞吐量爲響應時間的倒數,例如響應時間爲 10 ms,那麼吞吐量爲 100 req/s,所以高吞吐也就意味着低響應時間。

  • 可是在併發的系統中,因爲一個請求在調用 I/O 資源的時候,須要進行等待。服務器端通常使用的是異步等待方式,即等待的請求被阻塞以後不須要一直佔用 CPU 資源。這種方式能大大提升 CPU 資源的利用率,例如上面的例子中,單個請求在無併發的系統中響應時間爲 10 ms,若是在併發的系統中,那麼吞吐量將大於 100 req/s。所以爲了追求高吞吐量,一般會提升併發程度。可是併發程度的增長,會致使請求的平均響應時間也增長,由於請求不能立刻被處理,須要和其它請求一塊兒進行併發處理,響應時間天然就會增高。

可用性

可用性指系統在面對各類異常時能夠提供正常服務的能力。能夠用系統可用時間佔總時間的比值來衡量,4 個 9 的可用性表示系統 99.99% 的時間是可用的。

一致性

能夠從兩個角度理解一致性:從客戶端的角度,讀寫操做是否知足某種特性;從服務器的角度,多個數據副本之間是否一致。

可擴展性

指系統經過擴展集羣服務器規模來提升性能的能力。理想的分佈式系統須要實現「線性可擴展」,即隨着集羣規模的增長,系統的總體性能也會線性增長。

2. 數據分佈

分佈式存儲系統的數據分佈在多個節點中,經常使用的數據分佈方式有哈希分佈和順序分佈。

數據庫的水平切分(Sharding)也是一種分佈式存儲方法,下面的數據分佈方法一樣適用於 Sharding。

2.1. 哈希分佈

哈希分佈就是將數據計算哈希值以後,按照哈希值分配到不一樣的節點上。例若有 N 個節點,數據的主鍵爲 key,則將該數據分配的節點序號爲:hash(key)%N。

傳統的哈希分佈算法存在一個問題:當節點數量變化時,也就是 N 值變化,那麼幾乎全部的數據都須要從新分佈,將致使大量的數據遷移。

一致性哈希

Distributed Hash Table(DHT):對於哈希空間 [0, 2n-1],將該哈希空間當作一個哈希環,將每一個節點都配置到哈希環上。每一個數據對象經過哈希取模獲得哈希值以後,存放到哈希環中順時針方向第一個大於等於該哈希值的節點上。

一致性哈希的優勢是在增長或者刪除節點時只會影響到哈希環中相鄰的節點,例以下圖中新增節點 X,只須要將數據對象 C 從新存放到節點 X 上便可,對於節點 A、B、D 都沒有影響。

2.2. 順序分佈

哈希分佈式破壞了數據的有序性,順序分佈則不會。

順序分佈的數據劃分爲多個連續的部分,按數據的 ID 或者時間分佈到不一樣節點上。例以下圖中,User 表的 ID 範圍爲 1 ~ 7000,使用順序分佈能夠將其劃分紅多個子表,對應的主鍵範圍爲 1 ~ 1000,1001 ~ 2000,...,6001 ~ 7000。

順序分佈的優勢是能夠充分利用每一個節點的空間,而哈希分佈很難控制一個節點存儲多少數據。

可是順序分佈須要使用一個映射表來存儲數據到節點的映射,這個映射表一般使用單獨的節點來存儲。當數據量很是大時,映射表也隨着變大,那麼一個節點就可能沒法存放下整個映射表。而且單個節點維護着整個映射表的開銷很大,查找速度也會變慢。爲了解決以上問題,引入了一箇中間層,也就是 Meta 表,從而分擔映射表的維護工做。

2.3. 負載均衡

衡量負載的因素不少,如 CPU、內存、磁盤等資源使用狀況、讀寫請求數等。

分佈式系統存儲應當可以自動負載均衡,當某個節點的負載較高,將它的部分數據遷移到其它節點。

每一個集羣都有一個總控節點,其它節點爲工做節點,由總控節點根據全局負載信息進行總體調度,工做節點定時發送心跳包(Heartbeat)將節點負載相關的信息發送給總控節點。

一個新上線的工做節點,因爲其負載較低,若是不加控制,總控節點會將大量數據同時遷移到該節點上,形成該節點一段時間內沒法工做。所以負載均衡操做須要平滑進行,新加入的節點須要較長的一段時間來達到比較均衡的狀態。

3. 分佈式理論

3.1. CAP

分佈式系統不可能同時知足一致性(C:Consistency)、可用性(A:Availability)和分區容忍性(P:Partition Tolerance),最多隻能同時知足其中兩項。

一致性

一致性指的是多個數據副本是否能保持一致的特性。

在一致性的條件下,系統在執行數據更新操做以後可以從一致性狀態轉移到另外一個一致性狀態。

對系統的一個數據更新成功以後,若是全部用戶都可以讀取到最新的值,該系統就被認爲具備強一致性。

可用性

可用性指分佈式系統在面對各類異常時能夠提供正常服務的能力,能夠用系統可用時間佔總時間的比值來衡量,4 個 9 的可用性表示系統 99.99% 的時間是可用的。

在可用性條件下,系統提供的服務一直處於可用的狀態,對於用戶的每個操做請求老是可以在有限的時間內返回結果。

分區容忍性

網絡分區指分佈式系統中的節點被劃分爲多個區域,每一個區域內部能夠通訊,可是區域之間沒法通訊。

在分區容忍性條件下,分佈式系統在遇到任何網絡分區故障的時候,仍然須要能對外提供一致性和可用性的服務,除非是整個網絡環境都發生了故障。

權衡

在分佈式系統中,分區容忍性必不可少,由於須要老是假設網絡是不可靠的。所以,CAP 理論實際在是要在可用性和一致性之間作權衡。

可用性和一致性每每是衝突的,很難都使它們同時知足。在多個節點之間進行數據同步時,

  • 爲了保證一致性(CP),就須要讓全部節點下線成爲不可用的狀態,等待同步完成;
  • 爲了保證可用性(AP),在同步過程當中容許讀取全部節點的數據,可是數據可能不一致。

3.2. BASE

BASE 是基本可用(Basically Available)、軟狀態(Soft State)和最終一致性(Eventually Consistent)三個短語的縮寫。

BASE 理論是對 CAP 中一致性和可用性權衡的結果,它的理論的核心思想是:即便沒法作到強一致性,但每一個應用均可以根據自身業務特色,採用適當的方式來使系統達到最終一致性。

基本可用

指分佈式系統在出現故障的時候,保證核心可用,容許損失部分可用性。

例如,電商在作促銷時,爲了保證購物系統的穩定性,部分消費者可能會被引導到一個降級的頁面。

軟狀態

指容許系統中的數據存在中間狀態,並認爲該中間狀態不會影響系統總體可用性,即容許系統不一樣節點的數據副本之間進行同步的過程存在延時。

最終一致性

最終一致性強調的是系統中全部的數據副本,在通過一段時間的同步後,最終能達到一致的狀態。

ACID 要求強一致性,一般運用在傳統的數據庫系統上。而 BASE 要求最終一致性,經過犧牲強一致性來達到可用性,一般運用在大型分佈式系統中。

在實際的分佈式場景中,不一樣業務單元和組件對一致性的要求是不一樣的,所以 ACID 和 BASE 每每會結合在一塊兒使用。

4. 分佈式事務問題

4.1. 兩階段提交(2PC)

兩階段提交(Two-phase Commit,2PC)

主要用於實現分佈式事務,分佈式事務指的是事務操做跨越多個節點,而且要求知足事務的 ACID 特性。

經過引入協調者(Coordinator)來調度參與者的行爲,並最終決定這些參與者是否要真正執行事務。

運行過程

準備階段

協調者詢問參與者事務是否執行成功,參與者發回事務執行結果。

提交階段

若是事務在每一個參與者上都執行成功,事務協調者發送通知讓參與者提交事務;不然,協調者發送通知讓參與者回滾事務。

須要注意的是,在準備階段,參與者執行了事務,可是還未提交。只有在提交階段接收到協調者發來的通知後,才進行提交或者回滾。

問題

同步阻塞

全部事務參與者在等待其它參與者響應的時候都處於同步阻塞狀態,沒法進行其它操做。

單點問題

協調者在 2PC 中起到很是大的做用,發生故障將會形成很大影響,特別是在階段二發生故障,全部參與者會一直等待狀態,沒法完成其它操做。

數據不一致

在階段二,若是協調者只發送了部分 Commit 消息,此時網絡發生異常,那麼只有部分參與者接收到 Commit 消息,也就是說只有部分參與者提交了事務,使得系統數據不一致。

太過保守

任意一個節點失敗就會致使整個事務失敗,沒有完善的容錯機制。

2PC 優缺點

優勢:儘可能保證了數據的強一致,適合對數據強一致要求很高的關鍵領域。(其實也不能 100%保證強一致) 缺點:實現複雜,犧牲了可用性,對性能影響較大,不適合高併發高性能場景。

4.2. 補償事務(TCC)

補償事務(TCC)其核心思想是:針對每一個操做,都要註冊一個與其對應的確認和補償(撤銷)操做。它分爲三個階段:

  1. Try 階段主要是對業務系統作檢測及資源預留。
  2. Confirm 階段主要是對業務系統作確認提交,Try 階段執行成功並開始執行 Confirm 階段時,默認 Confirm 階段是不會出錯的。即:只要 Try 成功,Confirm 必定成功。
  3. Cancel 階段主要是在業務執行錯誤,須要回滾的狀態下執行的業務取消,預留資源釋放。

舉個例子,假設 Bob 要向 Smith 轉帳,思路大概是:

  1. 首先在 Try 階段,要先調用遠程接口把 Smith 和 Bob 的錢給凍結起來。
  2. 在 Confirm 階段,執行遠程調用的轉帳的操做,轉帳成功進行解凍。
  3. 若是第 2 步執行成功,那麼轉帳成功,若是第二步執行失敗,則調用遠程凍結接口對應的解凍方法 (Cancel)。

TCC 優缺點

  • 優勢:跟 2PC 比起來,實現以及流程相對簡單了一些,但數據的一致性比 2PC 也要差一些。
  • 缺點:缺點仍是比較明顯的,在 2,3 步中都有可能失敗。TCC 屬於應用層的一種補償方式,因此須要程序員在實現的時候多寫不少補償的代碼,在一些場景中,一些業務流程可能用 TCC 不太好定義及處理。

4.3. 本地消息表(異步確保)

本地消息表與業務數據表處於同一個數據庫中,這樣就能利用本地事務來保證在對這兩個表的操做知足事務特性。

  1. 在分佈式事務操做的一方完成寫業務數據的操做以後向本地消息表發送一個消息,本地事務能保證這個消息必定會被寫入本地消息表中。
  2. 以後將本地消息表中的消息轉發到 Kafka 等消息隊列(MQ)中,若是轉發成功則將消息從本地消息表中刪除,不然繼續從新轉發。
  3. 在分佈式事務操做的另外一方從消息隊列中讀取一個消息,並執行消息中的操做。

這種方案遵循 BASE 理論,採用的是最終一致性。

本地消息表利用了本地事務來實現分佈式事務,而且使用了消息隊列來保證最終一致性。

本地消息表優缺點

  • 優勢:一種很是經典的實現,避免了分佈式事務,實現了最終一致性。
  • 缺點:消息表會耦合到業務系統中,若是沒有封裝好的解決方案,會有不少雜活須要處理。

4.4. MQ 事務消息

有一些第三方的 MQ 是支持事務消息的,好比 RocketMQ,他們支持事務消息的方式也是相似於採用的二階段提交。可是市面上一些主流的 MQ 都是不支持事務消息的,好比 RabbitMQ 和 Kafka 都不支持。

以阿里的 RocketMQ 中間件爲例,其思路大體爲:

  1. Prepared 消息,會拿到消息的地址。
  2. 執行本地事務。
  3. 經過第一階段拿到的地址去訪問消息,並修改狀態。

也就是說在業務方法內要想消息隊列提交兩次請求,一次發送消息和一次確認消息。若是確認消息發送失敗了 RocketMQ 會按期掃描消息集羣中的事務消息,這時候發現了 Prepared 消息,它會向消息發送者確認,因此生產方須要實現一個 check 接口,RocketMQ 會根據發送端設置的策略來決定是回滾仍是繼續發送確認消息。這樣就保證了消息發送與本地事務同時成功或同時失敗。

MQ 事務消息優缺點

  • 優勢:實現了最終一致性,不須要依賴本地數據庫事務。
  • 缺點:實現難度大,主流 MQ 不支持。

5. 共識性問題

5.1. Paxos

用於達成共識性問題,即對多個節點產生的值,該算法能保證只選出惟一一個值。

主要有三類節點:

  • 提議者(Proposer):提議一個值;
  • 接受者(Acceptor):對每一個提議進行投票;
  • 告知者(Learner):被告知投票的結果,不參與投票過程。

算法須要知足 safety 和 liveness 兩方面的約束要求(實際上這兩個基礎屬性是大部分分佈式算法都該考慮的):

  • safety:保證決議結果是對的,無歧義的,不會出現錯誤狀況。
    • 決議(value)只有在被 proposers 提出的 proposal 才能被最終批准;
    • 在一次執行實例中,只批准(chosen)一個最終決議,意味着多數接受(accept)的結果能成爲決議;
  • liveness:保證決議過程能在有限時間內完成。
    • 決議總會產生,而且 learners 能得到被批准(chosen)的決議。

基本過程包括 proposer 提出提案,先爭取大多數 acceptor 的支持,超過一半支持時,則發送結案結果給全部人進行確認。一個潛在的問題是 proposer 在此過程當中出現故障,能夠經過超時機制來解決。極爲湊巧的狀況下,每次新的一輪提案的 proposer 都剛好故障,系統則永遠沒法達成一致(機率很小)。

Paxos 能保證在超過 $1/2$ 的正常節點存在時,系統能達成共識。

單個提案者+多接收者

若是系統中限定只有某個特定節點是提案者,那麼一致性確定能達成(只有一個方案,要麼達成,要麼失敗)。提案者只要收到了來自多數接收者的投票,便可認爲經過,由於系統中不存在其餘的提案。

但一旦提案者故障,則系統沒法工做。

多個提案者+單個接收者

限定某個節點做爲接收者。這種狀況下,共識也很容易達成,接收者收到多個提案,選第一個提案做爲決議,拒絕掉後續的提案便可。

缺陷也是容易發生單點故障,包括接收者故障或首個提案者節點故障。

以上兩種情形其實相似主從模式,雖然不那麼可靠,但由於原理簡單而被普遍採用。

當提案者和接收者都推廣到多個的情形,會出現一些挑戰。

多個提案者+多個接收者

既然限定單提案者或單接收者都會出現故障,那麼就得容許出現多個提案者和多個接收者。問題一會兒變得複雜了。

一種狀況是同一時間片斷(如一個提案週期)內只有一個提案者,這時能夠退化到單提案者的情形。須要設計一種機制來保障提案者的正確產生,例如按照時間、序列、或者你們猜拳(出一個數字來比較)之類。考慮到分佈式系統要處理的工做量很大,這個過程要儘可能高效,知足這一條件的機制很是難設計。

另外一種狀況是容許同一時間片斷內能夠出現多個提案者。那同一個節點可能收到多份提案,怎麼對他們進行區分呢?這個時候採用只接受第一個提案而拒絕後續提案的方法也不適用。很天然的,提案須要帶上不一樣的序號。節點須要根據提案序號來判斷接受哪一個。好比接受其中序號較大(每每意味着是接受新提出的,由於舊提案者故障機率更大)的提案。

如何爲提案分配序號呢?一種可能方案是每一個節點的提案數字區間彼此隔離開,互相不衝突。爲了知足遞增的需求能夠配合用時間戳做爲前綴字段。

此外,提案者即使收到了多數接收者的投票,也不敢說就必定經過。由於在此過程當中系統可能還有其它的提案。

5.2. Raft

Raft 算法是 Paxos 算法的一種簡化實現。

包括三種角色:leader、candidate 和 follower,其基本過程爲:

  • Leader 選舉 - 每一個 candidate 隨機通過必定時間都會提出選舉方案,最近階段中得票最多者被選爲 leader;
  • 同步 log - leader 會找到系統中 log 最新的記錄,並強制全部的 follower 來刷新到這個記錄;

注:此處 log 並不是是指日誌消息,而是各類事件的發生記錄。

單個 Candidate 的競選

有三種節點:Follower、Candidate 和 Leader。Leader 會週期性的發送心跳包給 Follower。每一個 Follower 都設置了一個隨機的競選超時時間,通常爲 150ms~300ms,若是在這個時間內沒有收到 Leader 的心跳包,就會變成 Candidate,進入競選階段。

  • 下圖表示一個分佈式系統的最初階段,此時只有 Follower,沒有 Leader。Follower A 等待一個隨機的競選超時時間以後,沒收到 Leader 發來的心跳包,所以進入競選階段。

  • 此時 A 發送投票請求給其它全部節點。

  • 其它節點會對請求進行回覆,若是超過一半的節點回復了,那麼該 Candidate 就會變成 Leader。

  • 以後 Leader 會週期性地發送心跳包給 Follower,Follower 接收到心跳包,會從新開始計時。

多個 Candidate 競選

  • 若是有多個 Follower 成爲 Candidate,而且所得到票數相同,那麼就須要從新開始投票,例以下圖中 Candidate B 和 Candidate D 都得到兩票,所以須要從新開始投票。

  • 當從新開始投票時,因爲每一個節點設置的隨機競選超時時間不一樣,所以能下一次再次出現多個 Candidate 並得到一樣票數的機率很低。

同步日誌

  • 來自客戶端的修改都會被傳入 Leader。注意該修改還未被提交,只是寫入日誌中。

  • Leader 會把修改複製到全部 Follower。

  • Leader 會等待大多數的 Follower 也進行了修改,而後纔將修改提交。

  • 此時 Leader 會通知的全部 Follower 讓它們也提交修改,此時全部節點的值達成一致。

6. 分佈式緩存問題

6.1. 緩存雪崩

緩存雪崩是指:在高併發場景下,因爲原有緩存失效,新緩存未到期間(例如:咱們設置緩存時採用了相同的過時時間,在同一時刻出現大面積的緩存過時),全部本來應該訪問緩存的請求都去查詢數據庫了,而對數據庫 CPU 和內存形成巨大壓力,嚴重的會形成數據庫宕機。從而造成一系列連鎖反應,形成整個系統崩潰。

解決方案:

  • 用加鎖或者隊列的方式保證來保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量的併發請求落到底層存儲系統上。
  • 還有一個簡單的方案,就是將緩存失效時間分散開,不要全部緩存時間長度都設置成 5 分鐘或者 10 分鐘;好比咱們能夠在原有的失效時間基礎上增長一個隨機值,好比 1-5 分鐘隨機,這樣每個緩存的過時時間的重複率就會下降,就很難引起集體失效的事件。

緩存失效時產生的雪崩效應,將全部請求所有放在數據庫上,這樣很容易就達到數據庫的瓶頸,致使服務沒法正常提供。儘可能避免這種場景的發生。

6.2. 緩存穿透

緩存穿透是指:用戶查詢的數據,在數據庫沒有,天然在緩存中也不會有。這樣就致使用戶查詢的時候,在緩存中找不到,每次都要去數據庫再查詢一遍,而後返回空(至關於進行了兩次無用的查詢)。這樣請求就繞過緩存直接查數據庫,這也是常常提的緩存命中率問題。

當在流量較大時,出現這樣的狀況,一直請求 DB,很容易致使服務掛掉。

解決方案:

  1. 在封裝的緩存 SET 和 GET 部分增長個步驟,若是查詢一個 KEY 不存在,就以這個 KEY 爲前綴設定一個標識 KEY;之後再查詢該 KEY 的時候,先查詢標識 KEY,若是標識 KEY 存在,就返回一個協定好的非 false 或者 NULL 值,而後 APP 作相應的處理,這樣緩存層就不會被穿透。固然這個驗證 KEY 的失效時間不能太長。
  2. 若是一個查詢返回的數據爲空(無論是數據不存在,仍是系統故障),咱們仍然把這個空結果進行緩存,但它的過時時間會很短,通常只有幾分鐘。
  3. 採用布隆過濾器,將全部可能存在的數據哈希到一個足夠大的 bitmap 中,一個必定不存在的數據會被這個 bitmap 攔截掉,從而避免了對底層存儲系統的查詢壓力。

6.3. 緩存預熱

緩存預熱這個應該是一個比較常見的概念,相信不少小夥伴都應該能夠很容易的理解,緩存預熱就是系統上線後,將相關的緩存數據直接加載到緩存系統。這樣就能夠避免在用戶請求的時候,先查詢數據庫,而後再將數據緩存的問題!用戶直接查詢事先被預熱的緩存數據!

解決方案:

  1. 直接寫個緩存刷新頁面,上線時手工操做下;
  2. 數據量不大,能夠在項目啓動的時候自動進行加載;
  3. 定時刷新緩存;

6.4. 緩存更新

除了緩存服務器自帶的緩存失效策略以外(Redis 默認的有 6 中策略可供選擇),咱們還能夠根據具體的業務需求進行自定義的緩存淘汰,常見的策略有兩種:

  1. 定時去清理過時的緩存;
  2. 當有用戶請求過來時,再判斷這個請求所用到的緩存是否過時,過時的話就去底層系統獲得新數據並更新緩存。

二者各有優劣,第一種的缺點是維護大量緩存的 key 是比較麻煩的,第二種的缺點就是每次用戶請求過來都要判斷緩存失效,邏輯相對比較複雜!具體用哪一種方案,你們能夠根據本身的應用場景來權衡。

6.5. 緩存降級

當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然須要保證服務仍是可用的,即便是有損服務。系統能夠根據一些關鍵數據進行自動降級,也能夠配置開關實現人工降級。

降級的最終目的是保證核心服務可用,即便是有損的。並且有些服務是沒法降級的(如加入購物車、結算)。

免費Java資料須要本身領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G。 
傳送門: https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

相關文章
相關標籤/搜索