您當前的位置:首頁 > 書法

[翻譯]分散式系統的模式-綜述

作者:由 流沙 發表于 書法時間:2020-08-11

本文翻譯自

https://

martinfowler。com/articl

es/patterns-of-distributed-systems/

,原作者對目前各類企業級架構中使用的多種分散式系統進行了總結,從中提取出了一些通用的“模式”(pattern)。本文作為系列文章的第一篇,介紹了分散式系統的特點和一些常見問題。

What this is about

分散式系統給程式設計帶來了特殊的挑戰。 它們通常要求我們擁有多個數據副本,這些副本需要保持同步。 但是我們無法期望各節點能永遠可靠地工作,同時,網路延遲也很容易導致不一致的現象出現。 儘管如此,許多組織仍依賴一系列核心的分散式系統來處理資料儲存,訊息傳遞,系統管理和計算等。 這些系統面臨很多共同的問題,這些問題也可以透過類似的方案來解決。 本文將這些解決方案定義為模式,透過這些模式,我們對於如何更好地理解,交流和教授分散式系統,將建立起更好的認知。

在過去的幾個月中,我一直在ThoughtWorks進行有關分散式系統的研討會。研討會中面臨的主要挑戰之一是如何將分散式系統的理論對映到諸如Kafka、Cassandra之類的開源專案中,同時還要保持討論足夠通用,能涵蓋更廣泛的解決方案。模式的概念提供了一個不錯的出路。

“模式”這個結構,本質上使我們能夠讓我們專注於特定問題,從而很清楚地說明為什麼需要特定的解決方案。然後,對解決方案的描述允許我們給出一個程式碼結構,這個結構足以具體到能夠表示出實際解決方案的程度,但同時又足夠通用以涵蓋各種變化。模式還允許我們將各種模式連結在一起以構建一個完整的系統。這樣,我們就有足夠的詞彙表,來討論分散式系統的實現。

接下來是在主流開源分散式系統中觀察到的第一組模式。我希望這些模式集對所有開發人員都有用。

分散式系統-實現角度

現在的企業架構中有很多天然分散式的平臺和框架。如果我們在當前典型的架構抽樣檢視平臺、框架,會看到下面這麼一些東西。

Type of platform/framework Example

Databases Cassandra, HBase, Riak

Message Brokers Kafka, Pulsar

Infrastructure Kubernetes, Mesos, Zookeeper, etcd, ConsulIn

Memory Data/Compute Grids Hazlecast, Pivotal Gemfire

Stateful Microservices Akka Actors, Axon

File Systems HDFS, Ceph

這些系統都天然是分散式的。關於什麼樣的系統叫“分散式”系統,包括兩個方面:

執行在多臺伺服器上。叢集內伺服器的數量差別可以非常大,從三臺到幾千臺都可能。

要處理資料,所以它們是內在“有狀態”的系統。

當多個伺服器參與儲存資料時,有幾種出錯的可能。 上述所有系統都需要解決這些問題。 對這些問題,這些系統的實現方式中有一些重複的解決方案。 以更通用的形式理解這些解決方案,有助於理解這些系統的廣泛實現方式,並且在需要構建新系統時也可以作為很好的指導。

模式,是Christopher Alexander提出的,在軟體社群中被廣泛接受的概念,用於在軟體系統設計時記錄設計架構。 模式為解決問題提供了一種結構化的方式,可以透過多次檢視和驗證來解決問題。 使用模式的一種有趣方式是能夠以模式序列或模式語言的形式將多個模式連結在一起,這為實現“整個”或完整的系統提供了一些指導。 將分散式系統視為一系列模式,是深入瞭解其實現方式的有效方法。

問題及其反覆出現的解決方案

當用多臺伺服器儲存資料時,會有幾種出問題的方式。

程序崩潰

由於硬體或軟體因素,程序在任何時刻都可能崩潰。有若干種程序崩潰的方式。

系統管理員進行常規的系統維護,程序會被關閉

進行IO操作,但磁碟空間不足,且異常沒有被正確處理時,會導致程序被殺掉

在雲環境下,可能會更復雜,一些無關的原因也可能導致系統宕機。

最重要的是,如果程序負責儲存資料,則必須將它們設計為能夠對儲存在伺服器上的資料提供永續性保證。 即使程序突然崩潰,它也應保留所有成功給過使用者ack確認訊息的資料。 根據訪問模式,不同的儲存引擎具有不同的儲存結構,範圍從簡單的hash表到複雜的圖結構都有。 將資料重新整理到磁碟是最耗時的操作之一,因此無法將每次插入或更新都立即重新整理到磁碟。 因此,大多數資料庫具有記憶體儲存結構,記憶體中的資料定期重新整理到磁碟。 如果程序突然崩潰,可能會丟失所有這些資料。

