筆者在項目中處理大文件上傳的需求,仿照七牛雲存儲的接口設計。然而,在服務器端文件合併時遇到了很大的問題:合併太慢。本文記錄了當時的思路和解決的方案 html
文件上傳是個很常見的需求。儘管HTTP是基於TCP上層的協議,可是HTTP協議自己並不適合處理超大的請求體,文件上傳有很大的穩定性問題,若是中途斷開了,將前功盡棄。爲了改善用戶體驗或者緩解服務器壓力,一般會考慮將文件分紅小片,將小片一個個上傳,若是中途斷開了也能從某個失敗的小片開始繼續上傳。 前端
在前端的處理上,對於Web頁面,能夠採用plupload做爲上傳組件,該組件支持html五、flash、sl等多種上傳方式,所以,能夠提供較好的瀏覽器兼容性。七牛雲存儲的js-sdk就是基於這個組件開發的。不過本文的重點並非討論前端技術,關於前端就到此爲止。 html5
既然文件被分紅片上傳,那麼天然在服務器端須要將分片合併成原始的文件,那麼這裏存在兩種策略 linux
這種方式要注意: nginx
七牛雲存儲就是使用的這種策略,具體的實現方式以下: git
這種設計解決了客戶端中途中止上傳帶來的服務器端資源的浪費,由於分片都是正在進行態,能夠對時間很早的分片進行清理。並且分片被記錄了下來,容易對分片進行一些管理。 github
如今進入本文的重點。文件合併是IO操做,IO操做是最耗時的工做了。上文的第一種策略,有一個好處是文件的合併是在上傳的過程當中完成的,對於用戶來講幾乎感知不到文件Append時的延時。然而,第二種策略的文件合併倒是在一個時刻同時進行的。筆者測試過,即便是4-5個4MB的分片,也會使客戶端有明顯的延遲感。若是分片再多的話,延遲將更大,甚至請求超時,這是不能接受的。 web
可是筆者在七牛雲上的測試,合併的請求在七牛的服務器端幾乎沒有延時。爲此,筆者還發了一問:七牛雲mkfile如何實現將大量的文件chunk快速合併的,可是七牛的技術太「吝嗇」,一點也不透露。那該怎麼辦呢? 編程
因爲是將分片合併,那麼很容易會想到並行。相似歸併排序的思想,將合併任務分開,而後經過集羣服務器的協調完成合並。可是筆者對這方面是知之甚少,並且這種方案會使本來簡單的架構變的異常複雜,不敢採用這種方案。並且感受會有坑: segmentfault
仔細思考合併這個動做,其實是將多個文件在文件系統裏面複製了一次,而文件的內容並無任何的變化。若是可以在文件系統層面將分片直接鏈接起來話,合併文件僅僅是修改一些指針,速度將十分的快。不過文件系統各不相同,能不能實現還須要看。並且,因爲筆者使用nfs做爲數據存儲,nfs的文件讀寫徹底是經過接口提供的,接口也不提供底層的文件系統操做,因此彷佛是沒法實現。
再思考下去,若是文件系統沒法作到將分片直接鏈接起來的的話,那麼從用戶接口層(HTTP)是否能作到呢?試想,經過HTTP的方式提供文件的訪問,若是HTTP服務器可以知道這個文件是由多個小文件按何種順序組成的,那麼就能夠按照順序將分片依次放在同一個HTTP流中返回,對用戶來講一次請求仍是獲得一個文件,好像文件是合併好的同樣,但實際上文件在文件系統並不存在。
這樣作須要單獨將分片的順序維護好,每次都要讀出分片的順序和位置,而後依次一個個寫入HTTP流中。可是高層的Web編程框架彷佛沒法支持這種作法。
筆者馬上想到了以前用過的Nginx模塊mod_zip,這個模塊可以將多個文件打包以zip流的方式返回。如今的需求其實跟這個模塊的工做幾乎差很少,甚至還要更簡單。苦苦尋覓網上的Nginx模塊,彷佛沒有找到筆者須要的,因而決定本身基於mod_zip開發一個。幸虧,以前用mod_zip的時候看過一些源碼。
目前這個模塊筆者命名爲mod_pieces,已經開發完成並在windows和linux兩個平臺下測試經過,惟一很差的是沒法支持HTTP Range,HTTP Range有點難弄,之後可能有時間再慢慢實現。代碼尚未整理,有時間放到Github上去共享。
mod_pieces的工做方式和原理和mod_zip很類似,有進一步需求的讀者能夠移步至:利用Nginx第三方模塊,實現附件打包下載
如今用戶那頭能夠"欺騙"成功了,可是若是系統自己須要對文件進一步處理,好比視頻的格式轉換,那麼仍是須要將文件合併起來的,不過這個時候就能夠用一個後臺的服務異步的慢慢作了,用戶不會感知。基於這些複雜的問題,筆者已經把文件上傳下載和處理做爲的一個全新的產品功能獨立出來,以支持主產品對文件的各類功能需求。
本文沒有一張圖片,沒有一行代碼,有些不適應。文字雖短,可是這些東西都是通過筆者的實踐而且有感而發,但願有個總結,並可以帶來更多的交流。