您現在的位置是:首頁 > 攝影首頁攝影

這三年被分散式坑慘了,曝光十大坑

由 阿風的架構筆記 發表于 攝影2021-07-06
簡介但可能出現的問題是呼叫 ack 介面時失敗了,所以會出現訊息被髮送兩次的問題,這個時候就需要保證消費者消費訊息的冪等性

什麼是分割曝光

作 者:悟空聊架構

原文連結:https://mp。weixin。qq。com/s/kifv23_FPMUDQxY20oubeQ

本篇主要內容如下:

這三年被分散式坑慘了,曝光十大坑

主要內容

前言

我們都在討論分散式,特別是面試的時候,不管是招初級軟體工程師還是高階,都會要求懂分散式,甚至要求用過。傳得沸沸揚揚的分散式到底是什麼東東,有什麼優勢?

借用火影忍術

這三年被分散式坑慘了,曝光十大坑

風遁·螺旋手裡劍

看過火影的同學肯定知道漩渦鳴人的招牌忍術:多重影分身之術。

這個術有一個特別厲害的地方,過程和心得:多個分身的感受和經歷都是相通的。比如 A 分身去找卡卡西(鳴人的老師)請教問題,那麼其他分身也會知道 A 分身問的什麼問題。

漩渦鳴人有另外一個超級厲害的忍術,需要由幾個影分身完成:風遁·螺旋手裡劍。這個忍術是靠三個鳴人一起協作完成的。

這兩個忍術和分散式有什麼關係?

分佈在不同地方的系統或服務,是彼此相互關聯的。

分散式系統是分工合作的。

案例:

比如 Redis 的哨兵機制,可以知道叢集環境下哪臺 Redis 節點掛了。

Kafka的 Leader 選舉機制,如果某個節點掛了,會從 follower 中重新選舉一個 leader 出來。(leader 作為寫資料的入口,follower 作為讀的入口)

那多重影分身之術有什麼缺點?

會消耗大量的查克拉。分散式系統同樣具有這個問題,需要幾倍的資源來支援。

對分散式的通俗理解

是一種工作方式

若干獨立計算機的集合,這些計算機對於使用者來說就像單個相關係統

將不同的業務分佈在不同的地方

優勢可以從兩方面考慮:一個是宏觀,一個是微觀。

宏觀層面:多個功能模組糅合在一起的系統進行服務拆分,來解耦服務間的呼叫。

微觀層面:將模組提供的服務分佈到不同的機器或容器裡,來擴大服務力度。

任何事物有陰必有陽,那分散式又會帶來哪些問題呢?

需要更多優質人才懂分散式,人力成本增加

架構設計變得異常複雜,學習成本高

運維部署和維護成本顯著增加

多服務間鏈路變長,開發排查問題難度加大

環境高可靠性問題

資料冪等性問題

資料的順序問題

等等

講到分散式不得不知道 CAP 定理和 Base 理論,這裡給不知道的同學做一個掃盲。

CAP 定理

在理論計算機科學中,CAP 定理指出對於一個分散式計算系統來說,不可能通是滿足以下三點:

一致性(Consistency)

所有節點訪問同一份最新的資料副本。

可用性(Availability)

每次請求都能獲取到非錯的響應,但不保證獲取的資料為最新資料

分割槽容錯性(Partition tolerance)

不能在時限內達成資料一致性,就意味著發生了分割槽的情況,必須就當前操作在 C 和 A 之間做出選擇)

BASE 理論

BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent(最終一致性)三個短語的縮寫。BASE 理論是對 CAP 中 AP 的一個擴充套件,透過犧牲強一致性來獲得可用性,當出現故障允許部分不可用但要保證核心功能可用,允許資料在一段時間內是不一致的,但最終達到一致狀態。滿足 BASE 理論的事務,我們稱之為柔性事務。

基本可用 :

分散式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。如電商網址交易付款出現問題來,商品依然可以正常瀏覽。

軟狀態:

由於不要求強一致性,所以BASE允許系統中存在中間狀態(也叫軟狀態),這個狀態不影響系統可用性,如訂單中的“支付中”、“資料同步中”等狀態,待資料最終一致後狀態改為“成功”狀態。

最終一致性:

