1. FastDFS介紹
FastDFS是一個開源的輕量級分布式文件系統,由跟蹤服務器(tracker server)、存儲服務器(storage server)和客戶端(client)三個部分組成,主要解決了海量數據存儲問題,特別適合以中小文件(建議范圍:4KB < file_size <500MB)為載體的在線服務。
Storage server
Storage server(后簡稱storage)以組(卷,group或volume)為單位組織,一個group內包含多台storage機器,數據互為備份,存儲空間以group內容量最小的storage為准,所以建議group內的多個storage盡量配置相同,以免造成存儲空間的浪費。
以group為單位組織存儲能方便的進行應用隔離、負載均衡、副本數定制(group內storage server數量即為該group的副本數),比如將不同應用數據存到不同的group就能隔離應用數據,同時還可根據應用的訪問特性來將應用分配到不同的group來做負載均衡;缺點是group的容量受單機存儲容量的限制,同時當group內有機器壞掉時,數據恢復只能依賴group內地其他機器,使得恢復時間會很長。
group內每個storage的存儲依賴於本地文件系統,storage可配置多個數據存儲目錄,比如有10塊磁盤,分別掛載在/data/disk1-/data/disk10,則可將這10個目錄都配置為storage的數據存儲目錄。
storage接受到寫文件請求時,會根據配置好的規則,選擇其中一個存儲目錄來存儲文件。為了避免單個目錄下的文件數太多,在storage第一次啟動時,會在每個數據存儲目錄里創建2級子目錄,每級256個,總共65536個文件,新寫的文件會以hash的方式被路由到其中某個子目錄下,然后將文件數據直接作為一個本地文件存儲到該目錄中。
Tracker server
Tracker是FastDFS的協調者,負責管理所有的storage server和group,每個storage在啟動后會連接Tracker,告知自己所屬的group等信息,並保持周期性的心跳,tracker根據storage的心跳信息,建立group==>[storage server list]的映射表。
Tracker需要管理的元信息很少,會全部存儲在內存中;另外tracker上的元信息都是由storage匯報的信息生成的,本身不需要持久化任何數據,這樣使得tracker非常容易擴展,直接增加tracker機器即可擴展為tracker cluster來服務,cluster里每個tracker之間是完全對等的,所有的tracker都接受stroage的心跳信息,生成元數據信息來提供讀寫服務。
Upload file
FastDFS向使用者提供基本文件訪問接口,比如upload、download、append、delete等,以客戶端庫的方式提供給用戶使用。
選擇tracker server
當集群中不止一個tracker server時,由於tracker之間是完全對等的關系,客戶端在upload文件時可以任意選擇一個trakcer。
選擇存儲的group
當tracker接收到upload file的請求時,會為該文件分配一個可以存儲該文件的group,支持如下選擇group的規則:
1. Round robin,所有的group間輪詢
2. Specified group,指定某一個確定的group
3. Load balance,剩余存儲空間多多group優先
選擇storage server
當選定group后,tracker會在group內選擇一個storage server給客戶端,支持如下選擇storage的規則:
1. Round robin,在group內的所有storage間輪詢
2. First server ordered by ip,按ip排序
3. First server ordered by priority,按優先級排序(優先級在storage上配置)
選擇storage path
當分配好storage server后,客戶端將向storage發送寫文件請求,storage將會為文件分配一個數據存儲目錄,支持如下規則:
1. Round robin,多個存儲目錄間輪詢
2. 剩余存儲空間最多的優先
生成Fileid
選定存儲目錄之后,storage會為文件生一個Fileid,由storage server ip、文件創建時間、文件大小、文件crc32和一個隨機數拼接而成,然后將這個二進制串進行base64編碼,轉換為可打印的字符串
選擇兩級目錄
當選定存儲目錄之后,storage會為文件分配一個fileid,每個存儲目錄下有兩級256*256的子目錄,storage會按文件fileid進行兩次hash(猜測),路由到其中一個子目錄,然后將文件以fileid為文件名存儲到該子目錄下
生成文件名
當文件存儲到某個子目錄后,即認為該文件存儲成功,接下來會為該文件生成一個文件名,文件名由group、存儲目錄、兩級子目錄、fileid、文件后綴名(由客戶端指定,主要用於區分文件類型)拼接而成。

