原创 沈剑 架构师之路
工程架構,Google乃先驅。其GFS(Google File System)有哪些優秀的架構設計點,值得我們學習借鏡呢?
GFS是什麼?
Google早期研發的分散式檔案系統。
GFS的設計目標是什麼?
主要有四個目標:
1. 高可用(availability);
2. 高可靠(reliability);
3. 高績效(performance);
4. 可擴展(scalability);
GFS對外提供什麼介面?
文件創建,刪除,打開,關閉,讀,寫,快照。畫外音:除了快照,介面和單機檔案系統差不多。
快照其實是快速文件目錄樹的拷貝,並不是所有文件的快照。
GFS能夠成為分散式架構的經典案例,原因之一,就是介面簡單,但反映的架構理念不簡單。
GFS的系統架構如何?系統裡只有檔案客戶端,主伺服器,儲存伺服器三個角色。

如上圖:1. 客戶端(GFS client),是以庫的形式提供的,提供的就是對外要用的接口;2. 主伺服器(GFS master),是單點,存儲文件信息,目錄信息,文件服務器信息,那個文件存在哪些文件服務器上等元數據;3.3.3點存儲文件信息,
為什麼要設計單點master?單點master意味著有一個節點可以避免分散式鎖,可以擁有全域視野,能夠統一調度與監控,系統整體複雜度降低很多。畫外音:鎖可以降級成本地鎖,分散式調度可以降級為單點調度。
更具體的:1. master擁有所有文件目錄結構,要操作某個文件,必須獲得相應的鎖;畫外音:一般情況下,不會對同一個網頁進行並發寫操作,應用場景決定鎖衝突其實不大;2. master擁有全域視野,能夠避免死鎖;3. master知道chunk-server的信息,能夠很容易監控的做負載; master知道所有文件的副本分佈訊息,能夠很容易的做文件大小的負載平衡;畫外音:負載平衡分為請求量的均衡,文件儲存的容量均衡。
GFS的高可用是怎麼保證的?高可用又分為服務高可用,文件儲存高可用,均透過「冗餘+自動故障轉移」的思路來實現。 1. master高可用:冗餘了一台影子master,平時不工作,master掛了工作,以保證master的高可用;畫外音:master資源利用率只有50%。 2. chunk-server高可用:本身是集群,冗餘服務;畫外音:當有chunk-server掛掉,master能偵測到,並且知道哪些檔案儲存在chunk-server上,就可以啟動新的實例,並複製相關檔案。 3. 文件存放高可用:每一份文件會存三份,冗餘文件;
GFS的高效能是怎麼保證的?多個chunk-server可以透過線性擴充提升處理能力和儲存空間,GFS的潛在瓶頸是單點master,所以GFS要想達到超高效能,主要架構最佳化想法在於,「提升master效能,減少與master互動」。
- 只儲存元數據,不儲存檔案數據,不讓磁碟容量成為master瓶頸;
- 元資料會儲存在磁碟和記憶體裡,不讓磁碟IO成為master瓶頸;
- 元資料大小記憶體完全能裝得下,不讓記憶體容量成為master瓶頸;所有資料流,資料緩存,可走遠,資料快取,成為master瓶頸;
- 元資料可以快取在客戶端,每次從客戶端本地快取存取元數據,只有元資料不準確的時候,才會存取master,不讓CPU成為成為master瓶頸;
當然,chunk-server雖然有多個,也會透過一些手段提升chunk-server的效能,例如:1. 檔案區塊使用64M,避免太多碎片降低效能;2. 使用追加寫,而不是隨機寫,提升效能;3. 使用TCP長連接,提升效能;
GFS如何保證系統可靠度?保證元資料與文件資料的可靠性,GFS使用了許多非常經典的手段。 1. 元資料的變更,會先寫日誌,以確保不會遺失;畫外音:日誌也會冗餘,具備高可用。 2. master會輪詢探測chunk-server的存活性,保證有chunk-server失效時,chunk-server的狀態是準確的;畫外音:檔案會存多份,短時間內chunk-server掛掉是不影響的。 3. 元資料的修改是原子的,由master控制,master必須保證元資料修改的順序性;4. 檔案的正確性,透過checksum保證;5. 監控,快速發現問題;
讀取操作的核心流程?文件讀取是最高頻的操作。 1. client讀本機緩存,看檔案在哪些chunk-server上;2. 如果client本機快取miss,詢問master文件所在位置,並更新本機快取;3. 從一個chunk-server裡讀文件,如果讀取到,就回傳;
寫入操作的核心流程?寫入操作會複雜很多。
為了確保資料高可用,資料必須在多個chunk-server上寫入多個副本,首先要解決的問題是,如何保證多個chunk-server上的資料是一致的呢?
想想一個MySQL叢集的多個MySQL實例,是如何保證多個實例的資料一致性的。 bingo!確定一個主實例,串行化所有寫入操作,然後在其他實例重播相同的操作序列,以確保多個實例資料的一致性。
GFS也採用了類似的策略,一個檔案冗餘3份,存在3個chunk-server上,如下圖步驟1-7:

- client存取master,要發起文件寫入操作;畫外音:假設client本地快取未生效;master回傳資料儲存在ABC三個實例上,且告之其中一個實例是主chunk-server;
- client將資料流傳遞給所有chunk-server;
- client將控制流產地給主chunk-unk-1
- 鍵結其他chunk-server則依照相同的控制流程對資料進行操作,並將結果告訴主chunk-server;
- 主chunk-server收到其他所有chunk-server的成果執行結果後,將結果傳回client;
- 畫外音:MySQL的主函式庫是寫瓶頸,GFS不會出現這樣的問題,每個檔案的主chunk-server是不同的請求,所以每個實例的寫字的寫法。
這裡要說明的是,GFS對於寫入操作,執行的是最保守的策略,必須所有chunk寫成功,才會返回client寫成功(寫吞吐會降低);這樣的好處是,讀取操作只要一個chunk讀取成功,就能返回讀取成功(讀吞吐會提升)。畫外音:這也符合R+W>N的定理,N=3份副本,W=3寫3個副本才算成功,R=1讀1個副本就算成功。 R+W>N定理未來再詳述。
之所以這麼設計,和文件操作「讀多寫少」的特性有關的,Google抓取的網頁,更新較少,讀取較多,這也是一個設計折衷的典型。畫外音:任何脫離業務的架構設計都是耍流氓。
除此之外,這裡還有一個「資料流與控制流分離」的設計準則:1. 控制流資料量小,client直接與主chunk-server互動;2. 資料流資料量大,client選擇「最近的路徑」傳送資料;畫外音:所謂「最近」,可以透過IP的相似度計算。
總結GFS的架構,體現了許多經典的設計實踐:1. 簡化系統角色,單點master降低系統複雜度;2. 不管是文件還是服務,均透過「冗餘+故障自動轉移」保證高可用;3. 由於存在單點master,GFS將「降低與單點核心的交互」作為性能優化;4. 透過寫入資料,原子法修改,checksum,原子監控6.5.控制流與資料流分離,提高效能;
知其然,知其所以然。思路比結論更重要。