框架篇:分散式一致性解決方案

潛行前行 發表於 2021-06-05

前言

上一篇架構篇:分散式理論CAP、BASE,我們瞭解到分散式存在的問題以及大致的解決理論,但是具體的實現協議或者方案有哪些?

  • 分散式一致性
  • 分散式共識演算法
    • paoxs、Raft、zab
  • 分散式事務一致性
  • 分散式事務一致性的實現方案(XA模式和AT模式)
    • 兩階段提交
    • 三階段提交
    • 柔性事務TCC
    • AT模式
    • 事件通知

關注公眾號,一起交流,微信搜一搜: 潛行前行

1 分散式一致性

  • 什麼是分散式一致性?分散式一致性其實更多是偏向解決多個服務間的資料副本狀態的一致,而不同於關係型資料庫的一致性(資料的約束)

2 分散式共識演算法

paoxs演算法

  • Paxos演算法是基於訊息傳遞且具有高度容錯特性的一致性演算法,是目前公認的解決分散式一致性問題最有效的演算法之一
  • Paxos演算法的通俗理解
    • 假設有十個人要去旅遊,目的地有成都和拉薩兩個地點。為了統一目的地,簡單的方法可以拉個微信群組聊天,大家投票,按少數服從多數的原則。但是在Paxos演算法裡,覺得微信平臺不可靠,它掛了怎麼辦?Paxos的原則是容錯性一定要很強,所以paxos採取相互發簡訊
    • 找另外三個人當中介人(也可從十個人中選,也不侷限三個中介),十個人給他們發簡訊,中介者之間可以不通訊
  • 申請階段:每個人的簡訊都會帶一個傳送時間,中介只會和最新簡訊的提議者交流,而且只能和一個人交流。每個人瘋狂向中介發簡訊,希望獲得溝通權
  • 溝通階段:如果獲得半數的中介者溝通權。提議者則會給這些中介提議自己希望的旅遊地(例如成都)。而收到的結果有三種;
    • A: 超過半數的中介者同意,收東西去成都;
    • B: 至少有一箇中介者決定了旅遊地(不一定是成都,可能是其他提議者和中介商定的拉薩),那先看看是否超過半數的旅遊地,如果沒有,則下次頂最近時間選擇出的旅遊地
    • C: 失去溝通權,再繼續發簡訊。。。。。。
  • Paxos的一致性,是為了解決冗餘副本的一致性,和關係型資料庫中ACID的一致性說的不是一個東西

Raft演算法

  • 由於Paxos難以理解,也難以實現。於是有了新的共識演算法。Raft有三種角色
    • Leader: 處理所有客戶端互動,日誌複製等,同一時刻只有一個有效的Leader
    • Follower: 類似選民,完全被動
    • Candidate候選人: 可以被選為一個新的領導人

選舉階段

  • 一開始任何一個伺服器都是Follwer,它們內建一個倒數計時,當倒數計時結束時變成Candidate,向其他follwers發出要求選舉自己的請求
  • 此時有三個狀態
    • A:超過半數follwers追隨,成為新的leader
    • B:存在競爭者,且有超過半數追隨者,放棄競選,成為其follwer
    • C:存在競爭者,大家半斤八兩。Candidate者則在下個競選週期term再次發起競選,此時也有內建一個倒數計時,誰先倒數計時結束快,誰則先成為搶佔半數follwer的leader(注意:前一輪成為別人的follwer不能在競選了)

日誌複製階段

  • 1:Leader領導人已經選出,客戶端發出增加一個日誌的要求,比如日誌是"hello"
  • 2:Leader要求Followe遵從他的指令,都將這個新的日誌內容追加到他們各自日誌中
  • 3:大多數follower伺服器將日誌寫入磁碟檔案後,確認追加成功,發出Commited Ok
  • 4:在下一個心跳heartbeat中,Leader會通知所有Follwer更新commited 專案
  • 如果在這一過程中,發生了網路分割槽或者網路通訊故障。使得Leader不能訪問大多數Follwers了,而follwers重新選舉新的Leader堆外提供服務。在恢復網路時,舊的leader會成為擁有多數follwer的新Leader的follwer。故障期間的commit回滾

zab演算法

ZXID

協議的事務編號 Zxid 設計中, Zxid 是一個 64位的數字

  • 其中低 32 位是一個簡單的單調遞增的計數器, 針對客戶端每一個事務請求,計數器加 1
  • 而高 32 位則代表 Leader 週期 epoch 的編號,每個當選產生一個新的 Leader 伺服器,就會從這個 Leader 伺服器上取出其本地日誌中的最大事務 ZXID ,並從中讀取 epoch 值,然後加 1 ,以此作為新的 epoch。而低 32 位計數器從 0 開始重新計數

崩潰恢復模式(選舉)

  • 叢集初始化或者Leader失去連線時,節點(任意節點)發起選主,然後叢集其他節點會為發起選主的節點進行投票
  • 節點B判斷確定A可以成為Leader,那麼節點B就投票給節點A,判斷的依據是: election epoch(A) > election epoch (B) || zxid(A) > zxid(B) || sid(A) > sid(B)。並更新自己的投票為B投票
  • sid是服務ID,人為配置的

