架構設計:文件服務存儲設計

架構設計:文件服務的設計與實現一文中,經過實現一個文件服務來梳理了一個架構設計的通常流程,並獲得以下靜態架構圖node

架構設計:文件服務存儲設計

本文繼續聊聊文件服務中的子模塊:「存儲模塊」的設計,包括:git

  • 引入「存儲模塊」後的架構調整
  • 本地文件存儲
  • libfuse
  • RAID
  • 分佈式文件存儲

架構調整

前面的架構沒有對存儲進行特別設計,直接使用了本地存儲。考慮到後期文件數量可能會愈來愈多,本地存儲可能沒法支撐,且本地存儲的安全性也沒有保障。爲了便於後期擴展,須要對「存儲」部分進行設計。github

存儲的方式有不少,本地存儲、NAS、分佈式存儲,爲了能支持不一樣的存儲方式,須要對「存儲模塊」進行抽象。考慮到「存儲模塊」涉及到IO,是一個相對底層的模塊。「上傳」這個核心模塊不能依賴於具體的存儲,因此這裏也須要對其進行依賴反轉。redis

架構設計:文件服務存儲設計

見紫色部分,UploadService調用了FileInfoRepository來存儲FileInfo,而FileInfoRepository是個接口,具體實現由存儲模塊中的實現類來實現。算法

public interface FileInfoRepository {
 public Path save(FileInfo fileInfo) throws IOException;
}
public class LocalFileInfoRepository implements FileInfoRepository {
 public Path save(FileInfo fileInfo) throws IOException {
 ...
 }
}
public class NASFileInfoRepository implements FileInfoRepository {
 public Path save(FileInfo fileInfo) throws IOException {
 ...
 }
}
public class DistributedFileInfoRepository implements FileInfoRepository {
 public Path save(FileInfo fileInfo) throws IOException {
 ...
 }
}
複製代碼

本地存儲

咱們先看本地存儲。最簡單的實現,就是直接使用IO將文件寫到對應的目錄下就能夠了。可是,本地存儲會有以下幾個問題:安全

  • 若是文件服務支持多租戶,全部租戶文件都寫在同一個目錄下,沒有作區分,可能致使文件混亂,遷移繁瑣。且文件數量增長會比較快。
  • 隨着文件數量的增多,某些文件系統的訪問速度可能會降低
  • 文件數增長,單機磁盤可能容量不夠
  • 沒有容錯和備份,磁盤損壞可能致使數據的永久丟失

下面咱們針對上面的問題,來一個個的解決。服務器

多租戶

首先,對於多租戶來講,在咱們的架構中,實際對應的是Group,咱們按照Group的不一樣,來劃分目錄便可。即不一樣的租戶有不一樣的文件根目錄,後期某個租戶遷移時,直接遷移對應目錄便可。這也稍微解決了單目錄文件數量多的問題。markdown

單目錄文加數量過多

對於單目錄下,隨着文件數量的增長致使訪問速度降低的問題,咱們該如何解決呢?架構

若是你作過度布式系統,那麼想想,咱們是否能夠把單目錄當作是一個服務器,訪問目錄下的文件當作是一個個的請求呢?若是能夠,那解決單目錄下訪問速度慢的問題是否是就變成了「如何解決單服務器下,負載太高」的問題了?那解決服務端負載太高的方法是否適用於解決目錄訪問速度降低的問題呢?併發

咱們從下面幾個方面來分析一下:

  • 解決服務端負載太高的方法
  • 目錄訪問和服務器的區別
  • 解決服務端負載太高的方法對目錄的適用性

首先來看「解決服務端負載太高的方法」!答案很明顯:分流+負載均衡

分佈式服務的負載均衡有幾種方式呢?

  • 隨機
  • 輪詢
  • 加權隨機
  • 加權輪詢
  • 源地址Hash
  • 一致性Hash

再來看「目錄訪問和服務器的區別」,雖然能夠把目錄當作服務器,可是二者仍是有區別的:

  • 部署一個服務要花費較長的時間,啓動服務最快也要幾秒鐘,且須要額外的硬件
  • 而目錄是系統基本功能,建立目錄很是的快速,只要磁盤夠,建立目錄基本沒有任何限制

也就是說,對於目錄來講,咱們不須要考慮建立成本。

