唐文軍,隆承志
(1.華南理工大學信息網絡工程研究中心,廣東廣州 510641;2.華南理工大學計算機科學與工程學院,廣東 廣州 510006)
文件上傳是在線學習平臺中一個使用非常頻繁的功能,教師提交課件、學生交作業等,都需要用到文件上傳的功能。文件上傳功能大多采用單進程模式實現,這種上傳方式實現起來比較簡單,但在實際應用中存在幾個方面的問題[1-5]:1)在服務器和網絡帶寬資源不夠的情況下,會因為內存溢出或網絡阻塞導致上傳失敗;2)在上傳較大的文件時會占用過多資源,導致瀏覽器甚至電腦沒有響應,影響用戶體驗;3)上傳失敗后,之前上傳的字節流將全部拋棄,必須全部重新上傳,極大地影響上傳效率。針對文件上傳的以上痛點問題,提出了一種基于分片的文件斷點續傳方法,對需上傳的文件按照一定的規則進行分片,并結合多線程,提高上傳效率,同時采用斷點續傳的方式,解決上傳失敗后要全部重新上傳的問題。
文件上傳的方式一般分為簡單文件上傳和文件斷點續傳兩種[6]。簡單文件上傳是指用戶通過客戶端(如瀏覽器)將文件傳輸到服務器進行保存,在傳輸過程中,服務器不保存文件上傳的狀態,如果傳輸過程中出現任何問題造成文件傳輸中止,文件需要重新傳輸。文件斷點續傳是指在使用文件斷點續傳服務時,服務端實時保存文件的上傳狀態,當用戶主動中止上傳或者其他原因導致文件上傳中止后再次上傳該文件時,系統無需從頭開始上傳,會從上一次上傳中止處開始傳輸數據。
簡單文件上傳的特點是流程簡單、容易實現,前后端只需很少的交互操作就能夠完成文件的傳輸和保存等工作。但簡單文件上傳的缺點也比較明顯,當用戶上傳較大的文件,所需時間比較長時,上傳過程中出現任何問題,都可能導致上傳失敗,當用戶重新上傳時,仍需要從文件頭部開始上傳,不僅會造成網絡帶寬的浪費,且嚴重影響用戶體驗[7]。
文件斷點續傳的基本原理就是在文件上傳過程中,定時或者實時保存文件的上傳狀態[8-9]。傳輸過程中保存文件上傳狀態有兩種方法:第一種是記錄文件已上傳的字節數,文件上傳被中止時,服務器端可以通過讀取臨時目錄中上傳文件的字節數來獲取上傳的進度,當用戶再次發起上傳時,服務器端可以從客戶端的請求中讀取文件名、文件長度和文件MD5 值等參數[10-11],獲取文件的上傳進度,并通知客戶端從服務端返回的字節處開始文件傳輸,直到文件傳輸完畢;第二種是將大文件按一定規則分片,逐個將文件分片傳輸到服務端進行保存,當文件傳輸被中止時,服務端只損失正在上傳的分片,已上傳的分片不受影響,當用戶再次上傳時,服務器端從客戶端請求中獲取上傳的文件信息,根據文件信息查詢該文件已上傳的分片記錄并返回給客戶端,客戶端根據服務器端的信息解析分片記錄并上傳的未上傳的文件分片[12]。
通過對于基于字節數的斷點續傳算法和基于分片的斷點續傳算法的原理介紹可以發現基于字節數的斷點續傳算法的優點在于原理簡單、便于實現,缺點是功能擴展性差。基于分片的斷點續傳算法在繼承了基于字節數的斷點續傳算法優點的同時,還能根據實際應用需求進行功能擴展,如為了發揮服務器多核CPU 的優勢[13],可以在基于分片上傳的基礎上采用多線程上傳模式,啟動多個線程同時上傳多個分片。同時,基于分片的斷點續傳算法容錯性較強[14],當用戶在首次上傳和二次上傳期間,對文件做過修改,使用基于字節數的斷點續傳算法方法,服務端無法判斷文件是否經過修改,如果是對已上傳部分的內容做的修改,那么文件是沒有意義的,用戶無法接受。如果使用基于分片的斷點續傳算法,在用戶修改過文件之后,二次上傳時,服務端可以檢測每個已上傳分片的MD5,如果發現兩次上傳的分片MD5 值不一致,則重傳該分片即可[15-16],保證了文件傳輸的正確性和可靠性。
基于分片的斷點續傳方法的關鍵問題是確定分片的大小。分片的大小對文件傳輸和存儲的的效率影響很大。文件分片過小會導致分片過多,增加上傳過程中的分片校驗負擔和傳輸完成后分片重組的開銷;但如果分片過大,分片上傳的時間較長,且在上傳過程中出現任何問題造成該分片上傳不成功,則需要重傳該分片,失去了斷點續傳的優勢。
分片的大小一般通過實驗獲取,我校學習平臺的云存儲系統所用的Linux 系統磁盤采用ext4 格式,其磁盤塊(block)的大小是4 kB,通過對比測試,分片大小的設置為4 kB 的整數倍時,存儲效率相對較高,其主要原因是無論傳輸過程中的分片寫入,還是上傳完后的分片重組,每一次I/O 都是寫入和讀取磁盤中整數倍的Block 塊,能有效提高I/O 的性能,對學習平臺中2 000 余份學生作業和近200 份教師課件文件的大小和頻率進行統計,如圖1 所示。

