FastDFS概述
FastDFS是一款開源的輕量級分散式檔案系統
純C實現,支援Linux, FreeBSD等UNIX系統
類google FS, 不是通用的檔案系統,只能夠透過專有API訪問,目前提供了C,Java和PHP API
為網際網路應用量身定做,解決大容量檔案儲存問題,追求高效能和高擴充套件性
FastDFS可以看做是基於檔案的key-value儲存系統,稱為分散式檔案儲存服務更為合適
FastDFS提供的功能
upload 上傳檔案
download 下載檔案
delete 刪除檔案
心得:一個合適的(不需要選擇最複雜的,而是最滿足自己的需求。複雜的自己因為理解問題,導致無法掌控。當在出現一些突發性問題時,因為無法及時解決導致災難性的後果)檔案系統需要符合什麼樣的哲學,或者說應該使用什麼樣的設計理念?
一個好的分散式檔案系統最好提供Nginx的模組,因為對於網際網路應用來說,象檔案這種靜態資源,一般是透過HTTP的下載,此時透過容易擴充套件的Nginx來訪問Fastdfs,能夠讓檔案的上傳和下載變得特別簡單。另外,網站型應用在網際網路領域中的比例是非常高,因此PHP這種語言作為非常成熟,效能也完全能夠讓人滿意的網站開發語言,提供相應的擴充套件,也是非常重要的。所以在應用領域上,Fastdfs是非常合適的。
檔案系統天生是靜態資源,因此象可修改或者可追加的檔案看起來就沒有太大的意義了。檔案屬性也最好不要支援,因為可以透過副檔名和尺寸等屬性,透過附加在檔名稱上,來避免出現儲存屬性的資訊。另外,透過新增屬性支援,還不如用其他的東西, 例如redis等來支援,以避免讓此分散式檔案系統變得非常複雜。
之所以說FastDFS簡單,在於其架構中,只有兩種角色,一個是storage, 一個是tracker。但從實現上講,實際上有三個模組:tracker, storage和fastdfs client。fastdfs純粹是協議的解析,以及一些簡單的策略。關鍵還是在於tracker和storage。
在設計FastDFS時,除了如上的哲學外,很重要的就是上傳,下載,以及刪除。以及如何實現同步,以便實現真正的分散式,否則的話這樣和普通的單機檔案系統就沒有什麼區別了。
如果是我們自己來設計一下分散式的檔案系統,如果我們要上傳。那麼,必然要面臨著下面的一些選擇:
上傳到哪裡去?難道由客戶端來指定上傳的伺服器嗎?
只上傳一臺伺服器夠嗎?
上傳後是原樣儲存嗎?(chunk server比較危險,沒有把握不要去做)
對於多IDC如何考慮?
對於使用者來說,當需要上傳檔案的時候,他/她關心什麼?
1- 上傳的檔案必須真實地保留著,不能夠有任何的加工。雖然chunk server之類的看起來不錯,但是對於中小型組織來說,一旦因為一些技術性的bug,會導致chunk server破壞掉原來的檔案內容,風險比較大
2- 上傳成功後,能夠立馬返回檔名稱,並根據檔名稱馬上完整地下載。原始檔名稱我們不關心(如果需要關心,例如象論壇的附件,可以在資料庫中儲存這些資訊,而不應該交給DFS來處理)。這樣的好處在於DFS能夠更加靈活和高效,例如可以在檔名稱中加入很多的附屬資訊,例如圖片的尺寸等。
3- 上傳後的檔案不能夠是單點,一定要有備份,以防止檔案丟失
4- 對於一些熱點檔案,希望能夠做到保證儘可能快速地大量訪問
上面的需求其實是比較簡單的。首先讓我們回到最原始的時代,即磁碟來儲存檔案。在這個時代,當我們需要管理檔案的時候,通常我們都是在單機的磁碟上建立一個目錄,然後在此目錄下面存放檔案。因為使用者往往檔名稱是很隨意的,所以使用使用者指定的檔名稱可能會錯誤地覆蓋其他的檔案。因此,在處理的時候,絕對不能夠使用使用者指定的名稱,這是分析後得到的第一個結論。
如果使用者上傳檔案後,分配一個檔名稱(具體檔名稱的分配策略以後再考慮),那麼如果所有的檔案都儲存在同一個目錄下面,在做目錄項的遍歷時將非常麻煩。根據網上的資料,一般單目錄下的檔案個數一般限制不能夠超過3萬;同樣的,一個目錄下面的目錄數也最好不要超過這個數。但實際上,為了安全考慮,一般都不要儲存這麼多的內容。假定,一個目錄下面,儲存1000個檔案,每個檔案的平均大小為10KB,則單目錄下面可儲存的容量是10MB。這個容量太小了,所以我們要多個目錄,假定有1000個目錄,每個目錄儲存10MB,則可以儲存10GB的內容;這對於目前磁碟的容量來說,利用率還是不夠的。我們再想辦法,轉成兩級目錄,這樣的話,就是第一層目錄有1000個子目錄,每一級子目錄下面又有1000級的二級子目錄,每個二級子目錄,可以儲存10MB的內容,此時就可以儲存10T的內容,這基本上超過了目前單機磁碟的容量大小了。所以,使用二級子目錄的辦法,是平衡儲存效能和利用儲存容量的辦法。
這樣子的話,就回到了上面的問題,如果我們開始只做一個單機版的基於檔案系統的儲存服務,假如提供TCP的服務(不基於HTTP,因為HTTP的負載比太低)。很簡單,客戶端需要知道儲存伺服器的地址和埠。然後,指定要上傳的檔案內容;伺服器收到了檔案內容後,如何選擇要儲存在哪個目錄下呢?這個選擇要保證均衡性,即儘量保證檔案能夠均勻地分散在所有的目錄下。
負載均衡性很重要的就是雜湊,例如,在PHP中常用的md5,其返回一個32個字元,即16位元組的輸出,即128位。雜湊後要變成桶,才能夠分佈,自然就有了如下的問題:
1- 如何得到雜湊值?md5還是SHA1
2- 雜湊值得到後,如何構造雜湊桶
3- 根據檔名稱如何定位雜湊桶
首先來回答第3個問題,根據檔名稱如何定位雜湊桶。很簡單,此時我們只有一個檔名稱作為輸入,首先要計算雜湊值,只有一個辦法了,就是根據檔名稱來得到雜湊值。這個函式可以用整個檔名稱作為雜湊的輸入,也可以根據檔名稱的一部分來完成。結合上面說的兩級目錄,而且每級目錄不要超過1000.很簡單,如果用32位的字元輸出後,可以取出實現上來說,由於檔案上傳是防止唯一性,所以如果根據檔案內容來產生雜湊,則比較好的辦法就是擷取其中的4位,例如:
md5sum fdfs_storaged.pid
52edc4a5890adc59cec82cb60f8af691 fdfs_storaged.pid
上面,這個fdfs_storage.pid中,取出最前面的4個字元,即52和ed。這樣的話,假如52是一級目錄的名稱,ed是二級目錄的名稱。因為每一個字元有16個取值,所以第一級目錄就有16 * 16 = 256個。總共就有256 * 256 = 65526個目錄。如果每個目錄下面存放1000個檔案,每個檔案30KB,都可以有1966G,即2TB左右。這樣的話,足夠我們用好。如果用三個字元,即52e作為一級目錄,dc4作為二級目錄,這樣子的目錄數有4096,太多了。所以,取二個字元比較好。
這樣的話,上面的第2和第3個問題就解決了,根據檔名稱來得到md5,然後取4個字元,前面的2個字元作為一級目錄名稱,後面的2個字元作為二級目錄的名稱。伺服器上,使用一個專門的目錄來作為我們的儲存根目錄,然後下面建立這麼多子目錄,自然就很簡單了。
這些目錄可以在初始化的時候建立出來,而不用在儲存檔案的時候才建立。
也許你會問,一個目錄應該不夠吧,實際上很多的廉價機器一般都配置2塊硬碟,一塊是作業系統盤,一塊是資料盤。然後這個資料盤掛在一個目錄下面,以這個目錄作為我們的儲存根目錄就好了。這樣也可以很大程度上減少運維的難度。
現在就剩下最後一個問題了,就是上傳檔案時候,如何分配一個唯一的檔名稱,避免同以前的檔案產生覆蓋。
如果沒有變數作為輸入,很顯然,只能夠採用類似於計數器的方式,即一個counter,每次加一個檔案就增量。但這樣的方式會要求維護一個持久化的counter,這樣比較麻煩。最好不要有歷史狀態的紀錄。
string md5 ( string $str [, bool $raw_output = false ] )
Calculates the MD5 hash of str using the ? RSA Data Security, Inc. MD5 Message-Digest Algorithm, and returns that hash.
raw_output
If the optional raw_output is set to TRUE, then the md5 digest is instead returned in raw binary format with a length of 16.
Return Values ?
Returns the hash as a 32-character hexadecimal number.
為了儘可能地生成唯一的檔名稱,可以使用檔案長度(假如是100MB的話,相應的整型可能會是4個位元組,即不超過2^32, 即uint32_t,只要程式程式碼中檢查一下即可)。但是長度並不能夠保證唯一,為了填充儘可能有用的資訊,CRC32也是很重要的,這樣下載程式後,不用做額外的互動就可以知道檔案的內容是否正確。一旦發現有問題,立馬要報警,並且想辦法修復。這樣的話,上傳的時候也要注意帶上CRC32,以防止在網路傳輸和實際的硬碟儲存過程中出現問題(檔案的完整性至關重要)。再加上時間戳,即long型的64位,8個位元組。最後再加上計數器,因為這個計數器由storage提供,這樣的話,整個結構就是:len + crc32 + timestamp + uint32_t = 4 + 4 + 8 + 4 = 20個位元組,這樣生成的檔名就算做base64計算出來,也就不是什麼大問題了。而且,加上計數器,每秒內只要單機不上傳超過1萬的檔案 ,就都不是問題了。這個還是非常好解決的。
// TODO 如何避免檔案重複上傳? md5嗎? 還是檔案的計算可以避免此問題?這個資訊儲存在tracker伺服器中嗎?
回頭來看一下我們的問題:
1- 如何得到雜湊值?md5還是SHA1
2- 雜湊值得到後,如何構造雜湊桶
3- 根據檔名稱如何定位雜湊桶
根據上面分析的結果,我們看到,當上傳一個檔案的時候,我們會獲取到如下的資訊
1- 檔案的大小(透過協議中包的長度欄位可以知道,這樣的好處在於服務端實現的時候簡單,不用過於擔心網路緩衝區的問題)
2- CRC32(也是協議包中傳輸,以便確定網路傳輸是否出錯)
3- 時間戳(獲取伺服器的當前時間)
4- 計數器(伺服器自己維護)
根據上面的4個資料,組織成base64的編碼,然後生成此檔名稱。根據此檔名稱的唯一性,就不會出現被覆蓋的情況。同時,唯一性也使得接下來做md5運算後,得到的HASH值離散性得麼保證。得到了MD5的雜湊值後,取出最前面的2部分,就可以知道要定位到哪個目錄下面去。雜湊桶的構造是固定的,即二級00-ff的目錄情況。
原文:http://blog.csdn.net/wallwind/article/details/39891105
純C實現,支援Linux, FreeBSD等UNIX系統
類google FS, 不是通用的檔案系統,只能夠透過專有API訪問,目前提供了C,Java和PHP API
為網際網路應用量身定做,解決大容量檔案儲存問題,追求高效能和高擴充套件性
FastDFS可以看做是基於檔案的key-value儲存系統,稱為分散式檔案儲存服務更為合適
FastDFS提供的功能
upload 上傳檔案
download 下載檔案
delete 刪除檔案
心得:一個合適的(不需要選擇最複雜的,而是最滿足自己的需求。複雜的自己因為理解問題,導致無法掌控。當在出現一些突發性問題時,因為無法及時解決導致災難性的後果)檔案系統需要符合什麼樣的哲學,或者說應該使用什麼樣的設計理念?
一個好的分散式檔案系統最好提供Nginx的模組,因為對於網際網路應用來說,象檔案這種靜態資源,一般是透過HTTP的下載,此時透過容易擴充套件的Nginx來訪問Fastdfs,能夠讓檔案的上傳和下載變得特別簡單。另外,網站型應用在網際網路領域中的比例是非常高,因此PHP這種語言作為非常成熟,效能也完全能夠讓人滿意的網站開發語言,提供相應的擴充套件,也是非常重要的。所以在應用領域上,Fastdfs是非常合適的。
檔案系統天生是靜態資源,因此象可修改或者可追加的檔案看起來就沒有太大的意義了。檔案屬性也最好不要支援,因為可以透過副檔名和尺寸等屬性,透過附加在檔名稱上,來避免出現儲存屬性的資訊。另外,透過新增屬性支援,還不如用其他的東西, 例如redis等來支援,以避免讓此分散式檔案系統變得非常複雜。
之所以說FastDFS簡單,在於其架構中,只有兩種角色,一個是storage, 一個是tracker。但從實現上講,實際上有三個模組:tracker, storage和fastdfs client。fastdfs純粹是協議的解析,以及一些簡單的策略。關鍵還是在於tracker和storage。
在設計FastDFS時,除了如上的哲學外,很重要的就是上傳,下載,以及刪除。以及如何實現同步,以便實現真正的分散式,否則的話這樣和普通的單機檔案系統就沒有什麼區別了。
如果是我們自己來設計一下分散式的檔案系統,如果我們要上傳。那麼,必然要面臨著下面的一些選擇:
上傳到哪裡去?難道由客戶端來指定上傳的伺服器嗎?
只上傳一臺伺服器夠嗎?
上傳後是原樣儲存嗎?(chunk server比較危險,沒有把握不要去做)
對於多IDC如何考慮?
對於使用者來說,當需要上傳檔案的時候,他/她關心什麼?
1- 上傳的檔案必須真實地保留著,不能夠有任何的加工。雖然chunk server之類的看起來不錯,但是對於中小型組織來說,一旦因為一些技術性的bug,會導致chunk server破壞掉原來的檔案內容,風險比較大
2- 上傳成功後,能夠立馬返回檔名稱,並根據檔名稱馬上完整地下載。原始檔名稱我們不關心(如果需要關心,例如象論壇的附件,可以在資料庫中儲存這些資訊,而不應該交給DFS來處理)。這樣的好處在於DFS能夠更加靈活和高效,例如可以在檔名稱中加入很多的附屬資訊,例如圖片的尺寸等。
3- 上傳後的檔案不能夠是單點,一定要有備份,以防止檔案丟失
4- 對於一些熱點檔案,希望能夠做到保證儘可能快速地大量訪問
上面的需求其實是比較簡單的。首先讓我們回到最原始的時代,即磁碟來儲存檔案。在這個時代,當我們需要管理檔案的時候,通常我們都是在單機的磁碟上建立一個目錄,然後在此目錄下面存放檔案。因為使用者往往檔名稱是很隨意的,所以使用使用者指定的檔名稱可能會錯誤地覆蓋其他的檔案。因此,在處理的時候,絕對不能夠使用使用者指定的名稱,這是分析後得到的第一個結論。
如果使用者上傳檔案後,分配一個檔名稱(具體檔名稱的分配策略以後再考慮),那麼如果所有的檔案都儲存在同一個目錄下面,在做目錄項的遍歷時將非常麻煩。根據網上的資料,一般單目錄下的檔案個數一般限制不能夠超過3萬;同樣的,一個目錄下面的目錄數也最好不要超過這個數。但實際上,為了安全考慮,一般都不要儲存這麼多的內容。假定,一個目錄下面,儲存1000個檔案,每個檔案的平均大小為10KB,則單目錄下面可儲存的容量是10MB。這個容量太小了,所以我們要多個目錄,假定有1000個目錄,每個目錄儲存10MB,則可以儲存10GB的內容;這對於目前磁碟的容量來說,利用率還是不夠的。我們再想辦法,轉成兩級目錄,這樣的話,就是第一層目錄有1000個子目錄,每一級子目錄下面又有1000級的二級子目錄,每個二級子目錄,可以儲存10MB的內容,此時就可以儲存10T的內容,這基本上超過了目前單機磁碟的容量大小了。所以,使用二級子目錄的辦法,是平衡儲存效能和利用儲存容量的辦法。
這樣子的話,就回到了上面的問題,如果我們開始只做一個單機版的基於檔案系統的儲存服務,假如提供TCP的服務(不基於HTTP,因為HTTP的負載比太低)。很簡單,客戶端需要知道儲存伺服器的地址和埠。然後,指定要上傳的檔案內容;伺服器收到了檔案內容後,如何選擇要儲存在哪個目錄下呢?這個選擇要保證均衡性,即儘量保證檔案能夠均勻地分散在所有的目錄下。
負載均衡性很重要的就是雜湊,例如,在PHP中常用的md5,其返回一個32個字元,即16位元組的輸出,即128位。雜湊後要變成桶,才能夠分佈,自然就有了如下的問題:
1- 如何得到雜湊值?md5還是SHA1
2- 雜湊值得到後,如何構造雜湊桶
3- 根據檔名稱如何定位雜湊桶
首先來回答第3個問題,根據檔名稱如何定位雜湊桶。很簡單,此時我們只有一個檔名稱作為輸入,首先要計算雜湊值,只有一個辦法了,就是根據檔名稱來得到雜湊值。這個函式可以用整個檔名稱作為雜湊的輸入,也可以根據檔名稱的一部分來完成。結合上面說的兩級目錄,而且每級目錄不要超過1000.很簡單,如果用32位的字元輸出後,可以取出實現上來說,由於檔案上傳是防止唯一性,所以如果根據檔案內容來產生雜湊,則比較好的辦法就是擷取其中的4位,例如:
md5sum fdfs_storaged.pid
52edc4a5890adc59cec82cb60f8af691 fdfs_storaged.pid
上面,這個fdfs_storage.pid中,取出最前面的4個字元,即52和ed。這樣的話,假如52是一級目錄的名稱,ed是二級目錄的名稱。因為每一個字元有16個取值,所以第一級目錄就有16 * 16 = 256個。總共就有256 * 256 = 65526個目錄。如果每個目錄下面存放1000個檔案,每個檔案30KB,都可以有1966G,即2TB左右。這樣的話,足夠我們用好。如果用三個字元,即52e作為一級目錄,dc4作為二級目錄,這樣子的目錄數有4096,太多了。所以,取二個字元比較好。
這樣的話,上面的第2和第3個問題就解決了,根據檔名稱來得到md5,然後取4個字元,前面的2個字元作為一級目錄名稱,後面的2個字元作為二級目錄的名稱。伺服器上,使用一個專門的目錄來作為我們的儲存根目錄,然後下面建立這麼多子目錄,自然就很簡單了。
這些目錄可以在初始化的時候建立出來,而不用在儲存檔案的時候才建立。
也許你會問,一個目錄應該不夠吧,實際上很多的廉價機器一般都配置2塊硬碟,一塊是作業系統盤,一塊是資料盤。然後這個資料盤掛在一個目錄下面,以這個目錄作為我們的儲存根目錄就好了。這樣也可以很大程度上減少運維的難度。
現在就剩下最後一個問題了,就是上傳檔案時候,如何分配一個唯一的檔名稱,避免同以前的檔案產生覆蓋。
如果沒有變數作為輸入,很顯然,只能夠採用類似於計數器的方式,即一個counter,每次加一個檔案就增量。但這樣的方式會要求維護一個持久化的counter,這樣比較麻煩。最好不要有歷史狀態的紀錄。
string md5 ( string $str [, bool $raw_output = false ] )
Calculates the MD5 hash of str using the ? RSA Data Security, Inc. MD5 Message-Digest Algorithm, and returns that hash.
raw_output
If the optional raw_output is set to TRUE, then the md5 digest is instead returned in raw binary format with a length of 16.
Return Values ?
Returns the hash as a 32-character hexadecimal number.
為了儘可能地生成唯一的檔名稱,可以使用檔案長度(假如是100MB的話,相應的整型可能會是4個位元組,即不超過2^32, 即uint32_t,只要程式程式碼中檢查一下即可)。但是長度並不能夠保證唯一,為了填充儘可能有用的資訊,CRC32也是很重要的,這樣下載程式後,不用做額外的互動就可以知道檔案的內容是否正確。一旦發現有問題,立馬要報警,並且想辦法修復。這樣的話,上傳的時候也要注意帶上CRC32,以防止在網路傳輸和實際的硬碟儲存過程中出現問題(檔案的完整性至關重要)。再加上時間戳,即long型的64位,8個位元組。最後再加上計數器,因為這個計數器由storage提供,這樣的話,整個結構就是:len + crc32 + timestamp + uint32_t = 4 + 4 + 8 + 4 = 20個位元組,這樣生成的檔名就算做base64計算出來,也就不是什麼大問題了。而且,加上計數器,每秒內只要單機不上傳超過1萬的檔案 ,就都不是問題了。這個還是非常好解決的。
// TODO 如何避免檔案重複上傳? md5嗎? 還是檔案的計算可以避免此問題?這個資訊儲存在tracker伺服器中嗎?
FastDFS中給我們一個非常好的例子,請參考下面的程式碼:
// 參考FastDFS的檔名稱生成演算法
- /**
- 1 byte: store path index
- 8 bytes: file size
- FDFS_FILE_EXT_NAME_MAX_LEN bytes: file ext name, do not include dot (.)
- file size bytes: file content
- **/
- static int storage_upload_file(struct fast_task_info *pTask, bool bAppenderFile)
- {
- StorageClientInfo *pClientInfo;
- StorageFileContext *pFileContext;
- DisconnectCleanFunc clean_func;
- char *p;
- char filename[128];
- char file_ext_name[FDFS_FILE_PREFIX_MAX_LEN + 1];
- int64_t nInPackLen;
- int64_t file_offset;
- int64_t file_bytes;
- int crc32;
- int store_path_index;
- int result;
- int filename_len;
- pClientInfo = (StorageClientInfo *)pTask->arg;
- pFileContext = &(pClientInfo->file_context);
- nInPackLen = pClientInfo->total_length - sizeof(TrackerHeader);
- if (nInPackLen
- FDFS_FILE_EXT_NAME_MAX_LEN)
- {
- logError("file: "__FILE__", line: %d, " \
- "cmd=%d, client ip: %s, package size " \
- "%"PRId64" is not correct, " \
- "expect length >= %d", __LINE__, \
- STORAGE_PROTO_CMD_UPLOAD_FILE, \
- pTask->client_ip, nInPackLen, \
- 1 + FDFS_PROTO_PKG_LEN_SIZE + \
- FDFS_FILE_EXT_NAME_MAX_LEN);
- return EINVAL;
- }
- p = pTask->data + sizeof(TrackerHeader);
- store_path_index = *p++;
- if (store_path_index == -1)
- {
- if ((result=storage_get_storage_path_index( \
- &store_path_index)) != 0)
- {
- logError("file: "__FILE__", line: %d, " \
- "get_storage_path_index fail, " \
- "errno: %d, error info: %s", __LINE__, \
- result, STRERROR(result));
- return result;
- }
- }
- else if (store_path_index = \
- g_fdfs_store_paths.count)
- {
- logError("file: "__FILE__", line: %d, " \
- "client ip: %s, store_path_index: %d " \
- "is invalid", __LINE__, \
- pTask->client_ip, store_path_index);
- return EINVAL;
- }
- file_bytes = buff2long(p);
- p += FDFS_PROTO_PKG_LEN_SIZE;
- if (file_bytes
- (1 + FDFS_PROTO_PKG_LEN_SIZE + \
- FDFS_FILE_EXT_NAME_MAX_LEN))
- {
- logError("file: "__FILE__", line: %d, " \
- "client ip: %s, pkg length is not correct, " \
- "invalid file bytes: %"PRId64 \
- ", total body length: %"PRId64, \
- __LINE__, pTask->client_ip, file_bytes, nInPackLen);
- return EINVAL;
- }
- memcpy(file_ext_name, p, FDFS_FILE_EXT_NAME_MAX_LEN);
- *(file_ext_name + FDFS_FILE_EXT_NAME_MAX_LEN) = '\0';
- p += FDFS_FILE_EXT_NAME_MAX_LEN;
- if ((result=fdfs_validate_filename(file_ext_name)) != 0)
- {
- logError("file: "__FILE__", line: %d, " \
- "client ip: %s, file_ext_name: %s " \
- "is invalid!", __LINE__, \
- pTask->client_ip, file_ext_name);
- return result;
- }
- pFileContext->calc_crc32 = true;
- pFileContext->calc_file_hash = g_check_file_duplicate;
- pFileContext->extra_info.upload.start_time = g_current_time;
- strcpy(pFileContext->extra_info.upload.file_ext_name, file_ext_name);
- storage_format_ext_name(file_ext_name, \
- pFileContext->extra_info.upload.formatted_ext_name);
- pFileContext->extra_info.upload.trunk_info.path. \
- store_path_index = store_path_index;
- pFileContext->extra_info.upload.file_type = _FILE_TYPE_REGULAR;
- pFileContext->sync_flag = STORAGE_OP_TYPE_SOURCE_CREATE_FILE;
- pFileContext->timestamp2log = pFileContext->extra_info.upload.start_time;
- pFileContext->op = FDFS_STORAGE_FILE_OP_WRITE;
- if (bAppenderFile)
- {
- pFileContext->extra_info.upload.file_type |= \
- _FILE_TYPE_APPENDER;
- }
- else
- {
- if (g_if_use_trunk_file && trunk_check_size( \
- TRUNK_CALC_SIZE(file_bytes)))
- {
- pFileContext->extra_info.upload.file_type |= \
- _FILE_TYPE_TRUNK;
- }
- }
- if (pFileContext->extra_info.upload.file_type & _FILE_TYPE_TRUNK)
- {
- FDFSTrunkFullInfo *pTrunkInfo;
- pFileContext->extra_info.upload.if_sub_path_alloced = true;
- pTrunkInfo = &(pFileContext->extra_info.upload.trunk_info);
- if ((result=trunk_client_trunk_alloc_space( \
- TRUNK_CALC_SIZE(file_bytes), pTrunkInfo)) != 0)
- {
- return result;
- }
- clean_func = dio_trunk_write_finish_clean_up;
- file_offset = TRUNK_FILE_START_OFFSET((*pTrunkInfo));
- pFileContext->extra_info.upload.if_gen_filename = true;
- trunk_get_full_filename(pTrunkInfo, pFileContext->filename, \
- sizeof(pFileContext->filename));
- pFileContext->extra_info.upload.before_open_callback = \
- dio_check_trunk_file_when_upload;
- pFileContext->extra_info.upload.before_close_callback = \
- dio_write_chunk_header;
- pFileContext->open_flags = O_RDWR | g_extra_open_file_flags;
- }
- else
- {
- char reserved_space_str[32];
- if (!storage_check_reserved_space_path(g_path_space_list \
- [store_path_index].total_mb, g_path_space_list \
- [store_path_index].free_mb - (file_bytes/FDFS_ONE_MB), \
- g_avg_storage_reserved_mb))
- {
- logError("file: "__FILE__", line: %d, " \
- "no space to upload file, "
- "free space: %d MB is too small, file bytes: " \
- "%"PRId64", reserved space: %s", \
- __LINE__, g_path_space_list[store_path_index].\
- free_mb, file_bytes, \
- fdfs_storage_reserved_space_to_string_ex( \
- g_storage_reserved_space.flag, \
- g_avg_storage_reserved_mb, \
- g_path_space_list[store_path_index]. \
- total_mb, g_storage_reserved_space.rs.ratio,\
- reserved_space_str));
- return ENOSPC;
- }
- crc32 = rand();
- *filename = '\0';
- filename_len = 0;
- pFileContext->extra_info.upload.if_sub_path_alloced = false;
- if ((result=storage_get_filename(pClientInfo, \
- pFileContext->extra_info.upload.start_time, \
- file_bytes, crc32, pFileContext->extra_info.upload.\
- formatted_ext_name, filename, &filename_len, \
- pFileContext->filename)) != 0)
- {
- return result;
- }
- clean_func = dio_write_finish_clean_up;
- file_offset = 0;
- pFileContext->extra_info.upload.if_gen_filename = true;
- pFileContext->extra_info.upload.before_open_callback = NULL;
- pFileContext->extra_info.upload.before_close_callback = NULL;
- pFileContext->open_flags = O_WRONLY | O_CREAT | O_TRUNC \
- | g_extra_open_file_flags;
- }
- return storage_write_to_file(pTask, file_offset, file_bytes, \
- p - pTask->data, dio_write_file, \
- storage_upload_file_done_callback, \
- clean_func, store_path_index);
- }
- static int storage_get_filename(StorageClientInfo *pClientInfo, \
- const int start_time, const int64_t file_size, const int crc32, \
- const char *szFormattedExt, char *filename, \
- int *filename_len, char *full_filename)
- {
- int i;
- int result;
- int store_path_index;
- store_path_index = pClientInfo->file_context.extra_info.upload.
- trunk_info.path.store_path_index;
- for (i=0; i<10; i++)
- {
- if ((result=storage_gen_filename(pClientInfo, file_size, \
- crc32, szFormattedExt, FDFS_FILE_EXT_NAME_MAX_LEN+1, \
- start_time, filename, filename_len)) != 0)
- {
- return result;
- }
- sprintf(full_filename, "%s/data/%s", \
- g_fdfs_store_paths.paths[store_path_index], filename);
- if (!fileExists(full_filename))
- {
- break;
- }
- *full_filename = '\0';
- }
- if (*full_filename == '\0')
- {
- logError("file: "__FILE__", line: %d, " \
- "Can't generate uniq filename", __LINE__);
- *filename = '\0';
- *filename_len = 0;
- return ENOENT;
- }
- return 0;
- }
- static int storage_gen_filename(StorageClientInfo *pClientInfo, \
- const int64_t file_size, const int crc32, \
- const char *szFormattedExt, const int ext_name_len, \
- const time_t timestamp, char *filename, int *filename_len)
- {
- char buff[sizeof(int) * 5];
- char encoded[sizeof(int) * 8 + 1];
- int len;
- int64_t masked_file_size;
- FDFSTrunkFullInfo *pTrunkInfo;
- pTrunkInfo = &(pClientInfo->file_context.extra_info.upload.trunk_info);
- int2buff(htonl(g_server_id_in_filename), buff);
- int2buff(timestamp, buff+sizeof(int));
- if ((file_size >> 32) != 0)
- {
- masked_file_size = file_size;
- }
- else
- {
- COMBINE_RAND_FILE_SIZE(file_size, masked_file_size);
- }
- long2buff(masked_file_size, buff+sizeof(int)*2);
- int2buff(crc32, buff+sizeof(int)*4);
- base64_encode_ex(&g_fdfs_base64_context, buff, sizeof(int) * 5, encoded, \
- filename_len, false);
- if (!pClientInfo->file_context.extra_info.upload.if_sub_path_alloced)
- {
- int sub_path_high;
- int sub_path_low;
- storage_get_store_path(encoded, *filename_len, \
- &sub_path_high, &sub_path_low);
- pTrunkInfo->path.sub_path_high = sub_path_high;
- pTrunkInfo->path.sub_path_low = sub_path_low;
- pClientInfo->file_context.extra_info.upload. \
- if_sub_path_alloced = true;
- }
- len = sprintf(filename, FDFS_STORAGE_DATA_DIR_FORMAT"/" \
- FDFS_STORAGE_DATA_DIR_FORMAT"/", \
- pTrunkInfo->path.sub_path_high,
- pTrunkInfo->path.sub_path_low);
- memcpy(filename+len, encoded, *filename_len);
- memcpy(filename+len+(*filename_len), szFormattedExt, ext_name_len);
- *filename_len += len + ext_name_len;
- *(filename + (*filename_len)) = '\0';
- return 0;
- }
回頭來看一下我們的問題:
1- 如何得到雜湊值?md5還是SHA1
2- 雜湊值得到後,如何構造雜湊桶
3- 根據檔名稱如何定位雜湊桶
根據上面分析的結果,我們看到,當上傳一個檔案的時候,我們會獲取到如下的資訊
1- 檔案的大小(透過協議中包的長度欄位可以知道,這樣的好處在於服務端實現的時候簡單,不用過於擔心網路緩衝區的問題)
2- CRC32(也是協議包中傳輸,以便確定網路傳輸是否出錯)
3- 時間戳(獲取伺服器的當前時間)
4- 計數器(伺服器自己維護)
根據上面的4個資料,組織成base64的編碼,然後生成此檔名稱。根據此檔名稱的唯一性,就不會出現被覆蓋的情況。同時,唯一性也使得接下來做md5運算後,得到的HASH值離散性得麼保證。得到了MD5的雜湊值後,取出最前面的2部分,就可以知道要定位到哪個目錄下面去。雜湊桶的構造是固定的,即二級00-ff的目錄情況。
原文:http://blog.csdn.net/wallwind/article/details/39891105
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/29754888/viewspace-1430951/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- FastDFS安裝fastdfs-nginx-module和nginxASTNginx
- 分散式檔案儲存FastDFS(三)FastDFS配置分散式AST
- FastDFS NET示例AST
- 品味FastDFS~第三回 專案中的FastDFSAST
- fastdfs資料遷移以及fastdfs問題排查記錄AST
- fastdfs管理工具Go-fastdfs-web 安裝教程ASTGoWeb
- FastDFS 技術整理AST
- 三、 FastDFS功能原理AST
- 一、 Fastdfs介紹AST
- fastdfs詳細配置AST
- FastDFS簡易概括AST
- 品味FastDFS~目錄AST
- FastDFS-nginx外掛作為FastDFS客戶端訪問ASTNginx客戶端
- FastDFS的配置、部署與API使用解讀(1)Get Started with FastDFSASTAPI
- 分散式檔案儲存FastDFS(七)FastDFS配置檔案詳解分散式AST
- FastDFS的配置、部署與API使用解讀(7)Nginx的FastDFS模組ASTAPINginx
- FastDFS簡介,運用AST
- FastDFS入門小DemoAST
- linux安裝fastdfsLinuxAST
- 【FastDFS】SpringBoot整合FastDFS實戰,我只看這一篇!!ASTSpring Boot
- DFS 避坑手記 (docker 搭建 fastDFS 教程 / 在 CentOS 上搭建 FastDFS+nginx)DockerASTCentOSNginx
- Fastdfs資料遷移方案AST
- CentOS 7.0 之 FastDFS安裝CentOSAST
- FastDFS 叢集 安裝 配置AST
- FastDFS安裝、配置、部署(一)AST
- FastDFS的配置、部署與API使用解讀(6)FastDFS配置詳解之Storage配置ASTAPI
- FastDFS的配置、部署與API使用解讀(5)FastDFS配置詳解之Tracker配置ASTAPI
- FastDFS的配置、部署與API使用解讀(4)FastDFS配置詳解之Client配置ASTAPIclient
- Laravel 使用 FastDFS 上傳圖片LaravelAST
- spring-boot 中使用 FastDFSSpringbootAST
- FastDFS依賴無法匯入AST
- PHP安裝fastDFS擴充套件PHPAST套件
- FastDFS儲存伺服器部署AST伺服器
- FastDFS分散式檔案系統AST分散式
- FastDFS視訊教程-如何用FastDFS一步步搭建檔案管理系統AST
- 【FastDFS】小夥伴們說在CentOS 8伺服器上搭建FastDFS環境總報錯?ASTCentOS伺服器
- FastDFS的配置、部署與API使用解讀(8)FastDFS多種檔案上傳介面詳解ASTAPI
- FastDFS安裝及使用(開山篇)AST