服務器端文件分片合併的思考和實踐

筆者在項目中處理大文件上傳的需求,仿照七牛雲存儲的接口設計。然而,在服務器端文件合併時遇到了很大的問題:合併太慢。本文記錄了當時的思路和解決的方案 html

大文件的需求

文件上傳是個很常見的需求。儘管HTTP是基於TCP上層的協議,可是HTTP協議自己並不適合處理超大的請求體,文件上傳有很大的穩定性問題,若是中途斷開了,將前功盡棄。爲了改善用戶體驗或者緩解服務器壓力,一般會考慮將文件分紅小片,將小片一個個上傳,若是中途斷開了也能從某個失敗的小片開始繼續上傳。 前端

在前端的處理上,對於Web頁面,能夠採用plupload做爲上傳組件,該組件支持html五、flash、sl等多種上傳方式,所以,能夠提供較好的瀏覽器兼容性。七牛雲存儲的js-sdk就是基於這個組件開發的。不過本文的重點並非討論前端技術,關於前端就到此爲止。 html5

服務器端的策略

既然文件被分紅片上傳,那麼天然在服務器端須要將分片合併成原始的文件,那麼這裏存在兩種策略 linux

  • 邊傳邊合併:每上傳一個分片,就將分片合併到文件的後面
  • 傳完一塊兒合併:先將分片保存起來,客戶端發起一個合併請求時,再將分片合併成一個文件

邊傳邊合併

這種方式要注意: nginx

  • 因爲HTTP的無狀態性,這種方式須要客戶端和服務器端維持一個標識。服務器端根據標識,才能知道分片應該向哪一個目標文件Append
  • 若是客戶端中途中止上傳,那麼保存在服務器上的目標文件將成爲垃圾文件,文件即不能被刪除又沒有利用價值。由於服務端沒法知道文件究竟的完成狀態仍是正在進行狀態
  • 因爲分片直接合並進了文件,沒法管理分片

傳完一塊兒合併

七牛雲存儲就是使用的這種策略,具體的實現方式以下: git

  1. 每上傳一個分片,服務端將分片保存下來,並返回客戶端一個惟一標識ctx,這個標識ctx與當前這個分片關聯
  2. 客戶端應當妥善保存每一個分片的標識,以及它們的前後順序
  3. 服務端須要提供一個接口,客戶端用這個接口請求分片合併,在請求時候將標識按順序告知服務端,ctx0,ctx1,ctx2...
  4. 服務端根據標識的順序找到對應的分片,併合併成完成的文件

這種設計解決了客戶端中途中止上傳帶來的服務器端資源的浪費,由於分片都是正在進行態,能夠對時間很早的分片進行清理。並且分片被記錄了下來,容易對分片進行一些管理。 github

服務器端的分片合併

如今進入本文的重點。文件合併是IO操做,IO操做是最耗時的工做了。上文的第一種策略,有一個好處是文件的合併是在上傳的過程當中完成的,對於用戶來講幾乎感知不到文件Append時的延時。然而,第二種策略的文件合併倒是在一個時刻同時進行的。筆者測試過,即便是4-5個4MB的分片,也會使客戶端有明顯的延遲感。若是分片再多的話,延遲將更大,甚至請求超時,這是不能接受的。 web

可是筆者在七牛雲上的測試,合併的請求在七牛的服務器端幾乎沒有延時。爲此,筆者還發了一問:七牛雲mkfile如何實現將大量的文件chunk快速合併的,可是七牛的技術太「吝嗇」,一點也不透露。那該怎麼辦呢? 編程

並行處理?

因爲是將分片合併,那麼很容易會想到並行。相似歸併排序的思想,將合併任務分開,而後經過集羣服務器的協調完成合並。可是筆者對這方面是知之甚少,並且這種方案會使本來簡單的架構變的異常複雜,不敢採用這種方案。並且感受會有坑: segmentfault

  • 並行處理每每是異步的,如何處理好與web服務器的同步
  • 再怎麼分任務,合併終究是IO操做,IO操做老是要耗費時間的

文件系統底層處理?

仔細思考合併這個動做,其實是將多個文件在文件系統裏面複製了一次,而文件的內容並無任何的變化。若是可以在文件系統層面將分片直接鏈接起來話,合併文件僅僅是修改一些指針,速度將十分的快。不過文件系統各不相同,能不能實現還須要看。並且,因爲筆者使用nfs做爲數據存儲,nfs的文件讀寫徹底是經過接口提供的,接口也不提供底層的文件系統操做,因此彷佛是沒法實現。

爲何非要合併!

再思考下去,若是文件系統沒法作到將分片直接鏈接起來的的話,那麼從用戶接口層(HTTP)是否能作到呢?試想,經過HTTP的方式提供文件的訪問,若是HTTP服務器可以知道這個文件是由多個小文件按何種順序組成的,那麼就能夠按照順序將分片依次放在同一個HTTP流中返回,對用戶來講一次請求仍是獲得一個文件,好像文件是合併好的同樣,但實際上文件在文件系統並不存在。

這樣作須要單獨將分片的順序維護好,每次都要讀出分片的順序和位置,而後依次一個個寫入HTTP流中。可是高層的Web編程框架彷佛沒法支持這種作法。

巧用Nginx避免文件合併

筆者馬上想到了以前用過的Nginx模塊mod_zip,這個模塊可以將多個文件打包以zip流的方式返回。如今的需求其實跟這個模塊的工做幾乎差很少,甚至還要更簡單。苦苦尋覓網上的Nginx模塊,彷佛沒有找到筆者須要的,因而決定本身基於mod_zip開發一個。幸虧,以前用mod_zip的時候看過一些源碼。

目前這個模塊筆者命名爲mod_pieces,已經開發完成並在windows和linux兩個平臺下測試經過,惟一很差的是沒法支持HTTP Range,HTTP Range有點難弄,之後可能有時間再慢慢實現。代碼尚未整理,有時間放到Github上去共享。

mod_pieces的工做方式和原理和mod_zip很類似,有進一步需求的讀者能夠移步至:利用Nginx第三方模塊,實現附件打包下載

後續仍是要合併的

如今用戶那頭能夠"欺騙"成功了,可是若是系統自己須要對文件進一步處理,好比視頻的格式轉換,那麼仍是須要將文件合併起來的,不過這個時候就能夠用一個後臺的服務異步的慢慢作了,用戶不會感知。基於這些複雜的問題,筆者已經把文件上傳下載和處理做爲的一個全新的產品功能獨立出來,以支持主產品對文件的各類功能需求。

後記

本文沒有一張圖片,沒有一行代碼,有些不適應。文字雖短,可是這些東西都是通過筆者的實踐而且有感而發,但願有個總結,並可以帶來更多的交流。

相關文章
相關標籤/搜索