最終一致是指的經過一段時間後,所有節點資料都將會達到一致。如訂單的“支付中”狀態,最終會變為“支付成功”或者“支付失敗”,使訂單狀態與實際交易結果達成一致,但需要一定時間的延遲、等待。

一、分散式訊息佇列的坑

訊息佇列如何做分散式?

將訊息佇列裡面的訊息分攤到多個節點(指某臺機器或容器)上,所有節點的訊息佇列之和就包含了所有訊息。

1. 訊息佇列的坑之非冪等

冪等性概念

所謂冪等性就是無論多少次操作和第一次的操作結果一樣。如果訊息被多次消費,很有可能造成資料的不一致。而如果訊息不可避免地被消費多次,如果我們開發人員能透過技術手段保證資料的前後一致性,那也是可以接受的,這讓我想起了 Java 併發程式設計中的 ABA 問題,如果出現了 [ABA問題),若能保證所有資料的前後一致性也能接受。

場景分析

RabbitMQ、RocketMQ、Kafka 訊息佇列中介軟體都有可能出現訊息重複消費問題。這種問題並不是 MQ 自己保證的,而是需要開發人員來保證。

這幾款訊息佇列中間都是是全球最牛的分散式訊息佇列,那肯定考慮到了訊息的冪等性。我們以 Kafka 為例,看看 Kafka 是怎麼保證訊息佇列的冪等性。

Kafka 有一個 偏移量 的概念,代表著訊息的序號,每條訊息寫到訊息佇列都會有一個偏移量,消費者消費了資料之後,每過一段固定的時間,就會把消費過的訊息的偏移量提交一下,表示已經消費過了,下次消費就從偏移量後面開始消費。

坑:當消費完訊息後,還沒來得及提交偏移量,系統就被關機了,那麼未提交偏移量的訊息則會再次被消費。

如下圖所示,佇列中的資料 A、B、C,對應的偏移量分別為 100、101、102,都被消費者消費了,但是隻有資料 A 的偏移量 100 提交成功,另外 2 個偏移量因系統重啟而導致未及時提交。

這三年被分散式坑慘了,曝光十大坑

系統重啟,偏移量未提交

重啟後,消費者又是拿偏移量 100 以後的資料,從偏移量 101 開始拿訊息。所以資料 B 和資料 C 被重複訊息。

如下圖所示:

這三年被分散式坑慘了,曝光十大坑

重啟後,重複消費訊息

避坑指南

微信支付結果通知場景

微信官方文件上提到微信支付通知結果可能會推送多次,需要開發者自行保證冪等性。第一次我們可以直接修改訂單狀態(如支付中 -> 支付成功),第二次就根據訂單狀態來判斷,如果不是支付中,則不進行訂單處理邏輯。

插入資料庫場景

每次插入資料時,先檢查下資料庫中是否有這條資料的主鍵 id,如果有,則進行更新操作。

寫 Redis 場景

Redis 的 Set 操作天然冪等性,所以不用考慮 Redis 寫資料的問題。

其他場景方案

生產者傳送每條資料時,增加一個全域性唯一 id,類似訂單 id。每次消費時,先去 Redis 查下是否有這個 id,如果沒有,則進行正常處理訊息,且將 id 存到 Redis。如果查到有這個 id,說明之前消費過,則不要進行重複處理這條訊息。

不同業務場景,可能會有不同的冪等性方案,大家選擇合適的即可,上面的幾種方案只是提供常見的解決思路。

2. 訊息佇列的坑之訊息丟失

坑:訊息丟失會帶來什麼問題?如果是訂單下單、支付結果通知、扣費相關的訊息丟失,則可能造成財務損失,如果量很大,就會給甲方帶來巨大損失。

那訊息佇列是否能保證訊息不丟失呢?答案:否。主要有三種場景會導致訊息丟失。

這三年被分散式坑慘了,曝光十大坑

訊息佇列之訊息丟失

(1)生產者存放訊息的過程中丟失訊息

這三年被分散式坑慘了,曝光十大坑

生產者丟失訊息

解決方案

事務機制(不推薦,非同步方式)

對於 RabbitMQ 來說,生產者傳送資料之前開啟 RabbitMQ 的

事務機制