訊息廣播模式

  • Leader將客戶端的request轉化成一個Proposal(提議)
  • Leader為每一個Follower準備了一個FIFO佇列,並把Proposal傳送到佇列上
  • Leader若收到follower的半數以上ACK反饋
  • Leader向所有的follower傳送commit
    image.png

一些細節

  • Leader在收到客戶端請求之後,會將這個請求封裝成一個事務,並給這個事務分配一個全域性遞增的唯一ID,稱為事務ID(ZXID),ZAB兮協議需要保證事務的順序,因此必須將每一個事務按照ZXID進行先後排序然後處理
  • 在Leader和Follwer之間還有一個訊息佇列,用來解耦他們之間的耦合,解除同步阻塞
  • zookeeper叢集中為保證任何所有程式能夠有序的順序執行,只能是 Leader 伺服器接受寫請求,即使是 Follower 伺服器接受到客戶端的請求,也會轉發到 Leader 伺服器進行處理

3 分散式事務一致性

  • 對於分散式一致性和分散式事務一致性。我更願意區分開來:
  • A-分散式一致性是為了解決資料分佈在多個服務的狀態一致(多個副本保持一致)
  • B-分散式事務一致性,更加類似關係型資料庫的一致性,是約束資料在分散式服務的關係(比如資料a在服務A的狀態和資料b在服務B需要保持一個固定的對映關係)

分散式共識演算法和分散式一致性的區別

  • 共識演算法就是為了解決分散式一致性的演算法,但不適合解決分散式事務一致性(可以解決只是不合適)

4 分散式事務一致性的實現方案(XA模式和AT模式)

  • XA模式是預提交資料模式(預提交資料無法被其他事務訪問),如果發生故障,則回滾預提交的資料
  • AT模式的資料是確認提交的,只不過存在鎖,使該資料無法被其他事務訪問。如果發生故障,則使用衝正操作修復資料。相對XA模式,AT模式更適合解決分散式事務,減少阻塞等待時間

兩階段提交(強一致性)(XA模式)

二階段提交協議(Two-phase Commit,即 2PC)是常用的分散式事務解決方案,即將事務的提交過程分為兩個階段來進行處理:準備階段和提交階段

處理流程

階段 1:準備階段

  • 協調者向所有參與者傳送事務內容,詢問是否可以提交事務,並等待所有參與者答覆。
  • 各參與者執行事務操作,將 undo 和 redo 資訊記入事務日誌中(但不提交事務)。
  • 如參與者執行成功,給協調者反饋 yes,即可以提交;如執行失敗,給協調者反饋 no,即不可提交

階段 2:提交階段

  • 如果協調者收到了參與者的失敗訊息或者超時,直接給每個參與者傳送回滾(rollback)訊息;否則,傳送提交(commit)訊息。
  • 參與者根據協調者的指令執行提交或者回滾操作,釋放所有事務處理過程中使用的鎖資源

2PC 方案缺點:

  • 效能問題:所有參與者在事務提交階段處於同步阻塞狀態,佔用系統資源,容易導致效能瓶頸
  • 可靠性問題:如果協調者存在單點故障問題,如果協調者出現故障,參與者將一直處於鎖定狀態
  • 資料一致性問題:在提交階段commit時,如果發生區域性網路問題,一部分事務參與者收到了提交訊息,另一部分事務參與者沒收到提交訊息,會導致了節點之間資料的不一致

三階段提交(強一致性)(XA模式)

三階段提交協議,是二階段提交協議的改進版本,與二階段提交不同的是,引入超時機制。同時在協調者和參與者中都引入超時機制

處理流程

階段 1:canCommit

  • 協調者向參與者傳送 commit 請求,參與者如果可以提交就返回 yes 響應(參與者不執行事務操作),否則返回 no 響應:
  • 協調者向所有參與者發出包含事務內容的 canCommit 請求,詢問是否可以提交事務,並等待所有參與者答覆
  • 參與者收到 canCommit 請求後,如果認為可以執行事務操作,則反饋 yes 並進入預備狀態,否則反饋 no

階段 2:preCommit

  • 協調者根據階段 1 canCommit 參與者的反應情況來決定是否可以進行基於事務的 preCommit 操作。根據響應情況,有以下兩種可能
  • 情況 1:階段 1 所有參與者均反饋 yes,參與者預執行事務
    • 協調者向所有參與者發出 preCommit 請求,進入準備階段
    • 參與者收到 preCommit 請求後,執行事務操作,將 undo 和 redo 資訊記入事務日誌中(但不提交事務)
    • 各參與者向協調者反饋 ack 響應或 no 響應,並等待最終指令
  • 情況 2:階段 1 任何一個參與者反饋 no,或者等待協調者超時,無法收到所有參與者的反饋,即中斷事務
    • 協調者向所有參與者發出 abort 請求
    • 無論收到協調者發出的 abort 請求,或者在等待協調者請求過程中出現超時,參與者均會中斷事務

