分布式系統:問題及其可復用的解決方案
作者: Unmesh Joshi
譯者: java達人
在過去的幾個月中,我一直在ThoughtWorks上進行有關分布式系統的研討會。舉辦研討會時面臨的主要挑戰之一是如何將分布式系統的理論映射到諸如Kafka或Cassandra之類的開源代碼庫,同時保持研討的通用性足以涵蓋廣泛的解決方案。模式的概念提供了一個不錯的方式。
模式結構本質上使我們能夠專注于特定問題,從而很清楚地說明了為什么需要特定解決方案。然后,解決方案描述中給出一個代碼結構,該結構足夠具體以顯示實際的解決方案,但又足夠通用以涵蓋各種變體。模式技術還允許我們將各種模式鏈接在一起以構建一個完整的系統。這為討論分布式系統實現提供了很好的術語。
接下來是在主流開源分布式系統中觀察到的第一組模式。我希望這些模式集對所有開發人員都有用。
分布式系統 - 實現的角度來看
當今的企業體系結構充滿了自然分布的平臺和框架。如果我們看到今天在典型的企業體系結構中使用的框架和平臺的示例列表,它將類似于以下內容:

所有這些本質上都是“分布式的”。分布式系統意味著什么?有兩個方面:
?它們在多臺服務器上運行。集群中的服務器數量可以從三臺到幾千臺不等。
? 它們管理數據。因此,這些天生就是“有狀態”的系統。
當多個服務器參與數據存儲時,有幾種途徑可能會導致問題。上述所有系統都需要解決這些問題。這些系統的實現對這些問題有一些可復用的解決方案。理解這些解決方案的一般形式,有助于理解這些系統的廣泛實現,并且在需要構建新系統時也可以作為很好的指導。下面進入模式章節。
問題及其可復用的解決方案
當數據存儲在多個服務器上時,可能會出現幾個問題。
進程崩潰
進程隨時會由于硬件故障或軟件故障崩潰。進程崩潰的方式有很多種。
?系統管理員可以將其下線進行日常維護。?由于磁盤已滿并且該異常無法正確被處理,因此在執行某些文件IO時被殺死。?在云環境中,這可能會更加棘手,因為一些不相關的事件可能會使服務器宕機。
最重要的是,如果進程負責存儲數據,則必須對存儲在服務器上的數據提供持久性保證。即使進程突然崩潰,它也應保留所有已通知用戶已成功存儲的數據。根據訪問模式,不同的存儲引擎具有不同的存儲結構,從簡單的哈希映射到復雜的圖存儲。由于將數據刷新到磁盤是最耗時的操作之一,因此無法將每次對存儲的插入或更新都刷新到磁盤。因此,大多數數據庫都具有內存存儲結構,這些存儲結構僅定期刷新到磁盤。如果進程突然崩潰,則可能會丟失所有數據。
一種稱為Write-Ahead Log的技術用于解決這種情況。服務器將每個狀態更改作為命令存儲在硬盤上的append-only文件中。append文件通常是非常快速的操作,因此可以在不影響性能的情況下進行。通過順序附加單個日志的方式存儲每一次更新。在服務器啟動時,可以重放日志以再次建立內存狀態。
這提供了持久性保證。即使服務器突然崩潰,然后重新啟動,數據也不會丟失。但是,在恢復服務器之前,客戶端將無法獲取或存儲任何數據。因此,如果服務器發生故障,缺乏可用性。
一種顯而易見的解決方案是將數據存儲在多個服務器上。因此,我們可以在多個服務器上復制預寫日志。
當涉及多個服務器時,還有更多的故障情況需要考慮。
網絡延遲
在TCP / IP協議棧中,在跨網絡傳輸消息時所引起的延遲沒有上限。它可以根據網絡上的負載而變化。例如,一條1 Gbps的網絡連接可能會被觸發的大數據作業吞沒,從而填滿網絡緩沖區,并可能導致某些消息到達服務器的超長延遲。
在典型的數據中心中,服務器一起放在機架中,并且有多個機架通過機架交換機連接。可能會有一個交換機樹將數據中心的一部分連接到另一部分。在某些情況下,一組服務器可以相互通信,但與另一組服務器斷開連接。這種情況稱為網絡分區。服務器通過網絡進行通信的基本問題之一是何時知道特定服務器發生故障。
這里有兩個問題要解決。
?某臺的服務器不能無限期地等待其他服務器是否崩潰。?不應有兩組服務器,每組服務器都認為另一組服務器發生了故障,因此繼續為不同組的客戶端提供服務。這稱為腦裂。
為了解決第一個問題,每臺服務器都會定期向其他服務器發送HeartBeat消息。如果心跳丟失,則將發送心跳的服務器視為已崩潰。心跳間隔足夠小,以確保不需要花費很多時間來檢測服務器故障。如我們將下面看到的,在最壞的情況下,服務器可能已啟動并正在運行,但是考慮到服務器出現故障,集群作為一個整體可以繼續運行。這樣可以確保提供給客戶端的服務不會中斷。
第二個問題是腦裂。腦裂,如果兩組服務器獨立接受更新請求,則不同的客戶端可以獲取和設置不同的數據,一旦腦裂得到解決,就不可能自動解決數據沖突。
為了解決腦裂問題,我們必須確保彼此斷開連接的兩組服務器不能獨立運展。為確保這一點,該服務器執行的每個動作只有獲得大多數服務器的確認才被認為是成功的。如果服務器無法獲得多數確認,則它們將無法提供所需的服務,并且某些客戶端組可能無法接收該服務的響應,但是集群中的服務器將始終處于一致狀態。占多數的服務器數量稱為Quorum。如何確定Quorum?這是根據群集可以容忍的故障數決定的。因此,如果我們有五個節點的集群,則需要三個仲裁。通常,如果我們要容忍f個故障,則需要2f + 1的集群大小。
Quorum確保我們有足夠的數據副本以承受某些服務器故障。但是,僅向客戶提供強大的一致性保證是不夠的。假設客戶端在quorum上開始了寫操作,但是該寫操作僅在一臺服務器上成功。quorum的其他服務器仍是舊值。當客戶端從quorum 取值時,如果具有最新值的服務器可用,則它可能會獲得最新值。但是,當客戶端開始讀取值時,具有最新值的服務器不可用,它就會獲取舊值。為了避免這種情況,需有設備跟蹤quorum是否同意特定的操作,并且僅將值發送給保證在所有服務器上都可用的客戶端。在這種情況下使用 Leader and Followers模式。其中一臺服務器當選領導者,其他服務器充當追隨者。領導者控制并協調對跟隨者的復制。領導者現在需要確定哪些更改應該對客戶可見。High-Water Mark用于跟蹤已知已成功復制到追隨者Quorum的預寫日志中的條目。客戶端可以看到所有High-Water之前的條目。領導者還將High-Water Mark傳播給跟隨者。因此,如果領導者失敗并且其中一個跟隨者成為新領導者,那么客戶看到的內容就不會出現不一致之處。
進程暫停
但這還不是全部,即使有了Quorums和Leader and Followers,仍然需要解決一個棘手的問題。領導者進程暫停。進程暫停的原因有很多。具有較長垃圾收集暫停時間的領導者會與追隨者者斷開連接,并在恢復后繼續向追隨者發送消息。同時,由于追隨者沒有收到領導者的任何心跳,因此他們可能選擇了新的領導者并接受了客戶的更新。如果舊領導者的請求按原邏輯處理,它們可能會覆蓋某些更新。因此,我們需要一種機制來檢測過時領導者的請求。Generation Clock 用于標記和檢測來自過期領導者的請求。Generation是單調增加的數字。
不同步的時鐘和事件順序
從較新的消息中檢測較舊的領導者消息的問題是保持消息順序的問題。我們似乎可以使用系統時間戳來排序一組消息,但事實上不能。我們不能使用系統時鐘的主要原因是不能保證跨服務器的系統時鐘是同步的。計算機中的一天中的時鐘由石英晶體管理,并根據晶體的振蕩來測量時間。
這種機制易于出錯,因為晶體可以更快或更慢地振蕩,因此不同的服務器可能具有截然不同的時間。一組服務器上的時鐘由稱為NTP的服務進行同步。該服務會定期檢查一組全局時間服務器,并相應地調整計算機時鐘。
因為這是通過網絡上的通信發生的,并且網絡延遲可能會如上一節中所述發生變化,所以時鐘同步可能會由于網絡問題而延遲。這可能會導致服務器時鐘彼此偏移,并且在NTP同步發生后甚至會向后移。由于計算機時鐘存在這些問題,因此通常不將一天中的時間用于排序事件。取而代之的是使用一種稱為Lamport時間戳的簡單技術。Generation Clock就是一個例子。
這些問題可能會發生在最復雜的設置中。考慮Amazon、谷歌和Github的例子。
一次Github宕機實質上導致了東海岸和西海岸數據中心之間的連接中斷。這會導致數據無法跨數據中心復制,使兩臺mysql服務器的數據不一致。
https://github.blog/2018-10-30-oct21-post-incident-analysis/
一次AWS宕機是由人為錯誤造成的,其中自動化腳本錯誤地傳遞了一個參數,關閉了大量服務器。https://aws.amazon.com/message/41926/
一次谷歌中斷是由一些錯誤配置引起的,對網絡容量造成了重大影響,從而導致網絡擁塞和服務中斷。
https://status.cloud.google.com/incident/cloud-networking/19009
匯總-分布式系統示例
我們可以發現理解這些模式如何幫助我們從頭開始建立一個完整的系統。我們將以共識實現為例。分布式共識是分布式系統實現的特例,它提供了最強的一致性保證。在流行的企業系統中常見的示例有Zookeeper,etcd和Consul。他們實現了zab和Raft等共識算法,以提供復制和強一致性。還有其他流行的算法可以實現共識,Paxos用于Google的Chubby鎖服務,查stamp replication和virtual-synchrony。用非常簡單的術語來說,“共識”是指一組服務器,它們在存儲的數據,存儲的順序以及何時使該數據對客戶端可見方面達成一致。
實現共識的模式序列
共識實現使用狀態機復制來實現容錯。在狀態機復制中,存儲服務(如鍵值存儲)在所有服務器上復制,并且用戶的輸入在每個服務器上以相同順序執行。實現此目的的關鍵技術是在所有服務器上復制預寫日志以獲得“ Replicated Wal”。
我們可以將這些模式放在一起以實現Replicated Wal,如下所示。

