分散式系統:常見陷阱和複雜性

banq發表於2024-05-21

分散式系統的複雜性對於工程師和開發人員來說是一個重要的挑戰。隨著系統的發展,複雜性往往會增加,因此積極主動很重要。讓我們談談您在工作中可能會遇到哪些型別的複雜性以及處理這些複雜性的有效策略。

分散式系統和複雜性
在開發過程中,分散式系統是相互連線並處理單個任務的計算機網路。每臺計算機或節點都有自己的本地記憶體和處理器,並執行自己的程序。然而,他們使用一個共同的網路來進行協調和集中化。分散式系統非常可靠;一個元件的故障不會破壞整個網路。

在集中式計算系統中,一臺計算機具有一個處理器和一個記憶體來解決問題。在中心化系統中,有節點,但它們訪問中心節點,這會導致網路擁塞和緩慢。集中式系統存在單點故障——這是它的一個重要缺點。

複雜
複雜性可以從不同的角度和方面來定義。有兩個重要的主要定義。

在系統理論中,複雜性描述了系統的不同獨立部分如何相互作用和相互通訊:它們如何定義彼此之間的相互作用,它們如何相互依賴,它們有多少依賴關係,以及它們在整體中如何相互作用。

從軟體和技術的角度來看,複雜性是指軟體架構的細節,例如元件的數量。

單體架構
單體架構是集中式系統的一個很好的例子。它被表示為單個可部署且單個可執行的元件。例如,此類元件可能包含使用者介面和位於一處的不同模組。 

單體架構

儘管這種架構是構建軟體的傳統架構,但它有幾個重要的缺點:

  • 無法獨立擴充套件模組
  • 越來越難以控制日益增長的複雜性
  • 缺乏模組獨立部署
  • 維護龐大的程式碼資料庫具有挑戰性
  • 技術與廠商耦合

微服務架構
微服務架構是一種架構風格,也是面向服務的架構的一種變體,它將系統構建為鬆散耦合服務的集合。例如,公司、帳戶、客戶和 UI 表示為部署在多個節點上的單獨程序。

所有這些服務都有自己的時時共享資料庫,但這可能是一種不好的做法或反模式。

這種架構有一些優點。

  • 水平可擴充套件性是遊戲規則的改變者!您可以水平擴充套件資料庫,也可以水平擴充套件服務。從技術上講,任何基礎設施元件都可以透過克隆進行水平擴充套件,但必須解決許多挑戰。
  • 高可用性和容忍度:每當您有多個克隆時,您都可以組織一些技術來幫助您避免因崩潰、記憶體洩漏或斷電而導致的任何停機。
  • 地理分佈:如果我們在美國、歐洲或亞洲都有客戶,並且我們也希望為客戶帶來最佳體驗,我們就需要在世界各地分發這些服務,並組織更復雜的資料複製技術。
  • 技術選擇:您可以自由選擇您的解決方案。

質量屬性
任何系統在某種程度上都具有三個主要質量屬性:

  • 可靠性:儘管面臨挑戰,仍能繼續正常執行,這意味著具有容錯性或彈性;即使系統現在可靠執行,也不能保證未來的可靠性。效能下降的常見原因是負載增加:例如,系統可能已從 10,000 個併發使用者擴充套件到 100,000 個併發使用者,或者從 100 萬個擴充套件到 1000 萬個。
  • 可擴充套件性是我們用來描述系統處理增加的負載的能力的術語。值得注意的是,整個系統的可擴充套件性弱點是由其最薄弱的元件決定的。
  • 可維護性是為了讓需要使用系統的工程和運營團隊的生活變得更好。良好且穩定的抽象有助於降低複雜性,並使系統更容易修改和適應新的使用功能。

主要問題是什麼?
“任何可能出錯的事情都會出錯,而且是在最糟糕的時候出錯。”
— 墨菲定律

不可靠的網路
網路不可靠的原因有很多,例如:

  • 您的請求可能已丟失。
  • 您的請求可能正在佇列中等待,稍後會傳送。
  • 遠端節點可能發生故障(可能崩潰或斷電)。
  • 遠端節點可能暫時停止響應。
  • 遠端節點可能已經處理了您的請求,但響應已在網路上丟失。
  • 遠端節點可能已經處理了您的請求,但響應已延遲,將稍後傳送。
  • 網路不可靠

策略:超時
解決該問題最簡單的方法是在呼叫方端應用超時邏輯。例如,如果呼叫方在超時後未收到響應,則只會丟擲錯誤並向使用者顯示錯誤。