階段 3:do Commit

  • 該階段進行真正的事務提交,分為以下三種情況
  • 情況 1:階段 2 所有參與者均反饋 ack 響應,執行真正的事務提交
    • 如果協調者處於工作狀態,則向所有參與者發出 do Commit 請求,參與者收到 do Commit 請求後,會正式執行事務提交,並釋放整個事務期間佔用的資源
    • 各參與者向協調者反饋 ack 完成的訊息,協調者收到所有參與者反饋的 ack 訊息後,即完成事務提交
  • 情況 2:階段 2 任何一個參與者反饋 no,或者等待超時後協調者尚無法收到所有參與者的反饋,即中斷事務
    • 如果協調者處於工作狀態,向所有參與者發出 abort 請求,參與者使用階段 1 中的 undo 資訊執行回滾操作,並釋放整個事務期間佔用的資源
    • 各參與者向協調者反饋 ack 完成的訊息,協調者收到所有參與者反饋的 ack 訊息後,即完成事務中斷
  • 情況 3:協調者與參與者網路出現問題
    • 參與者在協調者發出 do Commit 或 abort 請求等待超時,仍會繼續執行事務提交

優缺點

  • 優點:在第二階段,在等待超時後協調者或參與者會中斷事務
  • 優點:在第三階段,避免了協調者單點問題,在協調者出現問題時,參與者會繼續提交事務(同時也是個缺點)
  • 缺點:資料不一致問題依然存在,在第三階段,如果協調者請求中斷事務,而協調者無法與參與者正常通訊,會導致參與者繼續提交事務,造成資料不一致

柔性事務TCC (XA模式在服務級別的實現)

image.png

  • Try階段:需要做資源的檢查和預留。在扣錢場景下,Try 要做的事情是就是檢查賬戶可用餘額是否充足,再凍結賬戶的資金。Try 方法執行之後,賬號餘額雖然還是100,但是其中 30 元已經被凍結了,不能被其他事務使用
  • Confirm階段: 扣減 Try 階段凍結的資金,Confirm 方法執行之後,賬號在一階段中凍結的 30 元已經被扣除,賬號 A 餘額變成 70 元
  • Cancel階段:回滾的話,就需要在 Cancel 方法內釋放一階段 Try 凍結的 30 元,使賬號的回到初始狀態,100 元全部可用

AT模式(阿里分散式框架seata)

一階段:提交

image.png

  • 在一階段,Seata 會攔截“業務 SQL”,首先解析SQL語義,找到“業務 SQL”要更新的業務資料,在業務資料被更新前,將其儲存成“before image”,然後執行“業務 SQL”更新業務資料,在業務資料更新之後,再將其儲存成“after image”,最後生成行鎖。以上操作全部在一個資料庫事務內完成,這樣保證了一階段操作的原子性

二階段提交或回滾

image.png

  • 二階段如果是提交的話,因為“業務 SQL”在一階段已經提交至資料庫, 所以 Seata 框架只需將一階段儲存的快照資料和行鎖刪掉,完成資料清理即可

image.png

  • 二階段如果是回滾的話,Seata 就需要回滾一階段已經執行的“業務 SQL”,還原業務資料
  • 回滾方式便是用“before image”還原業務資料;但在還原前要首先要校驗髒寫,對比“資料庫當前業務資料”和 “after image”,如果兩份資料完全一致就說明沒有髒寫,可以還原業務資料,如果不一致就說明有髒寫,出現髒寫就需要轉人工處理

事件通知(事務訊息)

同步通知

image.png

  • 人的慣性思維都會考慮到同步呼叫,這是簡單易實現的方案。但是相對第三方系統,其是不可靠的,內部處理超時,網路斷開,很容易出事故。而且等待介面返回,是個阻塞過程,影響系統效能

非同步回撥通知

image.png

  • 相對同步通知,它的處理介面是非同步回撥的。因此可以避免超時處理,超時返回的問題
  • 考慮到回撥時介面報錯則需要發起重試回撥,因此需要加入重試機制

訊息佇列

image.png

  • 訊息佇列可以解耦服務,並且解決了錯誤重試的問題
  • 因為調介面會出錯或者重複呼叫,需要保證介面冪等性
  • 普通訊息處理存在的一致性問題:傳送者業務邏輯處理成功 -> MQ儲存訊息成功 -> 但是MQ處理超時 -> 從而ACK確認失敗 -> 導致傳送者本地事務回滾,但實際MQ是處理成功
  • 如果存在處理返回結果也可以通過訊息佇列回傳

事務狀態表+訊息佇列方案

image.png

  • 基於本地訊息的最終一致性方案的最核心做法就是在執行業務操作的時候,記錄一條訊息資料到DB,並且訊息資料的記錄與業務資料的記錄必須在同一個事務內完成
  • 在記錄完成後訊息資料後,可以通過一個定時任務到DB中去輪訓狀態為待傳送的訊息,然後將訊息投遞給MQ。這個過程中可能存在訊息投遞失敗的可能,此時就依靠重試機制來保證,直到成功收到MQ的ACK確認之後,再將訊息狀態更新或者訊息清除
  • 同樣也需要保障介面的冪等性

歡迎指正文中錯誤

參考文章