一種叫做Write-Ahead Log的技術就是用來解決這個問題的。伺服器將每個狀態更改作為命令儲存在硬碟上的僅附加(append-only)檔案中。 append操作通常是非常快的,因此可以在不影響效能的情況下完成。 順序寫單個日誌檔案,以儲存每個更新。 在伺服器啟動時,可以重播日誌以再次建立記憶體狀態。

這提供了永續性的保證。 即使伺服器突然崩潰,然後重新啟動,資料也不會丟失。 但是,在伺服器恢復之前,客戶端將無法獲取或儲存任何資料。 因此,如果伺服器發生故障,我們會缺乏可用性。

一種顯而易見的解決方案是將資料儲存在多個伺服器上。 因此,我們可以在多個伺服器上覆制預寫日誌。

當涉及多個伺服器時,還有更多的故障情況需要考慮。

網路延遲

在TCP / IP協議棧中,透過網路傳輸訊息時所引起的延遲沒有上限。 它會根據網路上的負載而變化。 例如,一個1 Gbps的網路連線可能會被定時觸發的大資料作業淹沒,從而填滿網路緩衝區,並且可能導致某些訊息在不確定的延遲後才到達伺服器。

在典型的資料中心中,伺服器打包在機架中,並且透過機架式交換機連線多個機架。 可能會有一棵交換機樹將資料中心的一部分連線到另一部分。 在某些情況下,一組伺服器可以相互通訊,但與另一組伺服器的連線是斷開的。 這種情況稱為網路分割槽。 伺服器透過網路進行通訊的基本問題之一是,何時能知道特定伺服器發生了故障。

這裡有兩個問題要解決。

一臺特定的伺服器不能透過無限期的等待,去知道另一臺伺服器已經宕機。

不應有兩套伺服器,每套伺服器都認為另一套伺服器發生了故障,因此繼續為不同的客戶端提供服務。 這稱為腦裂(split brain)。

為了解決第一個問題,每臺伺服器都會定期向其他伺服器傳送心跳訊息。如果沒有收到心跳,則將對應的伺服器視為已崩潰。心跳間隔足夠短,能夠確保不需要花費很長時間就能檢測伺服器故障。在最壞的情況下,伺服器可能在正常執行,但是被叢集整體認為已經宕機,並繼續執行。心跳這種方式可以確保提供給客戶端的服務不會中斷。

第二個問題是腦裂。在腦裂的情況下,如果兩組伺服器獨立接受更新,則不同的客戶端可以獲取和設定不同的資料,當腦裂的問題被解決後,是不可能自動解決衝突的。

為了解決腦裂的問題,我們必須確保彼此斷開連線的兩組伺服器不能獨立地正常執行下去。為確保這一點,伺服器執行動作時,只有大多數伺服器可以確認這個動作,該才將動作視為成功。如果伺服器無法選出來一個”大多數“,則它們將無法提供服務,並且某些客戶端組可能無法接收該服務。但是叢集中的伺服器將始終處於一致狀態。佔多數的伺服器數量稱為Quorum。如何確定Quorum?這是根據叢集可以容忍的故障數決定的。比如,我們有五個節點的群集,則需要三個quorum。通常,如果我們要容忍f個故障,則需要2f + 1的叢集大小。

Quorum確保我們有足夠的資料副本以承受某些伺服器故障。但是,僅向客戶提供強大的一致性保證是不夠的。假設客戶端在quorum上啟動了寫操作,但是該寫操作僅在一臺伺服器上成功。Quorum中的其他伺服器仍具有舊值。當客戶端從Quorum讀取值時,如果具有最新值的伺服器可用,則它可能會獲得最新值。但是,如果僅當客戶端開始讀取值時,具有最新值的伺服器不可用,它就可以獲得舊值。為了避免這種情況,需要有人跟蹤判斷quorum是否同意特定的操作,並且僅將值傳送給保證在所有伺服器上都可用的客戶端。在這種情況下使用主從模式(master&slave)。其中一臺伺服器當選為master,其他伺服器充當slave。Master控制並協調對slave的複製。Master需要確定哪些更改應對客戶可見。高水位標記(High-water mark)用於跟蹤預寫日誌中已成功複製到足夠的slave中的條目。客戶端可以看到所有高水位之前的條目。Master還將高水位標記傳送給slave。因此,如果Master失敗並且slave之一成為新master,那麼客戶看到的內容就不會出現不一致之處。

