分佈式十二問!萬字圖文詳解!

分佈式理論

1. 說說 CAP 原則?

CAP 原則又稱 CAP 定理,指的是在一個分佈式系統中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性)這 3 個基本需求,最多隻能同時滿足其中的 2 個。

CAP 原則

nHmrFO

2. 爲什麼 CAP 不可兼得呢?

首先對於分佈式系統,分區是必然存在的,所謂分區指的是分佈式系統可能出現的字區域網絡不通,成爲孤立區域的的情況。

分區

那麼分區容錯性(P)就必須要滿足,因爲如果要犧牲分區容錯性,就得把服務和資源放到一個機器,或者一個 “同生共死” 的集羣,那就違背了分佈式的初衷。

那麼滿足分區容錯的基礎上,能不能同時滿足一致性可用性

假如現在有兩個分區N1N2,N1 和 N2 分別有不同的分區存儲 D1 和 D2,以及不同的服務 S1 和 S2。

分區的服務

假如現在有這樣的場景:

接下來:

所以,可以看出,分區容錯的前提下,一致性可用性是矛盾的。

3. CAP 對應的模型和應用?

CA without P

理論上放棄 P(分區容錯性),則 C(強一致性)和 A(可用性)是可以保證的。實際上分區是不可避免的,嚴格上 CA 指的是允許分區後各子系統依然保持 CA。

CA 模型的常見應用:

CP without A

放棄 A(可用),相當於每個請求都需要在 Server 之間強一致,而 P(分區)會導致同步時間無限延長,如此 CP 也是可以保證的。很多傳統的數據庫分佈式事務都屬於這種模式。

CP 模型的常見應用:

AP wihtout C

要高可用並允許分區,則需放棄一致性。一旦分區發生,節點之間可能會失去聯繫,爲了高可用,每個節點只能用本地數據提供服務,而這樣會導致全局數據的不一致性。現在衆多的 NoSQL 都屬於此類。

AP 模型常見應用:

舉個大家更熟悉的例子,像我們熟悉的註冊中心ZooKeeperEurekaNacos中:

4. BASE 理論瞭解嗎?

BASE(Basically Available、Soft state、Eventual consistency)是基於 CAP 理論逐步演化而來的,核心思想是即便不能達到強一致性(Strong consistency),也可以根據應用特點採用適當的方式來達到最終一致性(Eventual consistency)的效果。

BASE 的主要含義:

什麼是基本可用呢?假設系統出現了不可預知的故障,但還是能用,只是相比較正常的系統而言,可能會有響應時間上的損失,或者功能上的降級。

什麼是硬狀態呢?要求多個節點的數據副本都是一致的,這是一種 “硬狀態”。

軟狀態也稱爲弱狀態,相比較硬狀態而言,允許系統中的數據存在中間狀態,並認爲該狀態不影響系統的整體可用性,即允許系統在多個不同節點的數據副本存在數據延時。

上面說了軟狀態,但是不應該一直都是軟狀態。在一定時間後,應該到達一個最終的狀態,保證所有副本保持數據一致性,從而達到數據的最終一致性。這個時間取決於網絡延時、系統負載、數據複製方案設計等等因素。

分佈式鎖

單體時代,可以直接用本地鎖來實現對競爭資源的加鎖,分佈式環境下就要用到分佈式鎖了。

5. 有哪些分佈式鎖的實現方案呢?

常見的分佈式鎖實現方案有三種:MySQL分佈式鎖ZooKepper分佈式鎖Redis分佈式鎖

分佈式鎖

5.1 MySQL 分佈式鎖如何實現呢?

用數據庫實現分佈式鎖比較簡單,就是創建一張鎖表,數據庫對字段作唯一性約束。

加鎖的時候,在鎖表中增加一條記錄即可;釋放鎖的時候刪除記錄就行。

如果有併發請求同時提交到數據庫,數據庫會保證只有一個請求能夠得到鎖。

這種屬於數據庫 IO 操作,效率不高,而且頻繁操作會增大數據庫的開銷,因此這種方式在高併發、高性能的場景中用的不多。

5.2 ZooKeeper 如何實現分佈式鎖?

ZooKeeper 也是常見分佈式鎖實現方法。