圖1 學習平臺上傳文件大小分布圖
在學習平臺中,10~100 MB 之間的文件占總上傳數的一半左右,所以選取的分片大小為4 MB,即每一個完整的分片都能夠寫滿Linux 系統的1 024 個節點,在提升磁盤使用率和IO 性能的前提下,不會因為分片過多造成分片合并時消耗過多資源,在具體實現時,為了提高性能,8 MB 以下的文件不做分片處理。
文件分片總數N的計算公式如下:
其中,fileSize 是文件大小,segmentSize 是預設的分片大小。分片上傳狀態用一個bit 數組保存,其中每一位的值代表該序號的片段的上傳狀態,0 表示未上傳,1 表示已上傳。如數組{0,1,1,0,0,1,0,1,1}表示正在上傳的文件分為9 個片段,其中第2、3、6、8、9 個片段已上傳到服務器,第1、4、5、7 個片段還未上傳成功。
服務器端負責更新并保存上傳狀態數組,直到文件傳輸完畢,客戶端按分片順序計算出分片的起止位置,并從文件中讀取這一段文件內容上傳到服務器,如傳輸因故中止重新上傳時,從服務器獲取上傳狀態記錄,并解析出未上傳分片序號。每個文件分片的起止位置計算公式如下:
其中,Sx和Ex分別是第x個片段的起始和結束位置。
基于分片的斷點續傳方法前端實現使用百度WebFE(FEX)團隊開發的一個基于HTML5 的開源JS項目WebUploader。WebUploader 封裝了文件分片、多線程并發、文件MD5 生成和校驗、上傳進度顯示等基礎功能,前端開發只需要在初始化WebUploader組件時將相關參數注入即可,基于分片的斷點續傳方法的前端關鍵代碼如圖2 所示。