策略:重試
從規模上看,我們不能只是為每個網路問題丟擲異常並讓使用者不安或延遲系統執行。因此,如果響應表明出現問題,請重試。但是,如果伺服器處理了請求並且僅丟失了響應怎麼辦?在這種情況下,重試可能會導致多次訂單、多次付款、多次交易等嚴重後果。 

策略:冪等性
為了避免這種情況,我們可以利用一種名為冪等性的技術。

冪等性的概念涉及多次執行相同操作與僅執行一次具有相同效果的概念。為了實現一次語義的屬性,可以採用將冪等金鑰附加到請求的解決方案。使用相同的冪等金鑰重試相同的請求時,伺服器將驗證具有此類金鑰的請求是否已被處理,並且將簡單地返回先前的響應。因此,使用相同金鑰進行任意次數的重試不會對系統的行為產生有害影響。

策略:熔斷
另一種可能有助於防止過載並在發生故障時完全破壞伺服器的模式是斷路器。

斷路器充當代理,以防止正在維護的呼叫系統可能會出現故障,或者現在嚴重故障。出錯的原因有很多:記憶體洩漏、程式碼中的錯誤或錯誤的外部依賴項。在這種情況下,最好是快速失敗,而不是冒著級聯失敗的風險。

併發和丟失寫入
併發性是分散式系統中最複雜的挑戰之一。併發意味著多個計算同時發生。

因此,當嘗試透過不同的操作同時更新帳戶餘額時會發生什麼情況?在缺乏防禦機制的情況下,很可能出現競爭條件,這將不可避免地導致寫入丟失和資料不一致。在此示例中,兩個操作嘗試同時更新帳戶餘額。由於它們是並行執行的,最後一個完成的獲勝,從而導致一個重大問題。為了避免這個問題,可以採用各種技術。

策略:快照隔離
ACID 縮寫代表原子性、一致性、隔離性和永續性。所有流行的 SQL 資料庫都實現了這些屬性。

  • 原子性指定操作要麼完全執行,要麼失敗,無論發生在哪個階段。它可以讓我們確保另一個執行緒無法看到操作的半完成結果。
  • 一致性意味著所有不變數都已定義,並且在成功提交事務和更改狀態之前都將得到滿足。
  • ACID 意義上的隔離意味著併發執行的事務彼此隔離。有一個可序列化的隔離級別,它是順序處理所有事務的最嚴格的級別,但主要使用流行資料庫中稱為快照隔離的另一種級別。 
  • 永續性承諾一旦事務提交,所有資料都會被安全地儲存。

此級別的關鍵思想是資料庫跟蹤記錄的版本,並且無法提交已在當前事務之外修改的版本的事務。

策略:比較並設定
大多數 NoSQL 資料庫不提供 ACID 屬性,而選擇使用 BASE,其中此類資料庫進行比較並使用集合。此操作的目的是透過僅當該值自上次讀取以來未更改時才允許進行更新,從而避免丟失更新。如果當前值與您之前讀取的值不匹配,則更新無效,並且必須重試讀取-修改-寫入週期。

例如,Cassandra 提供輕量級事務,允許您利用各種IF、IF NOT EXISTS和IF EXISTS條件來防止併發問題。

策略:租賃
另一個潛在的解決方案是租賃模式。為了說明這一點,請考慮必須以獨佔方式更新資源的場景。租賃模式需要首先獲取具有資源到期期限的租約,然後更新它,最後返回租約。

如果發生故障,租約將自動到期,從而允許另一個執行緒訪問該資源。雖然這種技術非常有益,但存在程序暫停和時鐘不同步的風險,這可能會導致並行資源訪問出現問題。

雙寫問題
雙寫問題是分散式系統中出現的一個挑戰,特別是當多個資料來源或資料庫必須保持同步時。為了說明這一點,請考慮一個場景,其中新資料必須儲存在資料庫中並將訊息傳送到 Kafka。由於這兩個操作不是原子的,因此在釋出新訊息的過程中存在失敗的可能性。

如果在傳送訊息時嘗試進行事務處理,則會出現更成問題的情況。如果事務無法提交,外部系統可能已經被告知實際上沒有發生的更改。

策略:事務發件箱
一種潛在的解決方案是實施事務發件箱。這涉及將事件儲存在與操作本身相同的事務內的“OutboxEvents”表中。由於該過程的原子性,如果事務失敗,則不會儲存任何資料。