ZooKeeper 的數據節點和文件目錄類似,例如有一個 lock 節點,在此節點下建立子節點是可以保證先後順序的,即便是兩個進程同時申請新建節點,也會按照先後順序建立兩個節點。

ZooKeeper 如何實現分佈式鎖

所以我們可以用此特性實現分佈式鎖。以某個資源爲目錄,然後這個目錄下面的節點就是我們需要獲取鎖的客戶端,每個服務在目錄下創建節點,如果它的節點,序號在目錄下最小,那麼就獲取到鎖,否則等待。釋放鎖,就是刪除服務創建的節點。

ZK 實際上是一個比較重的分佈式組件,實際上應用沒那麼多了,所以用 ZK 實現分佈式鎖,其實相對也比較少。

5.3 Redis 怎麼實現分佈式鎖?

Redis 實現分佈式鎖,是當前應用最廣泛的分佈式鎖實現方式。

Redis 執行命令是單線程的,Redis 實現分佈式鎖就是利用這個特性。

實現分佈式鎖最簡單的一個命令:setNx(set if not exist),如果不存在則更新:

setNx resourceName value

加鎖了之後如果機器宕機,那我這個鎖就無法釋放,所以需要加入過期時間,而且過期時間需要和 setNx 同一個原子操作,在 Redis2.8 之前需要用 lua 腳本,但是 redis2.8 之後 redis 支持 nx 和 ex 操作是同一原子操作。

set resourceName value ex 5 nx

當然,一般生產中都是使用 Redission 客戶端,非常良好地封裝了分佈式鎖的 api,而且支持 RedLock。

分佈式事務

6. 什麼是分佈式事務?

分佈式事務是相對本地事務而言的,對於本地事務,利用數據庫本身的事務機制,就可以保證事務的 ACID 特性。

ACID

而在分佈式環境下,會涉及到多個數據庫。

多數據庫

分佈式事務其實就是將對同一庫事務的概念擴大到了對多個庫的事務。目的是爲了保證分佈式系統中的數據一致性。

分佈式事務處理的關鍵是:

  1. 需要記錄事務在任何節點所做的所有動作;

  2. 事務進行的所有操作要麼全部提交,要麼全部回滾。

7. 分佈式事務有哪些常見的實現方案?

分佈式常見的實現方案有 2PC3PCTCC本地消息表MQ 消息事務最大努力通知SAGA 事務 等等。

7.1 說說 2PC 兩階段提交?

說到 2PC,就不得先說分佈式事務中的 XA 協議。

在這個協議裏,有三個角色:

XA 協議

XA 協議採用兩階段提交方式來管理分佈式事務。XA 接口提供資源管理器與事務管理器之間進行通信的標準接口。

兩階段提交的思路可以概括爲:參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋情況決定各參與者是否要提交操作還是回滾操作。

2PC

優點:儘量保證了數據的強一致,實現成本較低,在各大主流數據庫都有自己實現,對於 MySQL 是從 5.5 開始支持。

缺點:

7.2 3PC(三階段提交)瞭解嗎?

三階段提交(3PC)是二階段提交(2PC)的一種改進版本 ,爲解決兩階段提交協議的單點故障和同步阻塞問題。

三階段提交有這麼三個階段:CanCommitPreCommitDoCommit三個階段

3PC

可以看出,三階段提交解決的只是兩階段提交中單體故障同步阻塞的問題,因爲加入了超時機制,這裏的超時的機制作用於 預提交階段提交階段。如果等待 預提交請求 超時,參與者直接回到準備階段之前。如果等到提交請求超時,那參與者就會提交事務了。

無論是 2PC 還是 3PC 都不能保證分佈式系統中的數據 100% 一致

7.3 TCC 瞭解嗎?

TCC(Try Confirm Cancel) ,是兩階段提交的一個變種,針對每個操作,都需要有一個其對應的確認和取消操作,當操作成功時調用確認操作,當操作失敗時調用取消操作,類似於二階段提交,只不過是這裏的提交和回滾是針對業務上的,所以基於 TCC 實現的分佈式事務也可以看做是對業務的一種補償機制。

TCC 下單減庫存

TCC 是業務層面的分佈式事務,保證最終一致性,不會一直持有資源的鎖。

7.4 本地消息表瞭解嗎?