圖2 前端實現關鍵代碼
create 方法負責使用傳入的參數初始化WebUploader 組件,傳入參數是JSON 格式文本,其中:
server:指定服務器端對應處理文件上傳的接口;
chuncked:設置前端是否采用分片上傳方式;
chunckSize:設置文件分片大小,根據上一節的分析,文件分片大小設置為4 MB;
treads:設置文件上傳時同時啟動的并發線程數,考慮到性能問題,該參數設置為3,即同時啟動三個線程。
為實現文件秒傳功能,前端必須計算并上傳文件的MD5 值到服務器端,用戶在重復上傳文件時,服務器端會根據上傳的文件MD5 值進行判斷,如果已上傳文件中存在同樣MD5 值,則表示該文件已經上傳過,可以直接提示用戶上傳成功。通過測試發現,大文件的MD5 值計算非常消耗資源,為提高計算效率,在fileQueued 函數中,只將待上傳的文件名和文件尺寸組合hash,作為該文件的MD5 值。
服務器端接口采用Java SpringBoot 框架實現,Java 提供了專門處理文件分片隨機讀取的類,即RandomAccessFile(隨機訪問文件)類。該類是Java中功能最為豐富的文件訪問類,提供了眾多的文件訪問方法。RandomAccessFile 類支持對文件的隨機訪問方式,可以跳轉到文件的任意位置處讀寫數據。在訪問一個文件時,不必把文件從頭讀到尾,而是可以像訪問數據庫一樣隨意地訪問一個文件的某個部分。
基于分片的斷點續傳方法服務器端關鍵代碼如圖3 所示。

圖3 服務器端關鍵代碼
其中,checkFileMd5 方法用于檢驗當前請求上傳的文件在服務器上是否存在,并返回其狀態;uploadFile 方法負責將上傳的分片保存在臨時目錄;mergeFile 方法負責將文件分片合并。Controller層維持一個已上傳分片列表,記錄已經上傳的分片信息,并依次循環調用這三個接口,直至文件上傳完成。
服務器端處理文件分片上傳的邏輯如下:
1)服務器端收到上傳請求后,獲取文件MD5值,調用接口checkFileMd5,查詢該MD5 是否已經存在,如果存在,且文件傳輸狀態為已完成,則提示用戶文件已上傳,即實現了文件秒傳功能;
2)如果MD5 值存在,但文件傳輸狀態為未完成,則獲取已上傳分片列表,返回所有還沒上傳分塊的編號,前端進行條件篩算出哪些沒上傳的分塊,然后進行上傳,即實現斷點續傳功能;
3)如果MD5 值不存在,表示該文件是第一次上傳,則創建臨時目錄存放分片信息,每一次接收到分片時,判斷當前分片所屬的文件的所有分片是否已經傳輸完畢,如果已傳輸完成,則開始合并文件并轉移完整文件到目的目錄,并刪除臨時目錄,通知用戶文件傳輸成功。
將簡單文件上傳和基于分片的斷點續傳方法進行對比測試。測試網絡環境是校內千兆網絡,正常上行帶寬為8~10 MB/s,測試服務器是虛擬化服務器,CPU32核,內存為32 GB,硬盤空間為1 T,操作系統為CenterOS7,客戶端是個人電腦,CPU4核,Intel Core i7,內存為16 GB,使用Chrome 瀏覽器進行上傳測試。
考慮到學習平臺的實際運行情況,該次測試使用簡單文件上傳方法、不同分片大小和不同線程的斷點續傳方法分別對四個不同大小的文件(4.1、53、102、511 MB)進行上傳,對比其上傳效率,每個組合分別上傳三次,取其平均上傳時間(s),測試結果如表1 所示。

表1 測試結果
通過文件上傳測試可知,文件秒傳和斷點續傳的功能都已實現,從表1 可知,由于分片上傳相比簡單文件多了前端分片、MD5 值計算和服務器端合并分片等步驟的開銷,單線程模式下,不論分片大小,各種尺寸的文件傳輸效率都低于簡單文件上傳模式,在多線程模式下,分片尺寸4 MB 的上傳效率要顯著高于分片尺寸2 MB,但分片尺寸的繼續增長對上傳效率的提高影響不大。
文中提出基于分片的斷點續傳方法,前端使用百度提供的開源項目WebUpload 組件,服務器端使用SpringBoot框架,利用Java提供的RandomAccessFile類實現文件的讀取和合并,該方法已在華南理工大學特色專業在線學習平臺上部署并運行,有效地提高了文件上傳的效率和可靠性。