分佈式一致性問題解決思路 原

在分佈式系統中,對傳統的單體應用進行水平或垂直的拆分,拆分後不可避免的出現瞭如何保證系統狀態、數據之間一致性的問題。

在分佈式的架構中,一致性指分佈式服務化系統之間的弱一致性,包括應用系統的一致性和數據的一致性。

1.常見的一致性問題:

1.1 下訂單和扣庫存

在設計分佈式電商系統中如何保證下訂單和扣庫存保持一致的問題。

1.2 同步調用超時

系統A調用系統B超時,系統A可以明確得到超時反饋,但是無法確定系統B是否已經完成了請求的預定功能,系統B自己不能發現自己是否超時。

1.3 異步回調超時

系統A同步調用系統B發出指令,系統B接受指令並返回接收成功信息,系統處理後異步通知系統A處理結果,在這個過程中如果系統A由於某些原因沒有收到系統B的回調信息,就會導致兩個系統對同一處理事件的狀態不一致,導致系統錯誤。

1.4 緩存和數據庫不一致

高併發系統爲了保護數據庫需要在數據庫前加一層緩存,緩存和數據庫的一致性如何保證。

1.5 不同的緩存節點間的數據不一致

同一服務的多個節點爲了滿足更高的性能,需要使用本地緩存,這樣沒個節點都會有一份緩存數據的拷貝,如果這些緩存數據是半靜態的或者經常被更新,則被更新時各個節點的更新是有先後順序的,在更新的瞬間,在某個時間窗口內各個節點的數據是不一致的,重複的請求進入不同的節點執行的邏輯可能就不同。

1.6 緩存數據結構不一致

有的系統在緩存中暫存某種類型的數據,該數據由多個數據元素組成,其中,某個數據元素需要從數據庫或者服務中獲取,如果一部分元素獲取失敗,由於程序處理不正確,仍然將不完全的數據存入緩存中,緩存的使用者可能會因爲緩存的不完整出現異常。

 

2.解決一致性問題的模式和思路

2.1 分佈式一致性協議

國際開放標準組織Open Group定義了DTS(分佈式事務處理模型),根據DTS衍生了三種常用的協議。

2.1.1 兩階段提交協議

兩階段提交協議把分佈式事務分爲兩個階段,一個是準備階段,另一個是提交階段。準備階段和提交階段都是由事務管理器(下文協調者)發起的。

兩階段提交協議的流程如下所述。

a.準備階段:事務管理器向事務參與者發起指令,參與者評估自己的狀態,如果參與者評估指令可以完成,則會寫redo或者undo日誌,然後鎖定資源,執行操作,但是並不提交。

b.提交階段:如果每個參與者明確返回準備成功,也就是預留資源和執行操作成功,則事務管理器向參與者提交指令,參與者提交資源變更的事務,釋放鎖定的資源;如果任何一個參與者明確返回準備失敗,也就是預留資源或者執行操作失敗,則事務管理器向事務參與者發起中止指令,參與者取消已經變更的事務,執行undo日誌,釋放鎖定的資源。

流程如下圖(事務管理器起協調者作用):

 

2.1.2 三階段提交協議

三階段提交協議是兩階段提交協議的改進版本,通過超時機制解決了阻塞問題,並且把兩個階段增加爲一下三個階段。

a.詢問階段:協調者詢問參與者是否可以完成指令,協調者只需要回答是或者不是,而不需要做真正的操作,這個階段超時會導致中止。

b.主備階段:如果在詢問階段所有參與者都返回可以執行操作,則協調者向參與者發送預執行請求,然後參與者寫redo和undo日誌,執行操作但是不提交操作;如果在詢問階段任意參與者返回不能執行操作的結果,則協調者向參與者發送中止請求,這裏的邏輯與兩階段提交協議的準本階段相似。

c.提交階段:如果每個參與者在準備階段都返回成功,則協調者向參與者發起提交指令,參與者提交資源變更的事務,釋放鎖定的資源;如果任何參與者返回準備失敗,即預留資源或者執行操作失敗,則協調者向參與者發起中止指令,參與者取消已經變更的事務,執行undo日誌,釋放鎖定的資源。