channel。txselect ,如果訊息沒有進佇列,則生產者受到異常報錯,並進行回滾 channel。txRollback,然後重試傳送訊息;如果收到了訊息,則可以提交事務 channel。txCommit。但這是一個同步的操作,會影響效能。

confirm 機制(推薦,非同步方式)

我們可以採用另外一種模式:confirm 模式來解決同步機制的效能問題。每次生產者傳送的訊息都會分配一個唯一的 id,如果寫入到了 RabbitMQ 佇列中,則 RabbitMQ 會回傳一個 ack訊息,說明這個訊息接收成功。如果 RabbitMQ 沒能處理這個訊息,則回撥 nack 介面。說明需要重試傳送訊息。

也可以自定義超時時間 + 訊息 id 來實現超時等待後重試機制。但可能出現的問題是呼叫 ack 介面時失敗了,所以會出現訊息被髮送兩次的問題,這個時候就需要保證消費者消費訊息的冪等性。

事務模式 和 confirm 模式的區別:

事務機制是同步的,提交事務後悔被

阻塞

直到提交事務完成後。

confirm 模式非同步接收通知,但可能

接收不到通知

。需要考慮接收不到通知的場景。

(2)訊息佇列丟失訊息

這三年被分散式坑慘了,曝光十大坑

訊息佇列丟失訊息

訊息佇列的訊息可以放到記憶體中,或將記憶體中的訊息轉到硬碟(比如資料庫)中,一般都是記憶體和硬碟中都存有訊息。如果只是放在記憶體中,那麼當機器重啟了,訊息就全部丟失了。如果是硬碟中,則可能存在一種極端情況,就是將記憶體中的資料轉換到硬碟的期間中,訊息隊列出問題了,未能將訊息持久化到硬碟。

解決方案

建立 Queue 的時候將其設定為持久化。

傳送訊息的時候將訊息的 deliveryMode 設定為 2 。

開啟生產者 confirm 模式,可以重試傳送訊息。

(3)消費者丟失訊息

這三年被分散式坑慘了,曝光十大坑

消費者丟失訊息

消費者剛拿到資料,還沒開始處理訊息,結果程序因為異常退出了,消費者沒有機會再次拿到訊息。

解決方案

關閉 RabbitMQ 的自動 ack,每次生產者將訊息寫入訊息佇列後,就自動回傳一個 ack給生產者。

消費者處理完訊息再主動 ack,告訴訊息佇列我處理完了。

問題:

那這種主動 ack 有什麼漏洞了?如果 主動 ack 的時候掛了,怎麼辦?

則可能會被再次消費,這個時候就需要冪等處理了。

問題:

如果這條訊息一直被重複消費怎麼辦?

則需要有加上重試次數的監測,如果超過一定次數則將訊息丟失,記錄到異常表或傳送異常通知給值班人員。

(4)RabbitMQ 訊息丟失總結

這三年被分散式坑慘了,曝光十大坑

RabbitMQ 丟失訊息的處理方案

(5)Kafka 訊息丟失

場景:

Kafka 的某個 broker(節點)宕機了,重新選舉 leader (寫入的節點)。如果 leader 掛了,follower 還有些資料未同步完,則 follower 成為 leader 後,訊息佇列會丟失一部分資料。

解決方案

給 topic 設定 replication。factor 引數,值必須大於 1,要求每個 partition 必須有至少 2 個副本。

給 kafka 服務端設定 min。insyc。replicas 必須大於 1,表示一個 leader 至少一個 follower 還跟自己保持聯絡。

3. 訊息佇列的坑之訊息亂序

坑: 使用者先下單成功,然後取消訂單,如果順序顛倒,則最後資料庫裡面會有一條下單成功的訂單。

RabbitMQ 場景:

生產者向訊息佇列按照順序傳送了 2 條訊息,訊息1:增加資料 A,訊息2:刪除資料 A。

期望結果:資料 A 被刪除。

但是如果有兩個消費者,消費順序是:訊息2、訊息 1。則最後結果是增加了資料 A。

這三年被分散式坑慘了,曝光十大坑

RabbitMQ訊息亂序場景

這三年被分散式坑慘了,曝光十大坑

RabbitMQ 訊息亂序場景

RabbitMQ 解決方案:

