本篇主要內容以下:java
前言
咱們都在討論分佈式,特別是面試的時候,無論是招初級軟件工程師仍是高級,都會要求懂分佈式,甚至要求用過。傳得沸沸揚揚的分佈式究竟是什麼東東,有什麼優點?程序員
借用火影忍術
看過火影
的同窗確定知道漩渦鳴人
的招牌忍術:多重影分身之術
。面試
- 這個術有一個特別厲害的地方,
過程和心得
:多個分身的感覺和經歷都是相通的。好比 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、分佈式消息隊列的坑
消息隊列如何作分佈式?
將消息隊列裏面的消息分攤到多個節點(指某臺機器或容器)上,全部節點的消息隊列之和就包含了全部消息。
1. 消息隊列的坑之非冪等
(1)冪等性概念
所謂冪等性就是不管多少次操做和第一次的操做結果同樣。若是消息被屢次消費,頗有可能形成數據的不一致。而若是消息不可避免地被消費屢次,若是咱們開發人員能經過技術手段保證數據的先後一致性,那也是能夠接受的,這讓我想起了 Java 併發編程中的 ABA 問題,若是出現了 [ABA 問題](用積木講解 ABA 原理 | 老婆竟然又聽懂了!),若能保證全部數據的先後一致性也能接受。
(2)場景分析
RabbitMQ
、RocketMQ
、Kafka
消息隊列中間件都有可能出現消息重複消費問題。這種問題並非 MQ 本身保證的,而是須要開發人員來保證。
這幾款消息隊列中間都是是全球最牛的分佈式消息隊列,那確定考慮到了消息的冪等性。咱們以 Kafka 爲例,看看 Kafka 是怎麼保證消息隊列的冪等性。
Kafka 有一個 偏移量
的概念,表明着消息的序號,每條消息寫到消息隊列都會有一個偏移量,消費者消費了數據以後,每過一段固定的時間,就會把消費過的消息的偏移量提交一下,表示已經消費過了,下次消費就從偏移量後面開始消費。
> 坑:
當消費完消息後,還沒來得及提交偏移量,系統就被關機了,那麼未提交偏移量的消息則會再次被消費。
以下圖所示,隊列中的數據 A、B、C,對應的偏移量分別爲 100、10一、102,都被消費者消費了,可是隻有數據 A 的偏移量 100 提交成功,另外 2 個偏移量因系統重啓而致使未及時提交。
重啓後,消費者又是拿偏移量 100 之後的數據,從偏移量 101 開始拿消息。因此數據 B 和數據 C 被重複消息。
以下圖所示:
(3)避坑指南
- 微信支付結果通知場景
- 微信官方文檔上提到微信支付通知結果可能會推送屢次,須要開發者自行保證冪等性。第一次咱們能夠直接修改訂單狀態(如支付中 -> 支付成功),第二次就根據訂單狀態來判斷,若是不是支付中,則不進行訂單處理邏輯。
- 插入數據庫場景
- 每次插入數據時,先檢查下數據庫中是否有這條數據的主鍵 id,若是有,則進行更新操做。
- 寫 Redis 場景
- Redis 的
Set
操做自然冪等性,因此不用考慮 Redis 寫數據的問題。
- 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 消息丟失總結
(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 被刪除。
- 可是若是有兩個消費者,消費順序是:消息二、消息 1。則最後結果是增長了數據 A。
RabbitMQ 解決方案:
- 將 Queue 進行拆分,建立多個內存 Queue,消息 1 和 消息 2 進入同一個 Queue。
- 建立多個消費者,每個消費者對應一個 Queue。
Kafka 場景:
- 建立了 topic,有 3 個 partition。
- 建立一條訂單記錄,訂單 id 做爲 key,訂單相關的消息都丟到同一個 partition 中,同一個生產者建立的消息,順序是正確的。
- 爲了快速消費消息,會建立多個消費者去處理消息,而爲了提升效率,每一個消費者可能會建立多個線程來並行的去拿消息及處理消息,處理消息的順序可能就亂序了。
Kafka 解決方案:
- 解決方案和 RabbitMQ 相似,利用多個 內存 Queue,每一個線程消費 1個 Queue。
- 具備相同 key 的消息 進同一個 Queue。
4. 消息隊列的坑之消息積壓
消息積壓:消息隊列裏面有不少消息來不及消費。
場景 1: 消費端出了問題,好比消費者都掛了,沒有消費者來消費了,致使消息在隊列裏面不斷積壓。
場景 2: 消費端出了問題,好比消費者消費的速度太慢了,致使消息不斷積壓。
> 坑:好比線上正在作訂單活動,下單所有走消息隊列,若是消息不斷積壓,訂單都沒有下單成功,那麼將會損失不少交易。
解決方案:解鈴還須繫鈴人
- 修復代碼層面消費者的問題,確保後續消費速度恢復或儘量加快消費的速度。
- 停掉現有的消費者。
- 臨時創建好原先 5 倍的 Queue 數量。
- 臨時創建好原先 5 倍數量的 消費者。
- 將堆積的消息所有轉入臨時的 Queue,消費者來消費這些 Queue。
5. 消息隊列的坑之消息過時失效
> 坑:
RabbitMQ 能夠設置過時時間,若是消息超過必定的時間尚未被消費,則會被 RabbitMQ 給清理掉。消息就丟失了。
解決方案:
- 準備好批量重導的程序
- 手動將消息閒時批量重導
6. 消息隊列的坑之隊列寫滿
> 坑:
當消息隊列因消息積壓致使的隊列快寫滿,因此不能接收更多的消息了。生產者生產的消息將會被丟棄。
解決方案:
- 判斷哪些是無用的消息,RabbitMQ 能夠進行
Purge Message
操做。 - 若是是有用的消息,則須要將消息快速消費,將消息裏面的內容轉存到數據庫。
- 準備好程序將轉存在數據庫中的消息再次重導到消息隊列。
- 閒時重導消息到消息隊列。
2、分佈式緩存的坑
在高頻訪問數據庫的場景中,咱們會在業務層和數據層之間加入一套緩存機制,來分擔數據庫的訪問壓力,畢竟訪問磁盤 I/O 的速度是很慢的。好比利用緩存來查數據,可能5ms就能搞定,而去查數據庫可能須要 50 ms,差了一個數量級。而在高併發的狀況下,數據庫還有可能對數據進行加鎖,致使訪問數據庫的速度更慢。
分佈式緩存咱們用的最多的就是 Redis了,它能夠提供分佈式緩存服務。
1. Redis 數據丟失的坑
哨兵機制
Redis 能夠實現利用哨兵機制
實現集羣的高可用。那什麼十哨兵機制呢?
- 英文名:
sentinel
,中文名:哨兵
。 - 集羣監控:負責主副進程的正常工做。
- 消息通知:負責將故障信息報警給運維人員。
- 故障轉移:負責將主節點轉移到備用節點上。
- 配置中心:通知客戶端更新主節點地址。
- 分佈式:有多個哨兵分佈在每一個主備節點上,互相協同工做。
- 分佈式選舉:須要大部分哨兵都贊成,才能進行主備切換。
- 高可用:即便部分哨兵節點宕機了,哨兵集羣仍是能正常工做。
> 坑:
當主節點發生故障時,須要進行主備切換,可能會致使數據丟失。
異步複製數據致使的數據丟失
主節點異步同步數據給備用節點的過程當中,主節點宕機了,致使有部分數據未同步到備用節點。而這個從節點又被選舉爲主節點,這個時候就有部分數據丟失了。
腦裂致使的數據丟失
主節點所在機器脫離了集羣網絡,實際上自身仍是運行着的。但哨兵選舉出了備用節點做爲主節點,這個時候就有兩個主節點都在運行,至關於兩個大腦在指揮這個集羣幹活,但到底聽誰的呢?這個就是腦裂。
那怎麼腦裂怎麼會致使數據丟失呢?若是發生腦裂後,客戶端還沒來得及切換到新的主節點,連的仍是第一個主節點,那麼有些數據仍是寫入到了第一個主節點裏面,新的主節點沒有這些數據。那等到第一個主節點恢復後,會被做爲備用節點連到集羣環境,並且自身數據會被清空,從新重新的主節點複製數據。而新的主節點因沒有客戶端以前寫入的數據,因此致使數據丟失了一部分。
避坑指南
- 配置 min-slaves-to-write 1,表示至少有一個備用節點。
- 配置 min-slaves-max-lag 10,表示數據複製和同步的延遲不能超過 10 秒。最多丟失 10 秒的數據
注意:緩存雪崩
、緩存穿透
、緩存擊穿
並非分佈式所獨有的,單機的時候也會出現。因此不在分佈式的坑之列。
3、分庫分表的坑
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 部分-
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
算法。- 基於 Snowflake 的優化算法。
- 借用將來時間和雙 Buffer 來解決時間回撥與生成性能等問題,同時結合 MySQL 進行 ID 分配。
-
美團的
Leaf-Snowflake
算法。- 爲何叫 Leaf(葉子):來自數學家萊布尼茨的一句話:「世界上沒有兩片相同的樹葉」,也就是說這個算法生成的 ID 是惟一的。
- 獲取 id 是經過代理服務訪問數據庫獲取一批 id(號段)。
- 雙緩衝:當前一批的 id 使用 10%時,再訪問數據庫獲取新的一批 id 緩存起來,等上批的 id 用完後直接用。
- 優勢:
- Leaf服務能夠很方便的線性擴展,性能徹底可以支撐大多數業務場景。
- ID號碼是趨勢遞增的8byte的64位數字,知足上述數據庫存儲的主鍵要求。
- 容災性高:Leaf服務內部有號段緩存,即便DB宕機,短期內Leaf仍能正常對外提供服務。
- 能夠自定義max_id的大小,很是方便業務從原有的ID方式上遷移過來。
- 即便DB宕機,Leaf仍能持續發號一段時間。
- 偶爾的網絡抖動不會影響下個號段的更新。
- 缺點:
- ID號碼不夠隨機,可以泄露發號數量的信息,不太安全。
4、分佈式事務的坑
怎麼理解事務?
-
事務能夠簡單理解爲要麼這件事情所有作完,要麼這件事情一點都沒作,跟沒發生同樣。
-
在分佈式的世界中,存在着各個服務之間相互調用,鏈路可能很長,若是有任何一方執行出錯,則須要回滾涉及到的其餘服務的相關操做。好比訂單服務下單成功,而後調用營銷中心發券接口發了一張代金券,可是微信支付扣款失敗,則須要退回發的那張券,且須要將訂單狀態改成異常訂單。
> 坑
:如何保證分佈式中的事務正確執行,是個大難題。
分佈式事務的幾種主要方式
- XA 方案(兩階段提交方案)
- TCC 方案(try、confirm、cancel)
- SAGA 方案
- 可靠消息最終一致性方案
- 最大努力通知方案
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
> 你好,我是悟空哥
,「7年項目開發經驗,全棧工程師,開發組長,超喜歡圖解編程底層原理」。
我還手寫了 2 個小程序
,Java 刷題小程序
,PMP 刷題小程序
,點擊個人公衆號菜單打開!
另外有 111 本架構師資料以及 1000 道 Java 面試題,都整理成了PDF。
能夠關注公衆號 「悟空聊架構」 回覆 悟空
領取優質資料。
「轉發->在看->點贊->收藏->評論!!!」 是對我最大的支持!
《Java併發必知必會》系列:
1.反制面試官 | 14張原理圖 | 不再怕被問 volatile!