本地消息表的核心思想是將分佈式事務拆分成本地事務進行處理。

例如,可以在訂單庫新增一個消息表,將新增訂單和新增消息放到一個事務裏完成,然後通過輪詢的方式去查詢消息表,將消息推送到 MQ,庫存服務去消費 MQ。

本地消息表

執行流程:

  1. 訂單服務,添加一條訂單和一條消息,在一個事務裏提交

  2. 訂單服務,使用定時任務輪詢查詢狀態爲未同步的消息表,發送到 MQ,如果發送失敗,就重試發送

  3. 庫存服務,接收 MQ 消息,修改庫存表,需要保證冪等操作

  4. 如果修改成功,調用 rpc 接口修改訂單系統消息表的狀態爲已完成或者直接刪除這條消息

  5. 如果修改失敗,可以不做處理,等待重試

訂單服務中的消息有可能由於業務問題會一直重複發送,所以爲了避免這種情況可以記錄一下發送次數,當達到次數限制之後報警,人工接入處理;庫存服務需要保證冪等,避免同一條消息被多次消費造成數據不一致。

本地消息表這種方案實現了最終一致性,需要在業務系統裏增加消息表,業務邏輯中多一次插入的 DB 操作,所以性能會有損耗,而且最終一致性的間隔主要有定時任務的間隔時間決定

7.5 MQ 消息事務瞭解嗎?

消息事務的原理是將兩個事務通過消息中間件進行異步解耦

訂單服務執行自己的本地事務,併發送 MQ 消息,庫存服務接收消息,執行自己的本地事務,乍一看,好像跟本地消息表的實現方案類似,只是省去 了對本地消息表的操作和輪詢發送 MQ 的操作,但實際上兩種方案的實現是不一樣的。

消息事務一定要保證業務操作與消息發送的一致性,如果業務操作成功,這條消息也一定投遞成功。

MQ 消息事務

執行流程:

  1. 發送 prepare 消息到消息中間件

  2. 發送成功後,執行本地事務

  3. 如果事務執行成功,則 commit,消息中間件將消息下發至消費端

  4. 如果事務執行失敗,則回滾,消息中間件將這條 prepare 消息刪除

  5. 消費端接收到消息進行消費,如果消費失敗,則不斷重試

消息事務依賴於消息中間件的事務消息,例如我們熟悉的 RocketMQ 就支持事務消息(半消息),也就是隻有收到發送方確定纔會正常投遞的消息。

這種方案也是實現了最終一致性,對比本地消息表實現方案,不需要再建消息表,對性能的損耗和業務的入侵更小。

7.6 最大努力通知了解嗎?

最大努力通知相比實現會簡單一些,適用於一些對最終一致性實時性要求沒那麼高的業務,比如支付通知,短信通知。

以支付通知爲例,業務系統調用支付平臺進行支付,支付平臺進行支付,進行操作支付之後支付平臺會去同步通知業務系統支付操作是否成功,如果不成功,會一直異步重試,但是會有一個最大通知次數,如果超過這個次數後還是通知失敗,就不再通知,業務系統自行調用支付平臺提供一個查詢接口,供業務系統進行查詢支付操作是否成功。

最大努力通知

執行流程:

  1. 業務系統調用支付平臺支付接口, 並在本地進行記錄,支付狀態爲支付中

  2. 支付平臺進行支付操作之後,無論成功還是失敗,同步給業務系統一個結果通知

  3. 如果通知一直失敗則根據重試規則異步進行重試,達到最大通知次數後,不再通知

  4. 支付平臺提供查詢訂單支付操作結果接口

  5. 業務系統根據一定業務規則去支付平臺查詢支付結果

8. 你們用什麼?能說一下 Seata 嗎?

我們用比較常用的是 Seata——自己去實現分佈式事務調度還是比較麻煩的。

Seata 的設計目標是對業務無侵入,因此它是從業務無侵入的兩階段提交(全局事務)着手,在傳統的兩階段上進行改進,他把一個分佈式事務理解成一個包含了若干分支事務的全局事務。而全局事務的職責是協調它管理的分支事務達成一致性,要麼一起成功提交,要麼一起失敗回滾。也就是一榮俱榮一損俱損~

全局事務和分支事務

Seata 中存在這麼幾種重要角色:

