雖然分布式存儲的存儲層和上傳下載這一層很重要,但在元數據方面有哪些選擇,這些選擇有什么優缺點則更為重要。鑒于此,七牛首席架構師李道兵結合自己多年的實踐和思考,分享了存儲設計的幾大方法,并詳細分析了各種方法的利弊。
常規的存儲設計方法主要有以下幾類。
無中心的存儲設計,如GlusterFS。
有中心的存儲設計,如Hadoop。
基于數據庫的存儲設計,如GridFS和HBase。
繞過元數據的存儲設計,如FastDFS。
下面我們來逐一進行講述。
無中心的存儲設計:GlusterFS
目前,GlusterFS在互聯網行業中用的較少,在大型計算機中用的較多。它在設計上具有以下幾個特點。
兼容POSIX文件系統:單機上的應用程序無需修改就可以放到GlusterFS上運行。
沒有中心節點:不存在單點故障和性能瓶頸。
擴展能力:因為沒有中心節點,所以GlusterFS的擴展能力無限,擴展多少臺服務器都沒有問題。
我們不討論POSIX兼容的優劣,集中通過GlusterFS來討論無中心節點設計中普遍遇到的一些難點。
1. 壞盤修復。無中心暗示著可通過key推算這個key的存儲位置。但是,磁盤壞了(單盤故障)怎么辦呢?直接的處理方式是去掉壞盤,換上新盤,將這些數據拷貝過來。這樣的處理方式在小型系統上非常適用。但是一個4T的Sata盤,即使在千兆網下寫入速度也只能達到100MB/s,修復時間會是11小時。考慮到一般的存儲滿指的是全部存儲量的80%,那么修復時間將達到8到9個小時,所以不適合用于大型系統。大型系統磁盤多,每天會壞很多塊磁盤。如果磁盤是同一批購買的,由于對稱設計,讀寫的特性很相似,那么在壞盤修復的八九個小時里,其他磁盤同時壞的概率非常高。這時,整個系統的可靠性會比較低,只能達到6個9或7個9。如果想達到更高的可靠性,只能通過瘋狂增加副本數這種會讓成本大量提高的方法來解決。
GlusterFS本身不用這種修復設計,它的修復方式是在讀磁盤時發現有壞塊就觸發修復,但這種設計的修復時間會更長。該如何回避掉這個問題呢?最簡單的方法是將磁盤分成多個區,確保每個區盡量小。例如,將4T盤分成50個區,每個區只有80GB,并且記錄各個區的一些元信息。在我們從key推算存儲位置時,先計算出這個 key 所在區的編號,再通過剛才的元信息獲得這個區對應的機器、磁盤和目錄。這樣帶來的好處是,在磁盤壞了的時候,不用等換盤就可以開始修復,并且不一定要修復到同一塊磁盤上,可以修復到有空間的多塊磁盤上,從而得到一種并發的修復能力。如果將4T盤分成50個區(每個區80G),那么修復時間就可以從8到9個小時縮減到13分鐘左右。
2. 擴容。如前所述,在無中心節點的設計中,從key可以推算它存儲的位置,并且我們要求key是均勻Hash的。這時,如果集群規模從一百臺擴容到二百臺,會出現什么問題呢?Hash是均勻的,意味著新加的一百臺機器的存儲負載要和以前的一致,有一半的數據要進行遷移。但集群很大時,數據遷移過程中所花的時間通常會很長,這時會出現網絡擁塞。同時數據遷移過程中的讀寫邏輯會變得非常復雜。解決方法是多加一些測試,改善代碼質量,不要出Bug。還有一種方法是,保持容量固定,盡量不要擴容,但這與互聯網的風格(從很小的模型做起,慢慢將它擴大化)不相符。
3. 不支持異構存儲。提供云存儲服務的公司必然會面臨一個問題,有很多客戶存儲的文件非常小,而另外很多客戶存儲的文件非常大。小文件通常伴隨著很高的IOPS需求,比較簡單的做法是將Sata盤(100IOPS)換成SAS盤(300 IOPS)或者SSD盤(10000 IOPS以上)來得到更高的IOPS。但是對于無中心存儲,由于key是Hash的,所以根本不知道小文件會存在哪里,這時能提高IOPS的唯一辦法是加強所有機器的能力。例如,每臺機器中將10塊Sata盤、2塊SAS盤和1塊SSD盤作為一個整體加入緩存系統,提升系統的IOPS,但整體的成本和復雜性都會增加很多。另外一種方式是拼命擴大集群規模,這樣整體的IOPS能力自然會比較高。
類似的異構需求還包括某些客戶數據只想存兩份,而其他客戶數據則想多存幾份,這在無中心存儲中都很難解決。
4. 數據不一致的問題。比如要覆蓋一個key,基于Hash意味著要刪除一個文件,再重新上傳一個文件,新上傳的文件和之前的一個文件會在同一塊磁盤的同一個目錄下使用同樣的文件名。如果覆蓋時出現意外,只覆蓋了三個副本中的兩個或者一個,那么就很容易讀到錯誤的數據,讓用戶感覺到覆蓋沒有發生,但原始數據已經丟失了。這是最難容忍的一個問題。解決方案是在寫入文件時,先寫一個臨時文件,最后再重命名修改。但由于是在分布式架構中,且跨機器操作,會導致邏輯的復雜性增加很多。
簡單總結一下,以GlusterFS為代表的無中心設計帶來的一些問題如下圖所示。
[page]有中心的存儲設計:Hadoop
Hadoop的設計目標是存大文件、做offline分析、可伸縮性好。Hadoop的元數據存儲節點(NameNode節點)屬于主從式存儲模式,盡量將數據加載到內存,能提高對數據的訪問性能。另外,放棄高可用,可進一步提高元數據的響應性能(NameNode的變更不是同步更新到從機,而是通過定期合并的方式來更新)。
Hadoop具有以下幾方面優點。
1.為大文件服務。例如在塊大小為64MB時,10PB文件只需要存儲1.6億條數據,如果每條200Byte,那么需要32GB左右的內存。而元信息的QPS也不用太高,如果每次QPS能提供一個文件塊的讀寫,那么1000QPS就能達到512Gb/s的讀寫速度,滿足絕大部分數據中心的需求。
2.為offline業務服務。高可用服務可以部分犧牲。
3.為可伸縮性服務。可以伸縮存儲節點,元信息節點無需伸縮。
然而有如此設計優點的Hadoop為什么不適合在公有云領域提供服務呢?
1.元信息容量太小。在公有云平臺上,1.6億是個非常小的數字,單個熱門互聯網應用的數據都不止這些。1.6億條數據占用32GB內存,100億條數據需要2000GB內存,這時Hadoop就完全扛不住了。
2.元信息節點無法伸縮。元信息限制在單臺服務器,1000QPS甚至是優化后的15000QPS的單機容量遠不能達到公有云的需求。
3.高可用不完美。就是前面所提到的NameNode問題,在業務量太大時,Hadoop支撐不住。
因此,做有中心的云存儲時,通常的做法是完全拋棄掉原先的單中心節點設計,引入一些其他的設計。
常見的是WRN算法,為數據準備多個樣本,寫入W份才成功,成功讀取R份才算成功,W+R大于N(其中N為總副本數)。這樣就解決了之前提到的高可用問題,任何一個副本宕機,整個系統的讀寫都完全不受影響。
在這個系統里,利用這種技術有以下幾個問題需要注意。
1.W,R,N選多少合適。第一種選擇2,2,3,一共三副本,寫入兩份成功,讀取時成功讀取兩份算成功。但是其中一臺機器下線的話,會出現數據讀不出來的情況,導致數據不可用。第二種選擇4,4,6或者6,6,9,能夠容忍單機故障。但機器越多,平均響應時間越長。
2.失敗的寫入會污染數據。比如4,4,6的場景,如果寫入只成功了3份,那么這次寫入是失敗的。但如果寫入是覆蓋原來的元數據,就意味著現在有三份正確的數據,有三份錯誤的數據,無法判斷哪份數據正確,哪份數據錯誤。回避的方法是寫入數據帶版本(不覆蓋,只是追加),即往數據庫里面插入新數據。有些云存儲的使用方法是對一個文件進行反復的修改,比如用M3U8文件直播。隨著直播進程的開始不停地更改文件,大概5秒鐘一次,持續一個小時文件會被修改720次。這時候從數據庫讀取文件信息時,就不再是讀1條記錄,而是每臺讀取720條記錄,很容易導致數據庫出現性能瓶頸。
下面,我們簡單總結一下以Hadoop為代表的有中心的存儲設計速存在的問題。
[page]基于數據庫的分布式存儲設計:GridFS和HBase
GridFS
GridFS基于MongoDB,相當于一個文件存儲系統,是一種分塊存儲形式,每塊大小為255KB。數據存放在兩個表里,一個表是chunks,加上元信息后單條記錄在256KB以內;另一個表是files,存儲文件元信息。
GridFS的優點如下所述。
1.可以一次性滿足數據庫和文件都需要持久化這兩個需求。絕大部分應用的數據庫數據需要持久化,用戶上傳的圖片也要做持久化,GridFS用一套設施就能滿足,可以降低整個運維成本和機器采購成本。
2.擁有MongoDB的全部優點:在線存儲、高可用、可伸縮、跨機房備份。
3.支持Range GET,刪除時可以釋放空間(需要用MongoDB的定期維護來釋放空間)。
GridFS的缺點如下所述。
1.oplog耗盡。oplog是MongoDB上一個固定大小的表,用于記錄MongoDB上的每一步操作,MongoDB的ReplicaSet的同步依賴于oplog。一般情況下oplog在5GB~50GB附近,足夠支撐24小時的數據庫修改操作。但如果用于GridFS,幾個大文件的寫入就會導致oplog迅速耗盡,很容易引發secondary機器沒有跟上,需要手工修復,大家都知道,MongoDB的修復非常費時費力。簡單來說,就是防沖擊能力差,跟數據庫的設計思路有關,畢竟不是為文件存儲設計的。除了手工修復的問題,沖擊還會造成主從數據庫差異拉大,對于讀寫分離,或者雙寫后再返回等使用場景帶來不小的挑戰。
2.濫用內存。MongoDB使用MMAP將磁盤文件映射到內存。對于GridFS來說,大部分場景都是文件只需讀寫一次,對這種場景沒法做優化,內存浪費巨大,會擠出那些需要正常使用內存的數據(比如索引)。這也是設計阻抗失配帶來的問題。
3.伸縮性問題。需要伸縮性就必須引入MongoDB Sharding,需要用files_id作Sharding,如果不修改程序,files_id遞增,所有的寫入都會壓入最后一組節點,而不是均勻分散。在這種情況下,需要改寫驅動,引入一個新的files_id生成方法。另外,MongoDBSharding在高容量壓力下的運維很痛苦。MongoDB Sharding需要分裂,分裂的時候響應很差,會導致整個服務陷入不可用的狀態。
對GridFS的總結如下。
HBase
前面提到Hadoop因為NameNode容量問題,所以不適合用來做小文件存儲,那么HBase是否合適呢?
HBase有以下優點。
1.伸縮性、高可用都在底層解決了。
2.容量很大,幾乎沒有上限。
但缺點才是最關鍵的,HBase有以下缺點。
1.微妙的可用性問題。首先是Hadoop NameNode的高可用問題。其次,HBase的數據放在Region上,Region會有分裂的問題,分裂和合并的過程中Region不可用,不管用戶這時是想讀數據還是寫數據,都會失敗。雖然可以用預分裂回避這個問題,但這就要求預先知道整體規模,并且key的分布是近均勻的。在多用戶場景下,key均勻分布很難做到,除非舍棄掉key必須按順序這個需求。
2.大文件支持。HBase對10MB以上的大文件支持不好。改良方案是將數據拼接成大文件,然后HBase只存儲文件名、offset和size。這個改良方案比較實用,但在空間回收上需要補很多開發的工作。
應對方案是HBase存元數據,Hadoop存數據。但是,Hadoop是為offline設計的,對NameNode的高可用考慮不充分。HBase的Region分拆和合并會造成短暫的不可用,如果可以,最好做預拆,但預拆也有問題。如果對可用性要求低,那么這種方案可用。
[page]繞過問題的存儲設計:FastDFS
Hadoop的問題是NameNode壓力過高,那么FastDFS的思路是給NameNode減壓。減壓方法是將NameNode的信息編碼到key中。對于范例URL:group1/M00/00/00/rBAXr1AJGF_3rCZAAAAEc45MdM850_big.txt來說,NameNode只需要做一件事,將group1翻譯成具體的機器名字,不用關心key的位置,只要關心組的信息所在的位置,而這個組的信息就放在key里面,就繞過了之前的問題。
FastDFS的優點如下。
1.結構簡單,元數據節點壓力低。
2.擴容簡單,擴容后無需重新平衡。
FastDFS的缺點如下。
1.不能自定義key,這對多租戶是致命的打擊,自己使用也會降低靈活性。
2.修復速度:磁盤鏡像分布,修復速度取決于磁盤寫入速度,比如4TB的盤,100MB/s的寫入速度,至少需要11個小時。
3.大文件沖擊問題沒有解決。首先,文件大小有限制(不能超過磁盤大小);其次,大文件沒有分片,導致大文件的讀寫都由單塊盤承擔,所以對磁盤的網絡沖擊很大。
結語
本文基于七牛首席架構師李道兵在QCon會議上的演講內容整理而成,著重對幾種存儲設計進行了比較全面的分析,總結如下。而關于分布式存儲的元數據設計還有很多更為細節的點和經驗值得探討,留待日后慢慢與大家交流。