將 Queue 進行拆分,建立多個記憶體 Queue,訊息 1 和 訊息 2 進入同一個 Queue。

建立多個消費者,每一個消費者對應一個 Queue。

這三年被分散式坑慘了,曝光十大坑

RabbitMQ 解決方案

Kafka 場景:

建立了 topic,有 3 個 partition。

建立一條訂單記錄,訂單 id 作為 key,訂單相關的訊息都丟到同一個 partition 中,同一個生產者建立的訊息,順序是正確的。

為了快速消費訊息,會建立多個消費者去處理訊息,而為了提高效率,每個消費者可能會建立多個執行緒來並行的去拿訊息及處理訊息,處理訊息的順序可能就亂序了。

這三年被分散式坑慘了,曝光十大坑

Kafka 訊息丟失場景

Kafka 解決方案:

解決方案和 RabbitMQ 類似,利用多個 記憶體 Queue,每個執行緒消費 1個 Queue。

具有相同 key 的訊息 進同一個 Queue。

這三年被分散式坑慘了,曝光十大坑

Kafka 訊息亂序解決方案

4. 訊息佇列的坑之訊息積壓

訊息積壓:訊息佇列裡面有很多訊息來不及消費。

場景 1:

消費端出了問題,比如消費者都掛了,沒有消費者來消費了,導致訊息在佇列裡面不斷積壓。

場景 2:

消費端出了問題,比如消費者消費的速度太慢了,導致訊息不斷積壓。

坑:比如線上正在做訂單活動,下單全部走訊息佇列,如果訊息不斷積壓,訂單都沒有下單成功,那麼將會損失很多交易。

這三年被分散式坑慘了,曝光十大坑

訊息佇列之訊息積壓

解決方案:

解鈴還須繫鈴人

修復程式碼層面消費者的問題,確保後續消費速度恢復或儘可能加快消費的速度。

停掉現有的消費者。

臨時建立好原先 5 倍的 Queue 數量。

臨時建立好原先 5 倍數量的 消費者。

將堆積的訊息全部轉入臨時的 Queue,消費者來消費這些 Queue。

這三年被分散式坑慘了,曝光十大坑

訊息積壓解決方案

5. 訊息佇列的坑之訊息過期失效

坑:RabbitMQ 可以設定過期時間,如果訊息超過一定的時間還沒有被消費,則會被 RabbitMQ 給清理掉。訊息就丟失了。

這三年被分散式坑慘了,曝光十大坑

訊息過期失效

解決方案:

準備好批次重導的程式

手動將訊息限時批次重導

這三年被分散式坑慘了,曝光十大坑

訊息過期失效解決方案

6. 訊息佇列的坑之佇列寫滿

坑:當訊息佇列因訊息積壓導致的佇列快寫滿,所以不能接收更多的訊息了。生產者生產的訊息將會被丟棄。

解決方案:

判斷哪些是無用的訊息,RabbitMQ 可以進行 Purge Message 操作。

如果是有用的訊息,則需要將訊息快速消費,將訊息裡面的內容轉存到資料庫。

準備好程式將轉存在資料庫中的訊息再次重導到訊息佇列。

閒時重導訊息到訊息佇列。

二、分散式快取的坑

在高頻訪問資料庫的場景中,我們會在業務層和資料層之間加入一套快取機制,來分擔資料庫的訪問壓力,畢竟訪問磁碟 I/O 的速度是很慢的。比如利用快取來查資料,可能5ms就能搞定,而去查資料庫可能需要 50 ms,差了一個數量級。而在高併發的情況下,資料庫還有可能對資料進行加鎖,導致訪問資料庫的速度更慢。

分散式快取我們用的最多的就是 Redis了,它可以提供分散式快取服務。

1. Redis 資料丟失的坑

哨兵機制

Redis 可以實現利用哨兵機制實現叢集的高可用。那什麼是哨兵機制呢?

英文名:sentinel,中文名:哨兵。

叢集監控:負責主副程序的正常工作。

訊息通知:負責將故障資訊報警給運維人員。

故障轉移:負責將主節點轉移到備用節點上。

配置中心:通知客戶端更新主節點地址。

分散式:有多個哨兵分佈在每個主備節點上,互相協同工作。

分散式選舉:需要大部分哨兵都同意,才能進行主備切換。