那麼針對服務器負載高的解決方案是否適合目錄訪問呢?或者哪一種方式適合目錄訪問呢?咱們一個個來分析:

  • 隨機:在建立文件時,咱們隨機的選擇一個目錄進行建立。那麼問題來了,咱們一開始該建立多少目錄呢?新增目錄後,是否須要調整程序?由於隨機基數變了。對於服務來講,一開始須要作容量規劃,肯定有幾個服務,由於建立一個服務的成本仍是挺高的,比重啓服務的成本要高很多。可是建立目錄的成本卻很是的低,初期先肯定目錄數量的方式並不合適。
  • 輪詢:問題和隨機相似。
  • 加權隨機:同隨機
  • 加權輪詢:同輪詢
  • 源地址Hash:對於服務來講是對源地址hash,對文件來講,能夠對文件進行hash。hash完如何與目錄進行匹配呢?
  • 一致性Hash:同上

能夠看到,主要的問題就是建立目錄的問題!如何保證在目錄數量改變時,不須要調整程序呢?

實際上git已經給出了答案:

  • 對文件內容取sha1散列
  • 獲得的散列值的前兩位做爲目錄
  • 目錄不存在就新建
  • 若是已存在就直接保存
  • 後面的散列值做爲文件名

也就是說,根據sha1散列的前兩位對文件進行歸類。這樣既解決了目錄建立問題,也解決了文件分佈問題。可能的問題是,「sha1散列2^80次,可能會發生一次碰撞」。這個問題對於通常文件系統來講,好像也沒有擔憂的必要。

數據安全

解決了「單目錄文件過多,致使訪問速度降低」的問題,咱們來看下一個問題:數據安全

文件數據是存放在電腦磁盤上的,若是硬盤損壞,可能致使文件的丟失。這實際仍是一個「單點問題」!

「單點問題」的解決方案是什麼呢?冗餘啊!

最簡單的方案就是定時去備份數據,能夠有以下幾種方案:

  • 人工備份
  • 代碼實現
  • libfuse
  • RAID

咱們繼續一個個的討論。

人工備份

首先是人工備份,這是最low的方案,固然也是最簡單的,即有人按期去備份就好了。問題是時效性不高,例如一天備份一次,若是磁盤在備份前壞了,那就會丟失一天的數據。同時恢復比較耗時,須要人工處理。

代碼實現

第二個方案是代碼實現,即在上傳文件時,程序就自動備份。以上面的架構爲例,能夠添加一個BackupListener,當上傳完成後,經過事件,自動備份上傳的文件。同時下載時須要斷定文件是否完整,若是有問題則使用備份數據。此方案時效性獲得了保障,可是將數據備份和業務放到了一塊兒,且須要編碼實現,增長了業務代碼量。

libfuse

第三個方案是libfuse,libfuse是用戶態文件系統接口。下面是libfuse官方簡介:

FUSE (Filesystem in Userspace)是一個構建用戶態文件系統的接口。libfuse項目包括兩個組件:一個fuse內核模塊以及libufuse用戶態庫。libfuse用戶態庫提供了與FUSE內核模塊的通信實現。

經過libfuse能夠實現一個用戶態文件系統。libfuse提供方法,支持掛載文件系統、取消掛載文件系統、讀取內核請求及做出響應。lifuse提供了兩類API:高層級的同步API和低層級的異步API。不過不管哪一種方式,內核都是經過回調的方式和主程序通信。當使用高層級API的時候,回調基於文件名和路徑而不是索引節點(inodes),而且回調返回後這個進程也同時結束;當使用低層級API的時候,回調基於索引節點(inodes)工做而且響應必須使用獨立的API方法返回。

簡單來講,就是能夠用libfuse構建一個用戶態文件系統。以前在老東家作了一個日誌分析平臺,日誌的收集就使用了libfuse,大體架構以下:

架構設計:文件服務存儲設計

業務系統寫日誌到掛載的用戶態文件系統中,用戶態文件系統自動轉發到了後續的處理中間件:redis、消息隊列、文件系統。

在這裏也能夠用相似的功能,即在文件上傳後,用戶態文件系統自動備份。此方案解耦了文件備案邏輯與業務邏輯。

RAID

最後一個方案是RAID,即廉價冗餘磁盤陣列。RAID不但可備份文件,還支持併發讀寫,提升上傳下載速率。