Seata 工作流程

S'eata 整體執行流程:

  1. 服務 A 中的 TMTC 申請開啓一個全局事務,TC 就會創建一個全局事務並返回一個唯一的 XID

  2. 服務 A 中的 RMTC 註冊分支事務,然後將這個分支事務納入 XID 對應的全局事務管轄中

  3. 服務 A 開始執行分支事務

  4. 服務 A 開始遠程調用 B 服務,此時 XID 會根據調用鏈傳播

  5. 服務 B 中的 RM 也向 TC 註冊分支事務,然後將這個分支事務納入 XID 對應的全局事務管轄中

  6. 服務 B 開始執行分支事務

  7. 全局事務調用處理結束後,TM 會根據有誤異常情況,向 TC 發起全局事務的提交或回滾

  8. TC 協調其管轄之下的所有分支事務,決定是提交還是回滾

分佈式一致性算法

9. 分佈式算法 paxos 瞭解麼 ?

Paxos 有點類似前面說的 2PC3PC,但比這兩種算法更加完善。在很多多大廠都得到了工程實踐,比如阿里的 OceanBase分佈式數據庫Googlechubby 分佈式鎖

Paxos 算法是什麼?

Paxos 算法是 基於消息傳遞 且具有 高效容錯特性 的一致性算法,目前公認的解決 分佈式一致性問題 最有效的算法之一。

Paxos 算法的工作流程?

角色

在 Paxos 中有這麼幾個角色:

  1. Proposer(提議者) : 提議者提出提案,用於投票表決。

  2. Accecptor(接受者) : 對提案進行投票,並接受達成共識的提案。

  3. Learner(學習者) : 被告知投票的結果,接受達成共識的提案。

在實際中,一個節點可以同時充當不同角色。

Paxos 的三種角色

提議者提出提案,提案 = 編號 + value,可以表示爲 [M,V],每個提案都有唯一編號,而且編號的大小是趨勢遞增的。

算法流程

Paxos 算法包含兩個階段,第一階段 Prepare(準備)、第二階段** Accept(接受)**。

Paxos 算法流程

Prepare(準備) 階段
  1. 提議者提議一個新的提案 P[Mn,?],然後向接受者的某個超過半數的子集成員發送編號爲 Mn 的準備請求

  2. 如果一個接受者收到一個編號爲 Mn 的準備請求,並且編號 Mn 大於它已經響應的所有準備請求的編號,那麼它就會將它已經批准過的最大編號的提案作爲響應反饋給提議者,同時該接受者會承諾不會再批准任何編號小於 Mn 的提案。

總結一下,接受者在收到提案後,會給與提議者兩個承諾一個應答

Accept(接受) 階段
  1. 如果提議者收到來自半數以上的接受者對於它發出的編號爲 Mn 的準備請求的響應,那麼它就會發送一個針對 [Mn,Vn] 的接受請求給接受者,注意 Vn 的值就是收到的響應中編號最大的提案的值,如果響應中不包含任何提案,那麼它可以隨意選定一個值。

  2. 如果接受者收到這個針對 [Mn,Vn] 提案的接受請求,只要該接受者尚未對編號大於 Mn 的準備請求做出響應,它就可以通過這個提案。

當提議者收到了多數接受者的接受應答後,協商結束,共識決議形成,將形成的決議發送給所有學習節點進行學習。

所以 Paxos 算法的整體詳細流程如下:

Paxos 詳細流程

算法的流程模擬,可以查看參考 [13]。

Paxos 算法有什麼缺點嗎?怎麼優化?

前面描述的可以稱之爲 Basic Paxos 算法,在單提議者的前提下是沒有問題的,但是假如有多個提議者互不相讓,那麼就可能導致整個提議的過程進入了死循環。

Lamport 提出了 Multi Paxos 的算法思想。

Multi Paxos 算法思想,簡單說就是在多個提議者的情況下,選出一個 Leader(領導者),由領導者作爲唯一的提議者,這樣就可以解決提議者衝突的問題。

10. 說說 Raft 算法?

Raft 算法是什麼?