另一個必要的元件是 Relay,它定期輪詢 OutboxEvents 表並將訊息傳送到目的地。這種方法可以實現至少一項交付保證。然而,這不是一個問題,因為由於網路的不可靠性,所有消費者都必須是冪等的。 

策略:日誌尾隨
構建自定義事務發件箱的替代解決方案是利用資料庫事務日誌和自定義聯結器直接從此日誌讀取並將更改傳送到目的地。

這種方法有其自身的優點和缺點。例如,它需要與資料庫解決方案耦合,但允許在應用程式中編寫更少的程式碼。

不可靠的時鐘
時間跟蹤是任何軟體或基礎設施的一個基本方面,因為它可以強制執行超時、到期和指標收集。然而,時鐘的可靠性在分散式系統中是一個重大挑戰,因為時間的準確性取決於單個計算機的效能,而單個計算機的時鐘可能比其他計算機更快或更慢。

計算機使用的時鐘有兩種主要型別:時鐘和單調時鐘。時鐘根據特定日曆返回日期和時間,並且它們通常與網路時間協議 (NTP) 同步。然而,延遲和網路問題可能會影響同步過程,導致時鐘不同步。單調時鐘不斷前進,使它們適合測量持續時間。

然而,單調增加的值對於每臺計算機都是唯一的,限制了它們在多伺服器日期和時間比較中的使用。實現高精度時鐘同步是一項具有挑戰性的任務。在大多數情況下,這種解決方案的必要性並不明顯。然而,在需要遵守法規的情況下,可以使用精確時間協議,儘管這將需要大量投資。

可用性和一致性
CAP定理 認為,任何分散式資料儲存只能滿足三個保證中的兩個。然而,由於網路不可靠性並不是一個可以顯著影響的因素,因此在網路分割槽的情況下,唯一可行的選擇是在可用性或一致性之間做出選擇。

考慮這樣的場景:兩個客戶端從不同的節點讀取資料:一個從主節點,另一個從從節點。複製配置為在領導者更改後更新追隨者。但是,如果領導者出於某種原因停止響應,會發生什麼情況?

這可能是崩潰、網路分割槽或其他問題。在高可用系統中,必須分配一個新的領導者,但是我們如何在現有的追隨者之間進行選擇?為了解決這個問題,必須採用分散式共識演算法。然而,在深入研究該演算法的細節之前,有必要全面瞭解各種型別的一致性。 

一致性型別
用於描述保證的一致性主要有兩類。

  • 弱一致性,或者最終是弱一致性,意味著如果您停止對領導者進行更改,一段時間後,所有追隨者的資料將同步。
  • 強一致性是一種確保系統中的所有節點同時看到相同資料的屬性,無論它們訪問哪個節點。 

策略:分散式共識演算法(例如Raft)
回到leader崩潰時的問題,需要選舉一個新的leader。這個問題乍一看似乎很簡單,但實際上,在選擇合適的方法時必須考慮很多條件和權衡。

根據 Raft 協議,如果追隨者在指定的時間內沒有收到領導者的資料或心跳,則開始新的領導者選舉過程。每個複製單元(整體寫入節點或多個分片)都與一組 Raft 日誌和作業系統程序相關聯,這些程序維護日誌並將更改從領導者複製到追隨者。 

Raft 協議保證跟隨者按照領導者生成日誌記錄的順序接收日誌記錄。只要一半的跟隨者確認收到提交記錄並將其寫入 Raft 日誌,就會在領導者上提交使用者事務。

策略:閱讀領導者的意見
一種可能有效且簡單的策略是由剛剛儲存新資料的使用者從關注者那裡讀取資料以避免複製滯後。

結論
從單體架構到微服務,每種方法都有各自的優勢和挑戰。雖然單體架構提供了簡單性,但它們往往在可擴充套件性和可維護性方面存在問題,因此開發人員傾向於採用更模組化、更可擴充套件的微服務架構。

討論的核心是複雜性的管理,其表現形式多種多樣,從網路不可靠性到併發問題和雙寫問題。超時、重試、冪等性和斷路器等策略為減輕與不可靠網路相關的風險提供了有效的工具,而快照隔離、比較和設定以及租賃等技術則解決了併發和丟失寫入的挑戰。

此外,不可靠時鐘的關鍵問題凸顯了分散式系統中精確時間同步的重要性,解決方案包括從 NTP 同步到精確時間協議。此外,CAP 定理提醒我們可用性和一致性之間固有的權衡,需要透徹理解 Raft 等分散式共識演算法。

相關文章