經常使用的RAID有:RAID0,RAID1,RAID01/RAID10,RAID5和RAID6等。咱們來看看這幾種RAID的特色,以及是否適用於咱們的文件服務。你會發現從RAID0到RAID6,又是一個從單點到分佈式的過程。

具體RAID相關內容可參考wiki,文末有連接!

  • RAID 0:至關因而一個支持併發的單點應用。就是將兩個以上的磁盤並聯起來,成爲一個大容量的磁盤。同時在存放數據時,將數據分段後分散存儲在這些磁盤中。這樣就能並行的處理讀寫,提升了讀寫速度。可是沒有數據冗餘和容錯能力,假如一個磁盤壞了,那全部數據都會丟失。也就是說RAID0只起到了擴容和提升讀寫速度的做用。就咱們的文件服務來講,選RAID0確定是不合適的,由於最重要的數據安全沒有獲得保障,它比單磁盤的數據安全還要差!
  • RAID 1:至關因而個單線程的主從應用。將磁盤分爲兩組,一組工做磁盤、一組鏡像磁盤。當寫入時,同時寫工做磁盤和鏡像磁盤,因此寫入速度上會比RAID0慢一點。當一個工做磁盤壞了,能夠用鏡像磁盤。RAID1提供了數據安全保障,性能也足夠高。缺點就是利用率低,只用了磁盤的50%。不過對於通常場景來講,RAID1是個不錯的選擇。咱們的文件服務也能夠選擇RAID1
  • RAID10/RAID01是RAID0和RAID1的組合
  • RAID10:至關因而支持併發的集羣應用。即先對數據備份,而後數據被分段寫入
  • RAID01:至關因而支持併發的分佈式系統。即先將數據分段,而後對分段數據備份寫入

看下面的兩張圖應該能更好的理解:

架構設計:文件服務存儲設計

架構設計:文件服務存儲設計

不管是RAID10仍是RAID01,對磁盤的使用效率都不高。那如何提升磁盤使用率呢?就有了RAID3。

  • RAID3:至關於有註冊中心的分佈式系統。RAID3的一個前提是,通常狀況下,磁盤不會同時壞兩個。因此RAID3使用一個磁盤記錄校驗數據,其它盤做爲數據盤,寫入數據時,將數據分爲n-1份,寫到數據盤中,將校驗數據寫入校驗盤。當其中一個磁盤損壞了,能夠經過校驗盤和其它數據盤來恢復數據。RAID3讀取速度很快,可是寫入時由於要計算校驗值,因此較慢。適合讀多寫少的狀況。咱們的文件服務恰好是這樣的場景,因此也適合RAID3。可是眼尖的朋友確定已經發現問題了,這裏有個單點問題,就是校驗盤。由於校驗盤讀寫次數明顯大於數據盤,損壞的概率也就更大,頻繁更換校驗盤,重建校驗數據也是一件很麻煩的事情。那怎麼解決這個問題呢?
  • RAID5:經過將校驗數據也分散到各個磁盤中來解決這個問題。RAID5至關因而去中心化的分佈式系統。當一個磁盤損壞時,能夠經過其它磁盤的數據和校驗數據來恢復該磁盤的數據和校驗數據。和RAID3一樣的問題,重建數據速度比較慢,同時對於多個磁盤同時損壞的狀況也無能爲力。RAID5也一樣適用於咱們的文件服務。
  • RAID 6:至關於融合了註冊中心和去中心化的分佈式系統,也能夠說有兩套註冊中心的分佈式系統。它在RAID5的基礎上增長了兩個校驗盤,以另外一種校驗算法來進行校驗。當磁盤損壞時,經過其它數據盤的數據和校驗數據以及校驗盤的校驗數據來恢復數據。RAID6能恢復兩塊磁盤同時損壞狀況下的數據,相應的其寫入數據更慢,實現難度也更高。除非數據安全要求很高,通常不經常使用。

對於本地存儲來講,RAID是個相對實用的解決方案,既能提升數據安全、快速擴容,也提升了讀寫速率。可是不管擴展多少磁盤,容量仍是相對有限,吞吐也相對有限,同時因爲其仍是單點,若是文件服務自己掛掉,就會致使單點故障。因此就有了分佈式文件系統。

分佈式文件系統下次單獨討論!

參考資料

架構設計:文件服務存儲設計

相關文章
相關標籤/搜索