在架構設計:文件服務的設計與實現一文中,經過實現一個文件服務來梳理了一個架構設計的通常流程,並獲得以下靜態架構圖node
本文繼續聊聊文件服務中的子模塊:「存儲模塊」的設計,包括:git
前面的架構沒有對存儲進行特別設計,直接使用了本地存儲。考慮到後期文件數量可能會愈來愈多,本地存儲可能沒法支撐,且本地存儲的安全性也沒有保障。爲了便於後期擴展,須要對「存儲」部分進行設計。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
對於單目錄下,隨着文件數量的增長致使訪問速度降低的問題,咱們該如何解決呢?架構
若是你作過度布式系統,那麼想想,咱們是否能夠把單目錄當作是一個服務器,訪問目錄下的文件當作是一個個的請求呢?若是能夠,那解決單目錄下訪問速度慢的問題是否是就變成了「如何解決單服務器下,負載太高」的問題了?那解決服務端負載太高的方法是否適用於解決目錄訪問速度降低的問題呢?併發
咱們從下面幾個方面來分析一下:
首先來看「解決服務端負載太高的方法」!答案很明顯:分流+負載均衡!
分佈式服務的負載均衡有幾種方式呢?
再來看「目錄訪問和服務器的區別」,雖然能夠把目錄當作服務器,可是二者仍是有區別的:
也就是說,對於目錄來講,咱們不須要考慮建立成本。
那麼針對服務器負載高的解決方案是否適合目錄訪問呢?或者哪一種方式適合目錄訪問呢?咱們一個個來分析:
能夠看到,主要的問題就是建立目錄的問題!如何保證在目錄數量改變時,不須要調整程序呢?
實際上git已經給出了答案:
也就是說,根據sha1散列的前兩位對文件進行歸類。這樣既解決了目錄建立問題,也解決了文件分佈問題。可能的問題是,「sha1散列2^80次,可能會發生一次碰撞」。這個問題對於通常文件系統來講,好像也沒有擔憂的必要。
解決了「單目錄文件過多,致使訪問速度降低」的問題,咱們來看下一個問題:數據安全。
文件數據是存放在電腦磁盤上的,若是硬盤損壞,可能致使文件的丟失。這實際仍是一個「單點問題」!
「單點問題」的解決方案是什麼呢?冗餘啊!
最簡單的方案就是定時去備份數據,能夠有以下幾種方案:
咱們繼續一個個的討論。
首先是人工備份,這是最low的方案,固然也是最簡單的,即有人按期去備份就好了。問題是時效性不高,例如一天備份一次,若是磁盤在備份前壞了,那就會丟失一天的數據。同時恢復比較耗時,須要人工處理。
第二個方案是代碼實現,即在上傳文件時,程序就自動備份。以上面的架構爲例,能夠添加一個BackupListener,當上傳完成後,經過事件,自動備份上傳的文件。同時下載時須要斷定文件是否完整,若是有問題則使用備份數據。此方案時效性獲得了保障,可是將數據備份和業務放到了一塊兒,且須要編碼實現,增長了業務代碼量。
第三個方案是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有:RAID0,RAID1,RAID01/RAID10,RAID5和RAID6等。咱們來看看這幾種RAID的特色,以及是否適用於咱們的文件服務。你會發現從RAID0到RAID6,又是一個從單點到分佈式的過程。
具體RAID相關內容可參考wiki,文末有連接!
看下面的兩張圖應該能更好的理解:
不管是RAID10仍是RAID01,對磁盤的使用效率都不高。那如何提升磁盤使用率呢?就有了RAID3。
對於本地存儲來講,RAID是個相對實用的解決方案,既能提升數據安全、快速擴容,也提升了讀寫速率。可是不管擴展多少磁盤,容量仍是相對有限,吞吐也相對有限,同時因爲其仍是單點,若是文件服務自己掛掉,就會致使單點故障。因此就有了分佈式文件系統。
分佈式文件系統下次單獨討論!