文件同步
寫文件時,客戶端將文件寫至group內一個storage server即認為寫文件成功,storage server寫完文件后,會由后台線程將文件同步至同group內其他的storage server。
每個storage寫文件后,同時會寫一份binlog,binlog里不包含文件數據,只包含文件名等元信息,這份binlog用於后台同步,storage會記錄向group內其他storage同步的進度,以便重啟后能接上次的進度繼續同步;進度以時間戳的方式進行記錄,所以最好能保證集群內所有server的時鍾保持同步。
storage的同步進度會作為元數據的一部分匯報到tracker上,tracke在選擇讀storage的時候會以同步進度作為參考。
比如一個group內有A、B、C三個storage server,A向C同步到進度為T1 (T1以前寫的文件都已經同步到B上了),B向C同步到時間戳為T2(T2 > T1),tracker接收到這些同步進度信息時,就會進行整理,將最小的那個做為C的同步時間戳,本例中T1即為C的同步時間戳為T1(即所有T1以前寫的數據都已經同步到C上了);同理,根據上述規則,tracker會為A、B生成一個同步時間戳。
Download file
客戶端upload file成功后,會拿到一個storage生成的文件名,接下來客戶端根據這個文件名即可訪問到該文件。

跟upload file一樣,在download file時客戶端可以選擇任意tracker server。
tracker發送download請求給某個tracker,必須帶上文件名信息,tracke從文件名中解析出文件的group、大小、創建時間等信息,然后為該請求選擇一個storage用來服務讀請求。由於group內的文件同步時在后台異步進行的,所以有可能出現在讀到時候,文件還沒有同步到某些storage server上,為了盡量避免訪問到這樣的storage,tracker按照如下規則選擇group內可讀的storage。
1. 該文件上傳到的源頭storage - 源頭storage只要存活着,肯定包含這個文件,源頭的地址被編碼在文件名中。
2. 文件創建時間戳==storage被同步到的時間戳且(當前時間-文件創建時間戳) > 文件同步最大時間(如5分鍾) - 文件創建后,認為經過最大同步時間后,肯定已經同步到其他storage了。
3. 文件創建時間戳 < storage被同步到的時間戳。 - 同步時間戳之前的文件確定已經同步了 。
4. (當前時間-文件創建時間戳) > 同步延遲閥值(如一天)。 - 經過同步延遲閾值時間,認為文件肯定已經同步了。
小文件合並存儲
將小文件合並存儲主要解決如下幾個問題:
1. 本地文件系統inode數量有限,從而存儲的小文件數量也就受到限制。
2. 多級目錄+目錄里很多文件,導致訪問文件的開銷很大(可能導致很多次IO)
3. 按小文件存儲,備份與恢復的效率低
FastDFS在V3.0版本里引入小文件合並存儲的機制,可將多個小文件存儲到一個大的文件(trunk file),為了支持這個機制,FastDFS生成的文件fileid需要額外增加16個字節
1. trunk file id
2. 文件在trunk file內部的offset
3. 文件占用的存儲空間大小,字節對齊及刪除空間復用,文件占用存儲空間>=文件大小
每個trunk file由一個id唯一標識,trunk file由group內的trunk server負責創建(trunk server是tracker選出來的),並同步到group內其他的storage,文件存儲合並存儲到trunk file后,根據其offset就能從trunk file讀取到文件。
文件在trunk file內的offset編碼到文件名,決定了其在trunk file內的位置是不能更改的,也就不能通過compact的方式回收trunk file內刪除文件的空間。但當trunk file內有文件刪除時,其刪除的空間是可以被復用的,比如一個100KB的文件被刪除,接下來存儲一個99KB的文件就可以直接復用這片刪除的存儲空間。
HTTP訪問支持
FastDFS的tracker和storage都內置了http協議的支持,客戶端可以通過http協議來下載文件,tracker在接收到請求時,通過http的redirect機制將請求重定向至文件所在的storage上;除了內置的http協議外,FastDFS還提供了通過apache或nginx擴展模塊下載文件的支持。

