如何保證最終一致性的模式

在大規模、高併發服務化系統中,一個功能被拆分成多個具有單一功能的子功能,一個流程會有多個系統的多個單一功能的服務組合實現,如果使用兩階段提交協議和三階段提交協議,則確實能解決系統間的一致性問題。除了這兩個協議的自身問題,其實現也比較複雜、成本比較高,最重要的是性能不好,相比來看,TCC 協議更簡單且更容易實現,但是 TCC 協議由於每個事務都需要執行 Try,再執行 Confirm,略顯臃腫,因此,現實系統的底線是僅僅需要達到最終一致性,而不需要實現專業的、複雜的一致性協議。實現最終一致性協議有一些非常有效、簡單的模式,下面就介紹這些模式及其應用場景。

查詢模式

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

爲了能夠實現查詢,每個服務操作都需要有唯一的流水號標識,也可使用此次服務操作對應的資源 ID 來標識,例如:請求流水號、訂單號等。

首先,單筆查詢操作是必須提供的,也鼓勵使用單筆訂單查詢,這時因爲每次調用需要佔用的負載是可控的。批量查詢則根據需要來提供,如果使用了批量查詢,則需要有合理的分頁機制,並且必須限制分頁的大小,以及批量查詢的吞吐量有容量評估、熔斷、隔離和限流等措施。

查詢模式如下圖所示。

補償模式

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

對於服務化系統中同步調用的操作,若業務操作發起方還沒有收到業務操作執行方的明確返回或者調用超時,這時業務發起方需要及時的調用業務執行方來獲得操作執行的狀態,這裏使用在前面學習的查詢模式。在獲得業務操作執行方的狀態後,如果業務執行方已經完成預設工作,則業務發起方向業務的使用方返回成功;如果業務操作執行方的狀態爲失敗或者未知,則會立即告訴業務使用方失敗,也叫做快速失敗策略,然後調用業務操作的逆向操作,保證操作不被執行或者回滾已經執行的操作,讓業務使用方、業務操作發起方和業務操作執行方最終達到一致狀態。

補償模式如下圖所示。

補償操作根據發起形式分爲以下幾種。

異步確保模式

異步確保模式是補償模式的一個典型案例,經常應用到使用方對響應時間要求不太高的場景中,通常把這類操作從主流程中摘除,通過異步的方式進行處理,處理後把結果通過通知系統通知給使用方。這個方案的最大好處是能夠對高併發流量進行消峯,例如:電商系統中的物流、配送,以及支付系統中的計費、入賬等。

在實踐中將要執行的異步操作封裝後持久入庫,然後通過定時撈取未完成的任務進行補償操作來實現異步確保模式,只要定時系統足夠健壯,則任何任務最終都會被成功執行。

異步確保模式如下圖所示。

若對某個操作遲遲沒有收到響應,則通過查詢模式、補償模式和異步確保模式來繼續未完成的操作。

定期校對模式

系統在沒有達到一致之前,系統間的狀態是不一致的,甚至是混亂的,需要通過補償操作來達到最終一致性的目的,但是如何來發現需要補償的操作呢?

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

另外,實現定期校對的一個關鍵就是分佈式系統中需要有一個自始至終唯一的 ID,生成全局唯一 ID 有以下兩種方法。

在分佈式系統中,全局唯一 ID 的分佈如下圖所示。

在實踐中想在分佈式系統中迅速定位問題時,可通過分佈式系統的調用鏈跟蹤系統進行他能夠跟蹤一個請求的調用鏈。調用鏈是從二維的維度跟蹤一個調用請求,最後形成一個調用樹,其原理可參考谷歌的 Dapper 論文及他的一個流行的開源實現項目 Pinpoint。

分佈式系統中的調用鏈跟蹤如下圖所示。

全局的唯一流水 ID 可以將一個請求在分佈式系統中的流轉路徑聚合,而調用鏈中的 SpanID 可以將聚合的請求路徑通過樹形結構進行展示,讓技術人員支持工作人員輕鬆的發現系統出現的問題,能夠快速定位出現問題的服務節點,提高應急效率。

在分佈式系統中構建了唯一 ID、調用鏈等基礎設施後,我們很容易對系統間的不一致進行覈對。通常我們需要構建第三方的定期覈對系統,從第三方的角度來監控服務執行的健康程度。

定期覈對系統如下圖所示。

定期校對模式多應用於金融系統中。金融系統由於涉及資金安全,需要保證準確性,所以需要多重的一致性保證機制,包括商戶交易對賬、系統間的一致性對賬、現金對賬、賬務對賬、手續費對賬等,這些都屬於定期校對模式。順便說一下,金融系統與社交應用在技術上的本質區別爲社交應用在於量大,而金融系統在於數據的準確性。

可靠消息模式

在分佈式系統中,對於主流程中優先級比較低的操作,大多采用異步的方式執行,也就是異步確保模型,爲了讓異步操作的調用方和被調用方充分解耦,也由於專業的消息隊列本身具有可伸縮、可分片、可持久等功能,我們通常通過消息隊列實現異步化。對於消息隊列,我們需要建立特殊的設施來保證可靠的消息發送及處理機的冪等性。

消息的可靠發送

消息的可靠發送可以認爲是盡最大努力發送消息通知,有以下兩種實現方法。

第 1 種,在發送消息之前將消息持久到數據庫,狀態標記爲待發送,然後發送消息,如果發送成功,則將消息改爲發送成功。定時任務定時從數據庫撈取在一定時間內未發送的消息並將消息發送。可靠消息發送模式 1 如下圖所示。

第 2 種,該實現方式與第 1 種類似,不同的是持久消息的數據庫是獨立的,並不耦合在業務系統中。發送消息前,先發送一個預消息給某個第三方的消息管理器,消息管理器將其持久到數據庫,並標記狀態爲待發送,在發送成功後,標記消息爲發送成功。定時任務定時從數據庫中撈取一定時間內未發送的消息,查詢業務系統是否要繼續發送,根據查詢結果來確定消息的狀態。可靠消息發送模式 2 如下圖所示。

一些公司把消息的可靠發送實現在了中間件裏,通過 Spring 的注入,在消息發送時自動持久消息記錄,如果有消息記錄沒有發送成功,則定時補償發送。

消息處理器的冪等性

如果我們要保證可靠的發送消息,簡單來說就是要保證消息一定發送出去,那麼需要有重試機制。有了重試機制後,消息就一定重複,那麼我們需要對重複的問題進行處理。

處理重複問題的最佳方式是保證操作的冪等性,冪等性的數學公式爲:

f(f(x)) = f(x)

保證操作的冪等性的常用方法如下。

緩存一致性模式

在大規模、高併發系統中的一個常見的核心需求就是億級的讀需求,顯然,關係型數據庫並不是解決高併發讀需求的最佳方案,互聯網精單做法就是使用緩存來抗住讀流量。下面是使用緩存來保證一致性的最佳實踐。

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