高可用:即使部分哨兵節點宕機了,哨兵叢集還是能正常工作。

坑: 當主節點發生故障時,需要進行主備切換,可能會導致資料丟失。

非同步複製資料導致的資料丟失

主節點非同步同步資料給備用節點的過程中,主節點宕機了,導致有部分資料未同步到備用節點。而這個從節點又被選舉為主節點,這個時候就有部分資料丟失了。

腦裂導致的資料丟失

主節點所在機器脫離了叢集網路,實際上自身還是執行著的。但哨兵選舉出了備用節點作為主節點,這個時候就有兩個主節點都在執行,相當於兩個大腦在指揮這個叢集幹活,但到底聽誰的呢?這個就是腦裂。

那怎麼腦裂怎麼會導致資料丟失呢?如果發生腦裂後,客戶端還沒來得及切換到新的主節點,連的還是第一個主節點,那麼有些資料還是寫入到了第一個主節點裡面,新的主節點沒有這些資料。那等到第一個主節點恢復後,會被作為備用節點連到叢集環境,而且自身資料會被清空,重新從新的主節點複製資料。而新的主節點因沒有客戶端之前寫入的資料,所以導致資料丟失了一部分。

避坑指南

配置 min-slaves-to-write 1,表示至少有一個備用節點。

配置 min-slaves-max-lag 10,表示資料複製和同步的延遲不能超過 10 秒。最多丟失 10 秒的資料

注意:快取雪崩、快取穿透、快取擊穿並不是分散式所獨有的,單機的時候也會出現。所以不在分散式的坑之列。

三、分庫分表的坑

1.分庫分表的坑之擴容

分庫、分表、垂直拆分和水平拆分

分庫:

因一個數據庫支援的最高併發訪問數是有限的,可以將一個數據庫的資料拆分到多個庫中,來增加最高併發訪問數。

分表:

因一張表的資料量太大,用索引來查詢資料都搞不定了,所以可以將一張表的資料拆分到多張表,查詢時,只用查拆分後的某一張表,SQL 語句的查詢效能得到提升。

分庫分表優勢:分庫分表後,承受的併發增加了多倍;磁碟使用率大大降低;單表資料量減少,SQL 執行效率明顯提升。

水平拆分:

把一個表的資料拆分到多個數據庫,每個資料庫中的表結構不變。用多個庫抗更高的併發。比如訂單表每個月有500萬條資料累計,每個月都可以進行水平拆分,將上個月的資料放到另外一個數據庫。

垂直拆分:

把一個有很多欄位的表,拆分成多張表到同一個庫或多個庫上面。高頻訪問欄位放到一張表,低頻訪問的欄位放到另外一張表。利用資料庫快取來快取高頻訪問的新資料。比如將一張很多欄位的訂單表拆分成幾張表分別存不同的欄位(可以有冗餘欄位)。

分庫、分表的方式:

根據租戶來分庫、分表。

利用時間範圍來分庫、分表。

利用 ID 取模來分庫、分表。

坑:分庫分表是一個運維層面需要做的事情,有時會採取凌晨宕機開始升級。可能熬夜到天亮,結果升級失敗,則需要回滾,其實對技術團隊都是一種煎熬。

怎麼做成自動的來節省分庫分表的時間?

雙寫遷移方案:遷移時,新資料的增刪改操作在新庫和老庫都做一遍。

使用分庫分表工具 Sharding-jdbc 來完成分庫分表的累活。

使用程式來對比兩個庫的資料是否一致,直到資料一致。

坑: 分庫分表看似光鮮亮麗,但分庫分表會引入什麼新的問題呢?

垂直拆分帶來的問題

依然存在單表資料量過大的問題。

部分表無法關聯查詢,只能透過介面聚合方式解決,提升了開發的複雜度。

分散式事處理複雜。

水平拆分帶來的問題

跨庫的關聯查詢效能差。

資料多次擴容和維護量大。

跨分片的事務一致性難以保證。

2.分庫分表的坑之唯一 ID

為什麼分庫分表需要唯一 ID

如果要做分庫分表,則必須得考慮表主鍵 ID 是全域性唯一的,比如有一張訂單表,被分到 A 庫克 B 庫。如果 兩張訂單表都是從 1 開始遞增,那查詢訂單資料時就錯亂了,很多訂單 ID 都是重複的,而這些訂單其實不是同一個訂單。