為了提供持久性保證,請使用Write-Ahead Log。使用Segmented Log將Write-Ahead Log分為多個段。這有助于Low-Water Mark 處理日志清理。通過在多個服務器上復制預寫日志來提供容錯能力。服務器之間的復制是通過使用“領導者”和“追隨者”來管理的。Quorum法定數用于更新High-Water Mark,以確定客戶端可以看到哪些值。通過使用Singular Update Queue,所有請求均按嚴格順序處理。使用Single Socket Channel將領導者的請求發送給追隨者時,順序將得到維護。為了優化單個套接字通道上的吞吐量和延遲性,使用Request Pipeline。追隨者通過從領導者處獲得HeartBeat獲知其可用性。如果領導者由于網絡分區而暫時從集群斷開連接,則可以使用Generation Clock進行檢測。
通過這種方式,理解問題及其一般形式的可復用解決方案,有助于理解完整系統的構建模塊
下一步
分布式系統是一個廣泛的話題。這里討論的模式集只是一小部分,它涵蓋了不同類別,以展示模式方法如何幫助理解和設計分布式系統。我將繼續在這個集合中添加內容,任何分布式系統中都廣泛地包含了以下問題類別。
?集群成員和故障檢測?分區?復制和一致性?存儲?處理
請輸入評論內容...
請輸入評論/評論長度6~500個字
最新活動更多
- 1 AI狂歡遇上油價破百,全球股市還能漲多久? | 產聯看全球
- 2 OpenAI深夜王炸!ChatGPT Images 2.0實測:中文穩、細節炸,設計師慌了
- 3 6000億美元估值錨定:字節跳動的“去單一化”突圍與估值重構
- 4 Tesla AI5芯片最新進展總結
- 5 連夜測了一波DeepSeek-V4,我發現它可能只剩“審美”這個短板了
- 6 熱點丨AI“瑜亮之爭”:既生OpenClaw,何生Hermes?
- 7 AI界的殺豬盤:9秒刪庫跑路,全員被封號,還繼續扣錢!
- 8 2026,人形機器人只贏了面子
- 9 DeepSeek降價90%:價格屠夫不是身份,是戰略
- 10 AI Infra產業鏈卡在哪里了?


分享













