本篇文章是我上級老大所寫。 留在這里為了不弄丟。
FastDFS是一款開源的輕量級分布式文件系統
純C實現,支持Linux, FreeBSD等UNIX系統
類google FS, 不是通用的文件系統,僅僅可以通過專有API訪問,眼下提供了C,Java和PHP API
為互聯網應用量身定做,解決大容量文件存儲問題,追求高性能和高擴展性
FastDFS能夠看做是基于文件的key-value存儲系統,稱為分布式文件存儲服務更為合適
FastDFS提供的功能
upload 上傳文件
download 下載文件
delete 刪除文件
心得:一個合適的(不須要選擇最復雜的,而是最滿足自己的需求。復雜的自己由于理解問題,導致無法掌控。當在出現一些突發性問題時,由于無法及時解決導致災難性的后果)文件系統須要符合什么樣的哲學,或者說應該使用什么樣的設計理念?
一個好的分布式文件系統最好提供Nginx的模塊,由于對于互聯網應用來說,象文件這樣的靜態資源,通常是通過HTTP的下載,此時通過easy擴展的Nginx來訪問Fastdfs,可以讓文件的上傳和下載變得特別簡單。另外,站點型應用在互聯網領域中的比例是很高,因此PHP這樣的語言作為很成熟,性能也全然可以讓人愜意的站點開發語言,提供對應的擴展,也是很重要的。所以在應用領域上,Fastdfs是很合適的。
文件系統天生是靜態資源,因此象可改動或者可追加的文件看起來就沒有太大的意義了。文件屬性也最好不要支持,由于能夠通過文件擴展名和尺寸等屬性,通過附加在文件名上,來避免出現存儲屬性的信息。另外,通過加入屬性支持,還不如用其它的東西, 比如redis等來支持,以避免讓此分布式文件系統變得很復雜。
之所以說FastDFS簡單,在于其架構中,僅僅有兩種角色,一個是storage, 一個是tracker。但從實現上講,實際上有三個模塊:tracker, storage和fastdfs client。fastdfs純粹是協議的解析,以及一些簡單的策略。關鍵還是在于tracker和storage。
在設計FastDFS時,除了如上的哲學外,非常重要的就是上傳,下載,以及刪除。以及怎樣實現同步,以便實現真正的分布式,否則的話這樣和普通的單機文件系統就沒有什么差別了。
假設是我們自己來設計一下分布式的文件系統,假設我們要上傳。那么,必定要面臨著以下的一些選擇:
上傳到哪里去?難道由client來指定上傳的server嗎?
僅僅上傳一臺server夠嗎?
上傳后是原樣保存嗎?(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的負載比太低)。非常easy,client須要知道存儲server的地址和port。然后,指定要上傳的文件內容;server收到了文件內容后,怎樣選擇要存儲在哪個文件夾下呢?這個選擇要保證均衡性,即盡量保證文件可以均勻地分散在全部的文件夾下。
負載均衡性非常重要的就是哈希,比如,在PHP中經常使用的md5,其返回一個32個字符,即16字節的輸出,即128位。哈希后要變成桶,才可以分布,自然就有了例如以下的問題:
1- 怎樣得到哈希值?md5還是SHA1
2- 哈希值得到后,怎樣構造哈希桶
3- 依據文件名怎樣定位哈希桶
首先來回答第3個問題,依據文件名怎樣定位哈希桶。非常easy,此時我們僅僅有一個文件名作為輸入,首先要計算哈希值,僅僅有一個辦法了,就是依據文件名來得到哈希值。這個函數能夠用整個文件名作為哈希的輸入,也能夠依據文件名的一部分來完畢。結合上面說的兩級文件夾,并且每級文件夾不要超過1000.非常easy,假設用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個字符作為二級文件夾的名稱。server上,使用一個專門的文件夾來作為我們的存儲根文件夾,然后以下建立這么多子文件夾,自然就非常easy了。
這些文件夾能夠在初始化的時候創建出來,而不用在存儲文件的時候才建立。
或許你會問,一個文件夾應該不夠吧,實際上非常多的便宜機器一般都配置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,僅僅要程序代碼中檢查一下就可以)??墒情L度并不能夠保證唯一,為了填充盡可能實用的信息,CRC32也是非常重要的,這樣下載程序后,不用做額外的交互就能夠知道文件的內容是否正確。一旦發現有問題,立刻要報警,而且想辦法修復。這種話,上傳的時候也要注意帶上CRC32,以防止在網絡傳輸和實際的硬盤存儲過程中出現故障(文件的完整性至關重要)。再加上時間戳,即long型的64位,8個字節。最后再加上計數器,由于這個計數器由storage提供,這種話,整個結構就是:len + crc32 + timestamp + uint32_t = 4 + 4 + 8 + 4 = 20個字節,這樣生成的文件名稱就算做base64計算出來,也就不是什么大問題了。并且,加上計數器,每秒內僅僅要單機不上傳超過1萬的文件 ,就都不是問題了。這個還是很好解決的。
// TODO 怎樣避免文件反復上傳? md5嗎? 還是文件的計算能夠避免此問題?這個信息存儲在trackerserver中嗎?
回頭來看一下我們的問題:
1- 怎樣得到哈希值?md5還是SHA1
2- 哈希值得到后,怎樣構造哈希桶
3- 依據文件名怎樣定位哈希桶
依據上面分析的結果,我們看到,當上傳一個文件的時候,我們會獲取到例如以下的信息
1- 文件的大?。ㄍㄟ^協議中包的長度字段能夠知道,這種優點在于服務端實現的時候簡單,不用過于操心網絡緩沖區的問題)
2- CRC32(也是協議包中傳輸,以便確定網絡傳輸是否出錯)
3- 時間戳(獲取server的當前時間)
4- 計數器(server自己維護)
依據上面的4個數據,組織成base64的編碼,然后生成此文件名。依據此文件名的唯一性,就不會出現被覆蓋的情況。同一時候,唯一性也使得接下來做md5運算后,得到的HASH值離散性得么保證。得到了MD5的哈希值后,取出最前面的2部分,就能夠知道要定位到哪個文件夾以下去。哈希桶的構造是固定的,即二級00-ff的文件夾情況。
FastDFS是一款開源的輕量級分布式文件系統
純C實現,支持Linux, FreeBSD等UNIX系統
類google FS, 不是通用的文件系統,僅僅可以通過專有API訪問,眼下提供了C,Java和PHP API
為互聯網應用量身定做,解決大容量文件存儲問題,追求高性能和高擴展性
FastDFS能夠看做是基于文件的key-value存儲系統,稱為分布式文件存儲服務更為合適
FastDFS提供的功能
upload 上傳文件
download 下載文件
delete 刪除文件
心得:一個合適的(不須要選擇最復雜的,而是最滿足自己的需求。復雜的自己由于理解問題,導致無法掌控。當在出現一些突發性問題時,由于無法及時解決導致災難性的后果)文件系統須要符合什么樣的哲學,或者說應該使用什么樣的設計理念?
一個好的分布式文件系統最好提供Nginx的模塊,由于對于互聯網應用來說,象文件這樣的靜態資源,通常是通過HTTP的下載,此時通過easy擴展的Nginx來訪問Fastdfs,可以讓文件的上傳和下載變得特別簡單。另外,站點型應用在互聯網領域中的比例是很高,因此PHP這樣的語言作為很成熟,性能也全然可以讓人愜意的站點開發語言,提供對應的擴展,也是很重要的。所以在應用領域上,Fastdfs是很合適的。
文件系統天生是靜態資源,因此象可改動或者可追加的文件看起來就沒有太大的意義了。文件屬性也最好不要支持,由于能夠通過文件擴展名和尺寸等屬性,通過附加在文件名上,來避免出現存儲屬性的信息。另外,通過加入屬性支持,還不如用其它的東西, 比如redis等來支持,以避免讓此分布式文件系統變得很復雜。
之所以說FastDFS簡單,在于其架構中,僅僅有兩種角色,一個是storage, 一個是tracker。但從實現上講,實際上有三個模塊:tracker, storage和fastdfs client。fastdfs純粹是協議的解析,以及一些簡單的策略。關鍵還是在于tracker和storage。
在設計FastDFS時,除了如上的哲學外,非常重要的就是上傳,下載,以及刪除。以及怎樣實現同步,以便實現真正的分布式,否則的話這樣和普通的單機文件系統就沒有什么差別了。
假設是我們自己來設計一下分布式的文件系統,假設我們要上傳。那么,必定要面臨著以下的一些選擇:
上傳到哪里去?難道由client來指定上傳的server嗎?
僅僅上傳一臺server夠嗎?
上傳后是原樣保存嗎?(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的負載比太低)。非常easy,client須要知道存儲server的地址和port。然后,指定要上傳的文件內容;server收到了文件內容后,怎樣選擇要存儲在哪個文件夾下呢?這個選擇要保證均衡性,即盡量保證文件可以均勻地分散在全部的文件夾下。
負載均衡性非常重要的就是哈希,比如,在PHP中經常使用的md5,其返回一個32個字符,即16字節的輸出,即128位。哈希后要變成桶,才可以分布,自然就有了例如以下的問題:
1- 怎樣得到哈希值?md5還是SHA1
2- 哈希值得到后,怎樣構造哈希桶
3- 依據文件名怎樣定位哈希桶
首先來回答第3個問題,依據文件名怎樣定位哈希桶。非常easy,此時我們僅僅有一個文件名作為輸入,首先要計算哈希值,僅僅有一個辦法了,就是依據文件名來得到哈希值。這個函數能夠用整個文件名作為哈希的輸入,也能夠依據文件名的一部分來完畢。結合上面說的兩級文件夾,并且每級文件夾不要超過1000.非常easy,假設用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個字符作為二級文件夾的名稱。server上,使用一個專門的文件夾來作為我們的存儲根文件夾,然后以下建立這么多子文件夾,自然就非常easy了。
這些文件夾能夠在初始化的時候創建出來,而不用在存儲文件的時候才建立。
或許你會問,一個文件夾應該不夠吧,實際上非常多的便宜機器一般都配置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,僅僅要程序代碼中檢查一下就可以)??墒情L度并不能夠保證唯一,為了填充盡可能實用的信息,CRC32也是非常重要的,這樣下載程序后,不用做額外的交互就能夠知道文件的內容是否正確。一旦發現有問題,立刻要報警,而且想辦法修復。這種話,上傳的時候也要注意帶上CRC32,以防止在網絡傳輸和實際的硬盤存儲過程中出現故障(文件的完整性至關重要)。再加上時間戳,即long型的64位,8個字節。最后再加上計數器,由于這個計數器由storage提供,這種話,整個結構就是:len + crc32 + timestamp + uint32_t = 4 + 4 + 8 + 4 = 20個字節,這樣生成的文件名稱就算做base64計算出來,也就不是什么大問題了。并且,加上計數器,每秒內僅僅要單機不上傳超過1萬的文件 ,就都不是問題了。這個還是很好解決的。
// TODO 怎樣避免文件反復上傳? md5嗎? 還是文件的計算能夠避免此問題?這個信息存儲在trackerserver中嗎?
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 < 1 + FDFS_PROTO_PKG_LEN_SIZE + 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 < 0 || 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 < 0 || file_bytes != nInPackLen - \ (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- 時間戳(獲取server的當前時間)
4- 計數器(server自己維護)
依據上面的4個數據,組織成base64的編碼,然后生成此文件名。依據此文件名的唯一性,就不會出現被覆蓋的情況。同一時候,唯一性也使得接下來做md5運算后,得到的HASH值離散性得么保證。得到了MD5的哈希值后,取出最前面的2部分,就能夠知道要定位到哪個文件夾以下去。哈希桶的構造是固定的,即二級00-ff的文件夾情況。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