其他特性
FastDFS提供了設置/獲取文件擴展屬性的接口(setmeta/getmeta),擴展屬性以key-value對的方式存儲在storage上的同名文件(擁有特殊的前綴或后綴),比如/group/M00/00/01/some_file為原始文件,則該文件的擴展屬性存儲在/group/M00/00/01/.some_file.meta文件(真實情況不一定是這樣,但機制類似),這樣根據文件名就能定位到存儲擴展屬性的文件。
以上兩個接口作者不建議使用,額外的meta文件會進一步“放大”海量小文件存儲問題,同時由於meta非常小,其存儲空間利用率也不高,比如100bytes的meta文件也需要占用4K(block_size)的存儲空間。
FastDFS還提供appender file的支持,通過upload_appender_file接口存儲,appender file允許在創建后,對該文件進行append操作。實際上,appender file與普通文件的存儲方式是相同的,不同的是,appender file不能被合並存儲到trunk file。
安裝單機版FastDFS
將安裝包上傳到服務器的/usr/local/software目錄下
安裝所需的依賴包
yum install make cmake gcc gcc-c++
這個下載的過程很慢。中間會遇到兩次確認,[Y/N],選擇Y確認
安裝libfatscommon
unzip libfastcommon-master.zip 進入解壓后的路徑,執行./make.sh
等編譯完成后,執行./make.sh install
安裝FastDFS
返回/usr/local/software目錄,解壓fastdfs-master.zip
unzip fastdfs-master.zip
進入解壓后的目錄,執行編譯命令
cd fastdfs-master
./make.sh
等編譯成功后,進行安裝
cd ..
配置Tracker服務器
創建base_path目錄
在software下創建fastdfs文件文件夾,在里面再寫一個tracker文件夾。
創建文件夾的命令是:
你也可以把tracker創建在別的地方,只要在配置文件中填寫好你存放數據的位置就行。
我存放數據的路徑是/usr/local/software/fastdfs/tracker
修改tracker配置文件
復制tracker的配置文件並重命名,編輯配置文件
開放端口
如果你直接關閉了防火牆,可以省略這一步。
使用iptables開放如下端口
/sbin/iptables -I INPUT -p tcp --dport 22122 -j ACCEPT
保存
/etc/rc.d/init.d/iptables save
重啟服務
service iptables restart
啟動tracker
啟動命令:
/etc/init.d/fdfs_trackerd start
啟動成功后tracker目錄下會生成兩個文件夾
可以使用命令查看tracker的運行狀態:
ps -ef | grep fdfs_trackerd
配置Storage服務器
注:實際開發中,tracker和storage是在不同的服務器上的,所以安裝tracker和storage都要執行第1.1-1.3節中的步驟。在本課程中,只使用一台服務器,所以配置storage的時候省略了1.1-1.3節中的操作。
創建base_path目錄
mkdir -p /usr/local/software/fastdfs/storage
修改storage配置文件
cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
vim /etc/fdfs/storage.conf
往下找,還有個store_path0和tracker_server, tracker_server的ip就是tracker服務器的ip
開放端口
如果你直接關閉了防火牆,可以省略這一步。
使用iptables開放如下端口
/sbin/iptables -I INPUT -p tcp --dport 23000 -j ACCEPT
保存
/etc/rc.d/init.d/iptables save
重啟服務
service iptables restart
啟動storage
/etc/init.d/fdfs_storaged start
pwd使用命令查看storage的運行狀態:
ps -ef | grep fdfs_storaged
測試
cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
vim /etc/fdfs/client.conf
修改下面這兩行為tracker的配置
保存並退出
執行
/usr/bin/fdfs_upload_file /etc/fdfs/client.conf /usr/1712.txt
最后一個/usr/1712.txt是提前放到服務器上的文件。
返回的group1/xxxxx就是上傳成功后的訪問地址。暫時還不能通過http查看。
與nginx整合
把壓縮包上傳到/usr/local/software目錄下
解壓
unzip fastdfs-nginx-module-master.zip
進入解壓后的路徑
cd /usr/local/software/fastdfs-nginx-module-master/src
將mod_fastdfs.conf復制到/etc/fdfs下
cp mod_fastdfs.conf /etc/fdfs
修改下面的配置:
將libfdfsclient.so拷貝至/usr/lib下
cp /usr/lib64/libfdfsclient.so /usr/lib/
將/usr/local/software/fastdfs-master/conf下這兩個文件復制過去
創建nginx/client目錄
mkdir -p /var/temp/nginx/client
nginx依賴環境參照nginx安裝文檔
將nginx壓縮包上傳到/usr/local/software下
安裝nginx
進入/usr/local/software/nginx-1.10.0
安裝依賴包
yum -y install gcc pcre pcre-devel zlib zlib-devel openssl openssl-devel
這個下載的過程有點慢。
執行
./configure --prefix=/usr/local/software/nginx --sbin-path=/usr/bin/nginx --add-module=/usr/local/software/fastdfs-nginx-module-master/src
編譯后執行 make,再執行 make install
修改/usr/local/software/nginx/conf/nginx.conf
添加server:
server {
listen 8888;
server_name localhost;
location /group1/M00/{
ngx_fastdfs_module;
}
}
說明:
server_name指定本機ip
location /group1/M00/:group1為nginx 服務FastDFS的分組名稱,M00是FastDFS自動生成編號,對應store_path0,如果FastDFS定義store_path1,這里就是M01
8888端口號與/etc/fdfs/storage.conf中的http.server_port=8888相對應
啟動nginx
nginx
停止nginx
nginx -s stop
重新啟動
nginx -s reload
java操作fastDFS文件上傳
下載工程https://github.com/happyfish100/fastdfs-client-java
安裝到自己的maven倉庫
導入jar包:
1 <!-- fastdfs --> 2 <dependency> 3 <groupId>org.csource</groupId> 4 <artifactId>fastdfs-client-java</artifactId> 5 <version>1.27-SNAPSHOT</version> 6 </dependency>
創建配置文件client.conf:
1 tracker_server=服務器的ip或域名:22122
測試方法:
1 import org.csource.fastdfs.*; 2 3 public class FastDFSTest { 4 public static void main(String[] args) throws Exception { 5 // 1、向工程中添加jar包 6 // 2、創建一個配置文件。配置tracker服務器地址 7 // 3、加載配置文件(絕對路徑,工程目錄不要有中文) 8 ClientGlobal.init(FastDFSTest.class.getResource("/").getPath() + "client.conf"); 9 // 4、創建一個TrackerClient對象。 10 TrackerClient trackerClient = new TrackerClient(); 11 // 5、使用TrackerClient對象獲得trackerserver對象。 12 TrackerServer trackerServer = trackerClient.getConnection(); 13 // 6、創建一個StorageServer的引用null就可以。 14 StorageServer storageServer = null; 15 // 7、創建一個StorageClient對象。trackerserver、StorageServer兩個參數。 16 StorageClient storageClient = new StorageClient(trackerServer, storageServer); 17 // 8、使用StorageClient對象上傳文件。 18 String[] strings = storageClient.upload_file("f:/qf_logo.jpg", "jpg", null); 19 for (String string : strings) { 20 System.out.println(string); 21 } 22 } 23 }
封裝工具類:
1 public class FastDFSClient { 2 3 private TrackerClient trackerClient = null; 4 private TrackerServer trackerServer = null; 5 private StorageServer storageServer = null; 6 private StorageClient1 storageClient = null; 7 8 public FastDFSClient(String conf) throws Exception { 9 if (conf.contains("classpath:")) { 10 conf = conf.replace("classpath:", this.getClass().getResource("/").getPath()); 11 } 12 ClientGlobal.init(conf); 13 trackerClient = new TrackerClient(); 14 trackerServer = trackerClient.getConnection(); 15 storageServer = null; 16 storageClient = new StorageClient1(trackerServer, storageServer); 17 } 18 19 /** 20 * 上傳文件方法 21 * <p>Title: uploadFile</p> 22 * <p>Description: </p> 23 * @param fileName 文件全路徑 24 * @param extName 文件擴展名,不包含(.) 25 * @param metas 文件擴展信息 26 * @return 27 * @throws Exception 28 */ 29 public String uploadFile(String fileName, String extName, NameValuePair[] metas) throws Exception { 30 String result = storageClient.upload_file1(fileName, extName, metas); 31 return result; 32 } 33 34 public String uploadFile(String fileName) throws Exception { 35 return uploadFile(fileName, null, null); 36 } 37 38 public String uploadFile(String fileName, String extName) throws Exception { 39 return uploadFile(fileName, extName, null); 40 } 41 42 /** 43 * 上傳文件方法 44 * <p>Title: uploadFile</p> 45 * <p>Description: </p> 46 * @param fileContent 文件的內容,字節數組 47 * @param extName 文件擴展名 48 * @param metas 文件擴展信息 49 * @return 50 * @throws Exception 51 */ 52 public String uploadFile(byte[] fileContent, String extName, NameValuePair[] metas) throws Exception { 53 54 String result = storageClient.upload_file1(fileContent, extName, metas); 55 return result; 56 } 57 58 public String uploadFile(byte[] fileContent) throws Exception { 59 return uploadFile(fileContent, null, null); 60 } 61 62 public String uploadFile(byte[] fileContent, String extName) throws Exception { 63 return uploadFile(fileContent, extName, null); 64 } 65 } 66 測試方法: 67 FastDFSClient fastDFSClient = new FastDFSClient( 68 "classpath:client.conf"); 69 String string = fastDFSClient.uploadFile("f:/logo.jpg"); 70 System.out.println(string);
返回結果:
group1/M00/00/00/CscAbloEvACATPsCAALl54RJz6c951.jpg