流程如下圖:

2.1.3 TCC協議

兩階段提交協議和三階段提交協議實現方案中包含多個參與者、多個階段實現一個事務,實現複雜,性能也是個很大的問題,因此嚴格意義上的兩三階段協議在實際高併發系統中很少使用。

後來有人這此基礎上提出了TCC協議,TCC協議將一個任務拆分成Try、Confirm、Cancel三個步驟,正常的流程會先執行Try,如果執行沒有問題,則載執行Confirm,如果執行過程中出了問題,則執行操作的逆操作Cancel。從正常流程來看,這仍然是一個兩階段提交協議,但是在執行出現問題時有一定的自我修復能力,如果任何參與者出現了問題,則協調者通過執行操作的逆操作Cancel之前的操作,達到最終的一致狀態。

 

3 保證最終的一致性

上述描述的分佈式事務協議實現起來複雜,對系統性能也有比較大的影響,所以在我們實際的業務中可以去考慮是否有必要實現事務的強一致性,如果我們的系統達到最終一致性就可以滿足我的需求的話,儘量選擇實現最終一致性,避免複雜的實現協議。最終的一致性可以用一些簡單有效的方式來實現。

3.1 查詢模式

你的服務對外提供一個查詢接口,用來向外部輸出操作執行的狀態。服務操作的使用方可以通過查詢接口得知服務操作的執行狀態,然後根據不同的狀態來做不同的處理操作。

3.2 補償模式

查詢模式中,在任何情況下,我們都能得知具體的操作所處的狀態,如果整個操作都處於不正常的狀態,則我們需要修正操作中有問題的子操作,這可能需要重新執行未完成的子操作,後者取消已經完成的子操作,通過修復使整個分佈式系統達到一致。爲了讓系統最終達到一致狀態而做出的努力都叫做補償。

3.3 異步確保模式

異步確保模式是補償模式的一個特例,經常應用到使用方對響應時間要求不太高的場景中,通常把這類操作從主流程中分離出來,通過異步的方式進行處理,然後把結果通知使用方。在實際場景中,將要執行的異步操作封裝後持久入庫,然後通過定時撈取未完成的任務進行補償操作來實現異步確保模式,只要定時系統足夠健壯,則任何任務最終都會被執行。

3.4 定期校對模式

在操作主流程中的系統執行校對操作,可以在事後異步地批量校對操作的狀態,如果發現不一致的操作,則進行補償,補償操作與補償模式中的補償操作一致。

實現定期校對的關鍵是分佈式系統中需要一個始終唯一的ID,關於生成全局唯一ID的方法可以參考我另一篇文章。

3.5 可靠消息模式

在分佈式系統中,對於主流程中優先級比較低的操作,大多采用異步的方式執行,而在異步化的實現上消息隊列是我們常用的選擇。如何保證分佈式消息隊列的可靠性成了必須解決的問題。前提:消息可靠性發送,在發送消息前將消息持久到數據庫,狀態標記爲未發送,然後發送消息,如果發送成功,則將消息改爲發送成功。定時任務定時從數據庫撈取在一定數據內未發送的消息並將消息發送。消息可靠發送機制實現後,我們需要在消息接收處理器實現冪等性,之前爲了保證消息可靠發送需要重試機制,有了重試機制後消息一定會重複,所以消息處理端需要對重複的問題進行處理。

3.6 緩存一致性模式

在高併發的系統中,讓大量請求直接打到數據庫是有嚴重問題的,常用的做法就是使用緩存來抗住讀流量,下面是關於實現緩存一致性的一些方法。

  • 如果性能要求不是很高,則儘量使用分佈式緩存,而不用本地緩存。
  • 寫緩存時數據一定要完整,如果緩存數據的一部分有效,另一部分無效,則寧可在需要的時候匯源數據庫,也不要把部分數據放入緩存中。
  • 使用緩存犧牲了一致性,爲了提高性能,數據庫與緩存只需要保持弱一致性,而不是要保持強一制性,否則違背了使用緩存的初衷。
  • 讀的順序是先讀緩存,後讀數據庫,寫的順序要先寫數據庫,後寫緩存。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章