GitHub: https://github.com/storagezhang
Emai: debugzhang@163.com
概述
任何計算機系統都需要完成兩個基本任務:
- 儲存
- 計算
分散式程式設計是一門藝術,可以使用多臺計算機解決在單臺計算機上解決的問題。通常是因為該問題不再適用於單臺計算機。
並沒有任務明確要求必須使用分散式系統。如果有無限的資金和無限的研發時間,我們就不需要分散式系統。所有的計算和儲存都可以在一個魔盒——一個你付錢給其他人為你設計的單一的、非常快的、非常可靠的系統。
但是,很少有人擁有無限的資源。因此,他們必須在默寫實際成本收益曲線上找到正確的位置。在較小的規模上,升級硬體上可行的策略。但是,隨著問題規模的增加,你將無法實現可以在單個節點上解決問題的硬體升級,或者變得成本過高。到那時,歡迎進入分散式系統的世界。
當前的現實是,最好的解決方案是中端商用硬體——只要可以通過容錯軟體來降低維護成本。
計算主要受益於高階硬體,在某種程度上,它們可以通過內部記憶體訪問代替緩慢的網路訪問。高階硬體的效能優勢受限於需要節點之間進行大量通訊的任務。
正如上方來自 Barroso, Clidaras & Hölzle 的圖所示,假設叢集中所有節點的記憶體訪問模式相同,高階硬體和商用硬體之間的效能差距會隨著叢集增大而減小。
理想情況下,新增新硬體後系統所增加的效能和容量都是線性的。但是這是不可能的,因為由於計算機之間是獨立的,會產生一些額外開銷,比如資料需要被複制,計算任務需要協調等等。這就是為什麼值得研究分散式演算法——它們為特定問題提供了有效的解決方案、可行的可能行、正確實施的最低成本和不可能的解決方案。
本文的重點是在普通但具有商業意義的環境中的分散式程式設計和系統:資料中心。例如,我不會討論因非典型網路配置而引起的特殊問題,或者因為共享記憶體設定引起的特殊問題。此外,我們的重點在於探索系統設計領域,而不是針對任何特定設計的優化——後者是更專業的文章的主題。
我們想要實現的目標:可伸縮性和其他優點
我的看法是,一切始於處理規模的問題。
大多數事物在小規模上都是微不足道的——一旦超過一定大小、體積或者其他受物理限制的事物,相同的問題就會變得更加棘手。舉起一塊巧克力很容易,舉起山峰很難。計算房間中有多少人很容易,計算一個國家中有多少人則很困難。
因此,一切始於規模—可擴充套件性。非正式地講,在可伸縮系統中,從小到大,事情不會逐漸變得糟糕。這是另一個定義:
可伸縮性是系統、網路或過程處理不斷增長的工作量的能力,或者是未來適應這種增長而進行擴充套件的能力。
增長是什麼?好吧,你幾乎可以用任何術語(人數、用電量等)衡量增長。但是有三件事值得關注:
- 大小可伸縮性:新增更多的節點將使系統效能線性增長;擴大資料集不應該增加延遲。
- 地理可伸縮性:應該可以使用多個資料中心來減少使用者查詢所需的響應時間,同時以某種合理的方式處理跨資料中心的延遲。
- 管理可伸縮性:新增更多的節點不應增加系統的管理成本(例如,管理員與機器的比例)。
當然,在實際系統中,增長會同時在多個不同的軸上發生;每個指標僅反映增長的某些方面。
可擴充套件的系統上隨著規模的增長而不斷滿足其使用者需求的系統。有兩個特別相關的方面——效能和可用性,可以通過多種方式進行衡量。
效能(和延遲)
效能的特徵是與所使用的時間和資源相比,計算機系統完成的有用任務量。
根據上下文,這可能涉及實現以下一項或多項:
- 給定任務的響應時間(等待時間)短
- 高吞吐量(處理任務率)
- 較低的計算資源使用量
優化這些結果的任何一個都需要權衡。例如,系統可以通過處理更大批量的任務來實現更高的吞吐量,從而減少操作開銷。折衷方案是因為批量處理,導致單個任務的響應時間更長。
我發現低延遲(縮短響應時間)是效能中最有趣的方面,因為它與物理(而非財務)限制緊密相關。與效能的其他方面相比,使用財務資源解決延遲問題很困難。
讓我們假設一下,我們的分散式系統僅執行一項高階任務:給定查詢,獲得系統中所有的資料並計算單個結果。換句話說,將分散式系統視為一個能夠在其當前內容上執行單個確定性計算(函式)的資料儲存:
\(結果=查詢(系統中的所有資料)\)
然後,對於延遲而言,重要的不是舊資料的數量,而是新資料在系統中“生效”的速度。例如,可以根據寫入對讀者可見所需的時間來衡量延遲。
基於此定義的另一個關鍵點是,如果什麼也沒有發生,那麼就沒有“潛伏期”。資料不變的系統不會(也不應該)存在延遲問題。
在分散式系統中,又一個無法克服的最小延遲:光速限制了資訊能夠以多快的速度傳播,和硬體元件的每次操作所產生的最小延遲成本(例如 RAM 和硬碟驅動器,以及 CPU)。
最小延遲對你的查詢有多大影響,取決於這些查詢的性質,以及資訊傳播的物理距離。
可用性(和容錯)
可伸縮系統的第二個方面是可用性。
可用性是系統處於執行狀態的時間比例。如果使用者無法訪問系統,則稱該系統不可用。
分散式系統使我們能夠獲得理想的特性,而這些特性很難在單個系統上獲得。例如,一臺機器不能容忍任何故障,因為它要麼失敗要麼不失敗。
分散式系統可能會使用大量不可靠的元件,並在它們之上構建可靠的系統。
沒有冗餘的系統只能作為其基礎元件使用。使用冗餘構建的系統可以容忍部分故障,因此可以提供更好的可用性。值得注意的是,“冗餘”可能意味著不同的含義,具體取決於你所關注的內容——元件、伺服器、資料中心等等。
按照慣例:
從技術角度來看,可用性主要是關於容錯的。因為發生故障的概率會隨著元件數量的增加而提高,所以系統應該能夠進行補償,以使系統不會隨著元件數量的增加而變得不那麼可靠。
例如:
可用性 | 每年允許多少停機時間 |
---|---|
一個九:90% | 1 個多月 |
兩個九:99% | 少於 4 天 |
三個九:99.9% | 少於 9 小時 |
四個九:99.99% | 少於 1 小時 |
五個九:99.999% | 約 5 分鐘 |
六個九:99.9999% | 約 31 秒 |
從某種意義上說,可用性是一個比正常執行時間更廣泛的概念,因為服務的可用性還可能受到例如網路中斷或擁有該服務的公司停業的影響(這與容錯無關,但仍會影響系統的可用性)。但是,在不瞭解系統的每個特定方面的情況下,我們能做的最好的事情就是設計容錯能力。
容錯是什麼意思?
容錯是指故障發生後系統以明確定義的方式執行的能力。
容錯可以總結為:確定你預期的故障,然後設計可以容忍該故障的系統或演算法。你不能容忍從未考慮過的故障。
是什麼阻止我們取得成功?
分散式系統受兩個物理因素的限制:
- 節點個數(隨所需的儲存和計算能力而增加)
- 節點之間的距離(資訊最多以光速傳播)
在這些限制內工作:
- 獨立節點數量的增加會增加系統發生故障的可能性(降低可用性並增加管理成本)
- 獨立節點數量的增加可能會增加節點間通訊的需求(隨著規模的增加而降低效能)
- 地理距離的增加會增加遠端節點之間通訊的最小延遲(降低某些操作的效能)
除了這些趨勢(物理限制的結果)之外就是系統設計選擇的世界了。
系統的外部保證定義了效能和可用性。在較高的層次上,你可以將保證視為系統的 SLA(服務級別協議):
- 如果寫入資料,可以在多久之後訪問它?
- 資料寫入後,有什麼能保證耐用性?
- 如果要求系統執行計算,它將在多久後返回結果?
- 當元件發生故障或無法執行,這會對系統產生什麼影響?
還有另一個沒有明確提及但暗含的標準:可理解性。作出的保證有多可理解?當然,沒有什麼針對可理解性的簡單指標。
我很想將“可理解性”置於物理限制之下。畢竟,對於人們來說,硬體上的侷限在於,我們很難理解牽動比手指更多的東西。這就是錯誤和異常之間的區別——錯誤是不正確的行為,而異常是意外的行為。如果你非常聰明,你就可以預測會出現的異常情況。
抽象和模型
這是抽象和模型起作用的地方。抽象通過刪除與解決問題無關的實際方面,使事情變得更易於管理。模型以精確的方式描述了分散式系統的關鍵特性。在下一章,我將討論許多模型,例如:
- 系統模型(非同步/同步)
- 失敗模型(崩潰失敗,分割槽,拜占庭)
- 一致性模型(強,最終)
良好的抽象使得系統更容易理解,同時捕獲與特定目的相關的因素。
在存在許多節點的現實與我們對“像單個系統一樣工作”的系統的渴望之間存在著一種張力。通常,最熟悉的模型(例如,在分散式系統上實現共享記憶體抽象)過於昂貴。
做出較弱保證的系統具有更大的行動自由度,因此可能具有更好的效能——但也可能更難推理結果。人們更擅長於對像單個系統那樣工作的系統進行推理,而不是對節點的集合進行推理。
人們通常可以通過公開有關係統內部的更多細節來獲得效能。例如,在列式儲存中,使用者可以(在某種程度上)推斷鍵值對在系統中的位置,從而做出影響典型查詢的效能的決策。隱藏這些細節的系統更易於理解(因為它們的行為更像是一個獨立的單元,從而只有更少的細節需要被考慮),而暴露更多真實細節的系統可能具有更好的效能(因為它們與現實的關係更加緊密)。
幾種型別的故障使編寫像單個系統一樣工作的分散式系統變得困難。網路延遲和網路分割槽(例如某些節點之間的整體網路故障)意味著系統有時需要做出艱難的選擇,以便更好地保持可用性,但失去一些無法被強制執行的關鍵保證,或者當這些型別的故障發生時,以安全性為主拒絕客戶端的請求。
我將在下一章中討論的 CAP 定理涵蓋了其中的一些對立關係。最後,理想的系統既可以滿足程式設計師的需求(清晰的語義),又可以滿足業務的需求(可用性/一致性/延遲)。
設計技巧:分割槽和複製
在多個節點之間分配資料集的方式非常重要。為了進行任何計算,我們需要定位資料,然後對其進行操作。
有兩種基本技術可以應用於資料集:
- 可以將其拆分到多個節點,以進行更多並行處理(分割槽)。
- 可以將其複製或快取在不同的節點上,以縮短客戶端與伺服器之間的距離,並提高容錯能力(複製)。
分而治之——分割槽和複製。
下圖說明了兩者之間的區別:
- 分割槽資料(A 和 B)被分為獨立的集合。
- 複製資料(C)被複制到多個位置。
這是解決分散式計算所面臨的問題的兩種方法。當然,訣竅在於為你的具體實現選擇正確的技術。有許多實現複製和分割槽的演算法,每種演算法都有不同的限制和優點,需要針對你的設計目標進行評估。
分割槽
分割槽上將資料集分成較小的不同獨立集。由於每個分割槽都是資料的一個子集,所以可用於減少資料集增長帶來的影響。
- 分割槽通過限制要檢查的資料量並在同一分割槽中定位相關資料來提高效能。
- 分割槽通過允許分割槽發生獨立故障,增加犧牲可用性之前所能忍受的發生故障的節點數量,來提高可用性。
分割槽技術也是非常面向應用的,因此在不瞭解具體細節的情況下很難說太多。這就是為什麼大多數文章(包括本文)都將重點放在複製上。
分割槽的依據更多是你所推斷的主要訪問模式,並解決由於獨立分割槽而帶來的限制(例如,跨區的低效訪問,不同的增長率等)。
複製
複製上在多臺機器上儲存相同的資料。這使得更多的伺服器可以參與到計算中。
讓我不準確地引用 Homer J. Simpson:
To replication! The cause of, and solution to all of life's problems.
複製是我們減少延遲到主要方法。
- 複製通過適用資料副本的額外計算能力和頻寬來提高效能。
- 複製通過建立資料副本,增加犧牲可用性之前所能忍受的發生故障的節點數量,來提高可用性。
複製提供了額外的頻寬,並在在需要的地方進行快取。它根據某種一致性模型,以某種方式保持一致性。
複製使我們能夠實現可伸縮性、效能和容錯。
- 害怕失去可用性或者降低效能?複製資料以避免出現瓶頸或單點故障。
- 計算速度慢?將資料複製到多臺機器上進行計算。
- I/O 速度慢?將資料複製到本地快取以減少延遲,或者複製到多臺機器上以提高吞吐量。
複製也是許多問題的根源,因為現在有獨立的資料副本,而這些副本必須在多臺計算機上保持同步——這意味著確保複製要遵循一致性模型。
一致性模型的選擇至關重要:良好的一致性模型為程式設計師提供了清晰的語義(換句話說,它保證的特性易於被推斷),並滿足了諸如高可用性或強一致性之類的業務/設計目標。
只有一個一致性模型(強一致性)讓你程式設計時如同沒有複製資料。其他一致性模型向程式設計師公開了複製的某些內部資訊。然而,弱一致性模型能提供較低的延遲和較高的可用性——而且不一定更難理解,只是有所不同。
進一步閱讀
- The Datacenter as a Computer - An Introduction to the Design of Warehouse-Scale Machines - Barroso & Hölzle, 2008
- Fallacies of Distributed Computing
- Notes on Distributed Systems for Young Bloods - Hodges, 2013