分庫的一個期望結果就是將訪問資料的次數分攤到其他庫,有些場景是需要均勻分攤的,那麼資料插入到多個數據庫的時候就需要交替生成唯一的 ID 來保證請求均勻分攤到所有資料庫。

坑: 唯一 ID 的生成方式有 n 種,各有各的用途,別用錯了。

生成唯一 ID 的原則

全域性唯一性

趨勢遞增

單調遞增

資訊保安

生成唯一 ID 的幾種方式

資料庫自增 ID。每個資料庫每增加一條記錄,自己的 ID 自增 1。

多個庫的 ID 可能重複,這個方案可以直接否掉了,不適合分庫分表後的 ID 生成。

資訊不安全

缺點

適用 UUID 唯一 ID。

UUID 太長、佔用空間大。

不具有有序性,作為主鍵時,在寫入資料時,不能產生有順序的 append 操作,只能進行 insert 操作,導致讀取整個 B+ 樹節點到記憶體,插入記錄後將整個節點寫回磁碟,當記錄佔用空間很大的時候,效能很差。

缺點

獲取系統當前時間作為唯一 ID。

高併發時,1 ms內可能有多個相同的 ID。

資訊不安全

缺點

Twitter 的 snowflake(雪花演算法):Twitter 開源的分散式 id 生成演算法,64 位的 long 型的 id,分為 4 部分snowflake 演算法

基本原理和優缺點:

1 bit:不用,統一為 0

41 bits:毫秒時間戳,可以表示 69 年的時間。

10 bits:5 bits 代表機房 id,5 個 bits 代表機器 id。最多代表 32 個機房,每個機房最多代表 32 臺機器。

12 bits:同一毫秒內的 id,最多 4096 個不同 id,自增模式。

優點:

毫秒數在高位,自增序列在低位,整個ID都是趨勢遞增的。

不依賴資料庫等第三方系統,以服務的方式部署,穩定性更高,生成ID的效能也是非常高的。

可以根據自身業務特性分配bit位,非常靈活。

缺點:

強依賴機器時鐘,如果機器上時鐘回撥(可以搜尋

2017 年閏秒 7:59:60

),會導致發號重複或者服務會處於不可用狀態。

百度的 UIDGenerator 演算法。UIDGenerator 演算法

基於 Snowflake 的最佳化演算法。

借用未來時間和雙 Buffer 來解決時間回撥與生成效能等問題,同時結合 MySQL 進行 ID 分配。

優點:解決了時間回撥和生成效能問題。

缺點:依賴 MySQL 資料庫。

美團的 Leaf-Snowflake 演算法。

圖片來源於美團

獲取 id 是透過代理服務訪問資料庫獲取一批 id(號段)。

雙緩衝:當前一批的 id 使用 10%時,再訪問資料庫獲取新的一批 id 快取起來,等上新的 id 用完後直接用。

優點:

Leaf服務可以很方便的線性擴充套件,效能完全能夠支撐大多數業務場景。

ID號碼是趨勢遞增的8byte的64位數字,滿足上述資料庫儲存的主鍵要求。

容災性高:Leaf服務內部有號段快取,即使DB宕機,短時間內Leaf仍能正常對外提供服務。

可以自定義max_id的大小,非常方便業務從原有的ID方式上遷移過來。

即使DB宕機,Leaf仍能持續發號一段時間。

偶爾的網路抖動不會影響下個號段的更新。

缺點:

ID號碼不夠隨機,能夠洩露發號數量的資訊,不太安全。

怎麼選擇:一般自己的內部系統,雪花演算法足夠,如果還要更加安全可靠,可以選擇百度或美團的生成唯一 ID 的方案。

四、分散式事務的坑

怎麼理解事務?

事務可以簡單理解為要麼這件事情全部做完,要麼這件事情一點都沒做,跟沒發生一樣。

在分散式的世界中,存在著各個服務之間相互呼叫,鏈路可能很長,如果有任何一方執行出錯,則需要回滾涉及到的其他服務的相關操作。比如訂單服務下單成功,然後呼叫營銷中心發券介面發了一張代金券,但是微信支付扣款失敗,則需要退回發的那張券,且需要將訂單狀態改為異常訂單。