程序暫停

但這還不是全部,即使有了Quorums和Master and Slave,仍然需要解決一個棘手的問題。 Master程序隨時可能暫停。 程序暫停的原因很多。 對於支援垃圾回收的語言來說,可能會有很長的垃圾回收暫停。 具有較長垃圾收集暫停時間的Master可能會與slave斷開連線,並在暫停結束後繼續向slave傳送訊息。 同時,如果slave沒有收到master的任何心跳訊號,他們可能選擇了新的master並接受了客戶端的更新。 如果舊master的請求按原樣處理,則它們可能會覆蓋某些更新。 因此,我們需要一種機制來檢測過時master的請求。 Generation Clock用於標記和檢測來自老master的請求。 世代(generation)是單調增加的數字。

時鐘不同步及順序訊息

從較新的訊息中檢測較舊的master訊息的問題是如何保證訊息順序。看起來我們可以使用系統時間來給一組訊息排序,但實際上,是不行的。主要原因是我們無法保證跨伺服器的系統時鐘是同步的。計算機中的一天中的時鐘由石英晶體管理,並根據晶體的振盪來測量時間。

這種機制容易出錯,因為晶體可以更快或更慢地振盪,因此不同的伺服器可能具有截然不同的時間。一組伺服器上的時鐘透過稱為NTP的服務進行同步。該服務會定期檢查一組全域性時間伺服器,並相應地調整計算機時鐘。

由於這是透過網路上的通訊發生的,並且網路延遲可能會如以上各節中所述發生變化,所以由於網路問題,時鐘同步可能會延遲。這可能導致伺服器時鐘彼此之間漂移,並且在NTP同步發生後甚至會倒退。由於計算機時鐘存在這些問題,因此通常不將一天中的時間用於順序事件。取而代之的是使用一種稱為Lamport’s timestamp的簡單技術。Generation Clock就是一個例子。

整合-一個分散式系統的例子

我們可以看到,從頭開始理解這些模式是如何幫助我們建立一個完整系統的。 我們將以一致性為例。 分散式一致性是分散式系統實現的一種特例,它提供了最強的一致性保證。 比如流行的企業架構中常見的Zookeeper,etcd和Consul。 他們實現了zab和Raft等一致性演算法,以提供複製和強大的一致性。 還有其他流行的演算法可以實現一致性,Google的Chubby中用於鎖定服務的Paxos,考慮stamp replication and virtual-synchrony。 用非常簡單的術語來說,一致性是指在一組伺服器中,在儲存的資料,儲存的順序以及何時使該資料對客戶端可見,這些方面達成一致。

實現一致性的模式列表

一致性的實現中,使用 state machine replication來實現容錯。在state machine replication中,儲存服務,比如鍵值儲存,在所有伺服器上覆制,而且使用者的輸入也會在每個伺服器上以相同順序執行。用於實現此目的的關鍵實現技術是在所有伺服器上覆制Write-Ahead log以具有“ Replicated Wal”。

我們可以將這些模式放在一起以實現Replicated Wal,如下所示。

為了提供永續性保證,使用Write-Ahead log。使用Segmented Log將預寫日誌分為多個段。這有助於Low-Water Mark進行日誌清理。透過在多個伺服器上覆制預寫日誌來提供容錯能力。伺服器之間的複製透過使用主從模式進行管理。更新High-Water Mark時,使用Quorum,以確定哪些值對客戶可見。透過使用Single Update Quest,使所有請求均按嚴格順序處理。使用Single Socket Channel, 將master的請求傳送給slave時,事件將得到維護。為了最佳化Single Socket Channel上的吞吐量和延遲,使用了Request Pipeline。Slavet透過HeartBeat確定Master的可用性。如果Master由於網路分割槽而暫時從叢集斷開連線,則可以使用“Generation Clock”來檢測它。

[翻譯]分散式系統的模式-綜述

透過這種方式,理解這些問題,和它們的通用解決方式,幫助我們理解構建複雜系統的方式。

下一步

分散式系統是一個巨大的話題。 這裡涵蓋的模式集只是一小部分,涵蓋了不同的類別,以展示模式方法如何幫助理解和設計分散式系統。 我將繼續向這個合集新增下邊這些問題。

Group Membership and Failure Detection

Partitioning

Replication and Consistency

Storage

Processing

原文地址:

http://

lichuanyang。top/posts/3

914/