Raft 也是一個 一致性算法,和 Paxos 目標相同。但它還有另一個名字 - 易於理解的一致性算法PaxosRaft 都是爲了實現 一致性 產生的。這個過程如同選舉一樣,參選者 需要說服 大多數選民 (Server) 投票給他,一旦選定後就跟隨其操作。PaxosRaft 的區別在於選舉的 具體過程 不同。

Raft 算法的工作流程?

Raft 算法的角色

Raft 協議將 Server 進程分爲三種角色:

就像一個民主社會,領導者由跟隨者投票選出。剛開始沒有 領導者,所有集羣中的 參與者 都是 跟隨者

那麼首先開啓一輪大選。在大選期間 所有跟隨者 都能參與競選,這時所有跟隨者的角色就變成了 候選人,民主投票選出領袖後就開始了這屆領袖的任期,然後選舉結束,所有除 領導者候選人 又變回 跟隨者 服從領導者領導。

這裏提到一個概念 「任期」,用術語 Term 表達。

三類角色的變遷圖如下:

Raft 三種角色變遷圖

Leader 選舉過程

Raft 使用心跳(heartbeat)觸發 Leader 選舉。當 Server 啓動時,初始化爲 Follower。Leader 向所有 Followers 週期性發送 heartbeat。如果 Follower 在選舉超時時間內沒有收到 Leader 的 heartbeat,就會等待一段隨機的時間後發起一次 Leader 選舉。

Follower 將其當前 term 加一然後轉換爲 Candidate。它首先給自己投票並且給集羣中的其他服務器發送 RequestVote RPC 。結果有以下三種情況:

Leader 選舉

選出 Leader 後,Leader 通過 定期 向所有 Follower 發送 心跳信息 維持其統治。若 Follower 一段時間未收到 Leader心跳,則認爲 Leader 可能已經掛了,然後再次發起 選舉 過程。

分佈式設計

11. 說說什麼是冪等性?

什麼是冪等性?

冪等性是一個數學概念,用在接口上:用在接口上就可以理解爲:同一個接口,多次發出同一個請求,請求的結果是一致的。

簡單說,就是多次調用如一次。

什麼是冪等性問題?

在系統的運行中,可能會出現這樣的問題:

  1. 用戶在填寫某些form表單時,保存按鈕不小心快速點了兩次,表中竟然產生了兩條重複的數據,只是 id 不一樣。

  2. 開發人員在項目中爲了解決接口超時問題,通常會引入了重試機制。第一次請求接口超時了,請求方沒能及時獲取返回結果(此時有可能已經成功了),於是會對該請求重試幾次,這樣也會產生重複的數據。

  3. mq 消費者在讀取消息時,有時候會讀取到重複消息,也會產生重複的數據。

這些都是常見的冪等性問題。

在分佈式系統裏,只要下游服務有寫(保存、更新)的操作,都有可能會產生冪等性問題。

PS: 冪等和防重有些不同,防重強調的防止數據重複,冪等強調的是多次調用如一次,防重包含冪等。

怎麼保證接口冪等性?

接口冪等性

  1. insert 前先 select

    在保存數據的接口中,在insert前,先根據requestId等字段先select一下數據。如果該數據已存在,則直接返回,如果不存在,才執行  insert操作。

  2. 加唯一索引

    加唯一索引是個非常簡單但很有效的辦法,如果重複插入數據的話,就會拋出異常,爲了保證冪等性,一般需要捕獲這個異常。

    如果是java程序需要捕獲:DuplicateKeyException異常,如果使用了spring框架還需要捕獲:MySQLIntegrityConstraintViolationException異常。

  3. 加悲觀鎖

    更新邏輯,比如更新用戶賬戶餘額,可以加悲觀鎖,把對應用戶的哪一行數據鎖住。同一時刻只允許一個請求獲得鎖,其他請求則等待。

    select * from user id=123 for update;

    這種方式有一個缺點,獲取不到鎖的請求一般只能報失敗,比較難保證接口返回相同值。

  4. 加樂觀鎖

    更新邏輯,也可以用樂觀鎖,性能更好。可以在表中增加一個timestamp或者version字段,例如version:

    在更新前,先查詢一下數據,將 version 也作爲更新的條件,同時也更新 version:

    update user set amount=amount+100,version=version+1 where id=123 and version=1;

    更新成功後,version 增加,重複更新請求進來就無法更新了。

  5. 建防重表

    有時候表中並非所有的場景都不允許產生重複的數據,只有某些特定場景纔不允許。這時候,就可以使用防重表的方式。

    例如消息消費中,創建防重表,存儲消息的唯一 ID,消費時先去查詢是否已經消費,已經消費直接返回成功。

  6. 狀態機

    有些業務表是有狀態的,比如訂單表中有:1 - 下單、2 - 已支付、3 - 完成、4 - 撤銷等狀態,可以通過限制狀態的流動來完成冪等。

  7. 分佈式鎖

    直接在數據庫上加鎖的做法性能不夠友好,可以使用分佈式鎖的方式,目前最流行的分佈式鎖實現是通過 Redis,具體實現一般都是使用 Redission 框架。

  8. token 機制

    請求接口之前,需要先獲取一個唯一的 token,再帶着這個 token 去完成業務操作,服務端根據這個 token 是否存在,來判斷是否是重複的請求。