坑:如何保證分散式中的事務正確執行,是個大難題。

分散式事務的幾種主要方式

XA 方案(兩階段提交方案)

TCC 方案(try、confirm、cancel)

SAGA 方案

可靠訊息最終一致性方案

最大努力通知方案

XA 方案原理

這三年被分散式坑慘了,曝光十大坑

XA 方案

事務管理器負責協調多個數據庫的事務,先問問各個資料庫準備好了嗎?如果準備好了,則在資料庫執行操作,如果任一資料庫沒有準備,則回滾事務。

適合單體應用,不適合微服務架構。因為每個服務只能訪問自己的資料庫,不允許交叉訪問其他微服務的資料庫。

TCC 方案

Try 階段:對各個服務的資源做檢測以及對資源進行鎖定或者預留。

Confirm 階段:各個服務中執行實際的操作。

Cancel 階段:如果任何一個服務的業務方法執行出錯,需要將之前操作成功的步驟進行回滾。

應用場景:

跟支付、交易打交道,必須保證資金正確的場景。

對於一致性要求高。

缺點:

但因為要寫很多補償邏輯的程式碼,且不易維護,所以其他場景建議不要這麼做。

Sega 方案

基本原理:

業務流程中的每個步驟若有一個失敗了,則補償前面操作成功的步驟。

適用場景:

業務流程長、業務流程多。

參與者包含其他公司或遺留系統服務。

優勢:

第一個階段提交本地事務、無鎖、高效能。

參與者可非同步執行、高吞吐。

補償服務易於實現。

缺點:

不保證事務的隔離性。

可靠訊息一致性方案

這三年被分散式坑慘了,曝光十大坑

可靠訊息一致性方案

基本原理:

利用訊息中介軟體 RocketMQ 來實現訊息事務。

第一步:A 系統傳送一個訊息到 MQ,MQ將訊息狀態標記為 prepared(預備狀態,半訊息),該訊息無法被訂閱。

第二步:MQ 響應 A 系統,告訴 A 系統已經接收到訊息了。

第三步:A 系統執行本地事務。

第四步:若 A 系統執行本地事務成功,將 prepared 訊息改為 commit(提交事務訊息),B 系統就可以訂閱到訊息了。

第五步:MQ 也會定時輪詢所有 prepared的訊息,回撥 A 系統,讓 A 系統告訴 MQ 本地事務處理得怎麼樣了,是繼續等待還是回滾。

第六步:A 系統檢查本地事務的執行結果。

第七步:若 A 系統執行本地事務失敗,則 MQ 收到 Rollback 訊號,丟棄訊息。若執行本地事務成功,則 MQ 收到 Commit 訊號。

B 系統收到訊息後,開始執行本地事務,如果執行失敗,則自動不斷重試直到成功。或 B 系統採取回滾的方式,同時要透過其他方式通知 A 系統也進行回滾。

B 系統需要保證冪等性。

最大努力通知方案

基本原理:

系統 A 本地事務執行完之後,傳送訊息到 MQ。

MQ 將訊息持久化。

系統 B 如果執行本地事務失敗,則最大努力服務會定時嘗試重新呼叫系統 B,儘自己最大的努力讓系統 B 重試,重試多次後,還是不行就只能放棄了。轉到開發人員去排查以及後續人工補償。

幾種方案如何選擇

跟支付、交易打交道,優先 TCC。

大型系統,但要求不那麼嚴格,考慮 訊息事務或 SAGA 方案。

單體應用,建議 XA 兩階段提交就可以了。

最大努力通知方案建議都加上,畢竟不可能一出問題就交給開發排查,先重試幾次看能不能成功。

寫在最後

分散式還有很多坑,這篇只是一個小小的總結,從這些坑中,我們也知道分散式有它的優勢也有它的劣勢,那到底該不該用分散式,完全取決於業務、時間、成本以及開發團隊的綜合實力。後續我會繼續分享分散式中的一些底層原理,當然也少不了分享一些避坑指南。

參考資料:

美團的 Leaf-Snowflake 演算法。

百度的 UIDGenerator 演算法。

Advanced-Java、