分佈式限流

12. 你瞭解哪些限流算法?

計數器比較簡單粗暴,比如我們要限制 1s 能夠通過的請求數,實現的思路就是從第一個請求進來開始計時,在接下來的 1s 內,每個請求進來請求數就 + 1,超過最大請求數的請求會被拒絕,等到 1s 結束後計數清零,重新開始計數。

這種方式有個很大的弊端:比如前 10ms 已經通過了最大的請求數,那麼後面的 990ms 的請求只能拒絕,這種現象叫做 “突刺現象”。

就是桶底出水的速度恆定,進水的速度可能快慢不一,但是當進水量大於出水量的時候,水會被裝在桶裏,不會直接被丟棄;但是桶也是有容量限制的,當桶裝滿水後溢出的部分還是會被丟棄的。

算法實現:可以準備一個隊列來保存暫時處理不了的請求,然後通過一個線程池定期從隊列中獲取請求來執行。

漏桶算法

令牌桶就是生產訪問令牌的一個地方,生產的速度恆定,用戶訪問的時候當桶中有令牌時就可以訪問,否則將觸發限流。

實現方案:Guava RateLimiter 限流

Guava RateLimiter 是一個谷歌提供的限流,其基於令牌桶算法,比較適用於單實例的系統。

令牌桶算法

這一期的分佈式面試題就整理到這裏了,主要是偏理論的一些問題,分佈式其實是個很大的類型,比如分佈式調用、分佈式治理……

所以,這篇文章只是個開始,後面還會有分佈式調用(RPC)、微服務相關的主題文章,敬請期待。

參考:

[1] . 《從 Paxos 到 Zookeeper 分佈式一致性原理與實踐》
[2]. 分佈式理論 (一) - CAP 定理:https://juejin.cn/post/6844903621490901006
[3]. 分佈式理論 (二) - BASE 理論 :https://juejin.cn/post/6844903621495095304
[4]. 分佈式理論 (三) - 2PC 協議:https://juejin.cn/post/6844903621495095309
[5] . CAP 和 BASE 理論瞭解麼?可以結合實際案例說下不:https://juejin.cn/post/6898288789371027470
[6] 從分佈式事務解決到 Seata 使用,一梭子給你整明白了:https://juejin.cn/post/6944882663148748807
[7]. 高併發下如何保證接口的冪等性?:https://juejin.cn/post/6944559294939398158)
[8]. 分佈式理論 (三) - 2PC 協議 :https://juejin.cn/post/6844903621495095309
[9]. 再有人問你分佈式鎖,這篇文章扔給他:https://juejin.cn/post/6844903688088059912)
[10]. 分佈式理論 (五) - 一致性算法 Paxos :https://juejin.cn/post/6844903621499289613)
[11]. 《分佈式系統技術及其案例分析》
[12]. 不就是分佈式事務,這下徹底清楚了😎:https://juejin.cn/post/7008939082579443748)
[13]. 諸葛亮 VS 龐統,拿下 Paxos 共識算法:http://www.passjava.cn/#/03.Distributed/05. 諸葛 VS 龐統,拿下 Paxos?id = 諸葛亮 - vs - 龐統,拿下 - paxos - 共識算法
[14].http://icyfenix.cn/distribution/consensus/paxos.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/d84tWIjbcGKhwUptzkO2hQ