史若曦,馬俊才,田園靜,張薦轅
1(中國(guó)科學(xué)院 計(jì)算機(jī)網(wǎng)絡(luò)信息中心,北京 100190)
2(中國(guó)科學(xué)院 微生物研究所,北京 100101)
3(中國(guó)科學(xué)院大學(xué),北京 100049)
隨著微生物數(shù)據(jù)的快速增長(zhǎng),研究者越來(lái)越重視微生物大數(shù)據(jù)的高效管理和分析.國(guó)家科學(xué)微生物數(shù)據(jù)中心建立了以云計(jì)算為基礎(chǔ)的生物信息分析的數(shù)據(jù)平臺(tái)[1],用戶可以登陸網(wǎng)站,根據(jù)需求在線使用生物信息分析工具.由于網(wǎng)站面向所有用戶開(kāi)放,一般生物文件較大,HTTP 協(xié)議下云服務(wù)器端對(duì)文件接收大小也有一定的限制,用傳統(tǒng)的插件技術(shù)、表單直傳等方法進(jìn)行文件上傳時(shí)效率過(guò)低,網(wǎng)絡(luò)波動(dòng)或?yàn)g覽器異常等突發(fā)情況導(dǎo)致上傳中斷時(shí)也需要對(duì)整體文件進(jìn)行重復(fù)上傳[2].所以,本文設(shè)計(jì)了一個(gè)與分片傳輸技術(shù)相結(jié)合的多線程傳輸方案,保證用戶數(shù)據(jù)可以安全、快速的傳遞至云平臺(tái).
傳統(tǒng)的基于Web 文件上傳的方法有Form 表單上傳,該方法要求表單里包含一個(gè)類型為file的輸入文件框,與JavaScript 等技術(shù)結(jié)合,實(shí)現(xiàn)文件的上傳操作.插件技術(shù)是另一種實(shí)現(xiàn)Web 端文件上傳的方法,主要利用ActiveX、Huploader 等一系列插件實(shí)現(xiàn)文件上傳,但是該技術(shù)容易因?yàn)g覽器的設(shè)置導(dǎo)致插件運(yùn)行失敗,因此只適合在內(nèi)網(wǎng)此類安全的內(nèi)部環(huán)境使用[3].
除此之外,文獻(xiàn)[4]提出了一種斷點(diǎn)續(xù)傳方案,大文件在Web 端分片后,通過(guò)計(jì)算出每片的MD5 值來(lái)做為其唯一標(biāo)識(shí)符.當(dāng)網(wǎng)絡(luò)異常發(fā)生傳輸中斷時(shí),由唯一標(biāo)識(shí)符來(lái)確定斷點(diǎn)續(xù)傳從分片相應(yīng)部分開(kāi)始.文獻(xiàn)[5]提出了一種多線程控制的方式.該方法在傳輸多個(gè)不同文件時(shí),為每個(gè)文件開(kāi)啟一個(gè)線程來(lái)控制內(nèi)容文件和配置文件,雖然在傳輸多個(gè)文件時(shí)有明顯優(yōu)點(diǎn),但針對(duì)單個(gè)文件時(shí)效率不高.文獻(xiàn)[6,7]采用雙線程分別記錄文件內(nèi)容和內(nèi)容偏移量的方法,該方法在實(shí)現(xiàn)斷點(diǎn)續(xù)傳的情況下?tīng)奚藗鬏斝?文獻(xiàn)[8]在線程選擇上采取逐步增加的方式,雖然提高了傳輸效率,但是逐步增加試探,在網(wǎng)絡(luò)承載率大的情況下仍然有一定程度的資源浪費(fèi).
盡管上述技術(shù)已經(jīng)對(duì)斷點(diǎn)續(xù)傳、重復(fù)文件上傳等問(wèn)題進(jìn)行了一定程度的解決,但針對(duì)如何提升大文件傳輸速率這一問(wèn)題仍存在不足.部分技術(shù)為了實(shí)現(xiàn)斷點(diǎn)續(xù)傳,采用多線程來(lái)記錄文件傳輸時(shí)產(chǎn)生的相關(guān)配置文件,造成了帶寬資源的浪費(fèi).一些技術(shù)雖然提出利用多線程來(lái)實(shí)現(xiàn)文件并發(fā)傳輸,但并沒(méi)有具體提出如何選擇并發(fā)線程數(shù)來(lái)保證盡可能高效的利用網(wǎng)絡(luò)帶寬.
因此本文提出了一種根據(jù)帶寬時(shí)延積(BDP)來(lái)選擇并發(fā)線程數(shù)的文件傳輸方案,與分片上傳、斷點(diǎn)續(xù)傳技術(shù)相結(jié)合,滿足生物數(shù)據(jù)分析平臺(tái)用戶的需要,實(shí)現(xiàn)生物數(shù)據(jù)的穩(wěn)定上傳.
在實(shí)際的傳輸過(guò)程中,數(shù)據(jù)每份發(fā)送后無(wú)法立即得到確認(rèn),這些在信道中傳輸?shù)€未被確認(rèn)的數(shù)據(jù)量通過(guò)用帶寬時(shí)延積(BDP)來(lái)表示,它是衡量網(wǎng)絡(luò)鏈路能力和承載能力的關(guān)鍵指標(biāo).窗口機(jī)制就是防止數(shù)據(jù)量超過(guò)接收端確認(rèn)處理能力的一種措施,它通過(guò)限定窗口大小的方式來(lái)進(jìn)行TCP的流量控制和擁塞控制.滑動(dòng)窗口和固定窗口是常用的兩種窗口機(jī)制,但是由于TCP 報(bào)文頭中窗口字段大小只有16 位,無(wú)論哪種方式,TCP 窗口最大值都為64 KB.在理想的寬帶利用率下,帶寬時(shí)延積應(yīng)與TCP 窗口大小一致.在1000 Mb/s的網(wǎng)絡(luò)帶寬下,只有往返時(shí)延在小于0.448 ms 時(shí),BDP才會(huì)小于64 KB,此時(shí)能夠有效利用帶寬.但是在實(shí)際的網(wǎng)絡(luò)情況下,要想達(dá)到這么小的往返時(shí)延幾乎是不可能的.因此,對(duì)于并發(fā)TCP 連接,通過(guò)同時(shí)建立多條連接,并發(fā)n條連接就相當(dāng)于將窗口大小擴(kuò)大了n倍,從而能有效的提高傳輸速率[9].
根據(jù)前文對(duì)TCP 窗口機(jī)制與帶寬時(shí)延積的理論分析可知,為了實(shí)現(xiàn)高效的利用網(wǎng)絡(luò)帶寬,減少數(shù)據(jù)等待確認(rèn)時(shí)的資源浪費(fèi),TCP 發(fā)送窗口大小應(yīng)與帶寬時(shí)延積一致.然而受到報(bào)文頭字段大小限制,要想在生物數(shù)據(jù)分析平臺(tái)用戶的網(wǎng)絡(luò)條件下提高文件傳輸效率,應(yīng)采用多線程的方法建立多條TCP 連接,擴(kuò)大傳輸窗口.
對(duì)于并發(fā)TCP 來(lái)說(shuō),理想的并發(fā)數(shù)N可以用式(1)計(jì)算.
其中,BDP為帶寬時(shí)延積,由網(wǎng)絡(luò)帶寬與傳輸時(shí)延(RTT)共同確定,其計(jì)算方式為BDP=帶寬 ×RTT,MaxWindowSize為最大窗口數(shù).
本文綜合分片傳輸和斷點(diǎn)續(xù)傳等技術(shù),提出一種根據(jù)用戶網(wǎng)絡(luò)帶寬時(shí)延積選擇最佳并發(fā)線程數(shù)的傳輸方案.該方案采用部分功能函數(shù),實(shí)現(xiàn)Web 端的文件分片傳輸,并根據(jù)MD5的計(jì)算原理,采用分片計(jì)算后整合的方式,最終得到原文件的MD5 值.在解決相同文件上傳時(shí),使用MD5 校驗(yàn)來(lái)檢測(cè)服務(wù)器端是否已經(jīng)存儲(chǔ)該文件[10],提高傳輸效率.本文的關(guān)鍵在于設(shè)計(jì)了一個(gè)多線程創(chuàng)建方式,在用戶打開(kāi)網(wǎng)站時(shí)獲得此時(shí)的網(wǎng)絡(luò)狀態(tài),根據(jù)此參數(shù)得到當(dāng)前網(wǎng)絡(luò)狀態(tài)下用戶進(jìn)行上傳操作時(shí)可使用的最大線程數(shù),進(jìn)而提升了文件的上傳效率.文件發(fā)送流程如圖1所示.
圖1 客戶端文件發(fā)送流程
線程數(shù)的選擇主要依據(jù)網(wǎng)絡(luò)帶寬和傳輸時(shí)延,該參數(shù)可以在用戶與服務(wù)器建立連接時(shí)測(cè)定,通過(guò)BpsDataPerInterval 方法,獲取連接服務(wù)器時(shí)的帶寬傳輸時(shí)延RTT,根據(jù)上文公式,確定并發(fā)數(shù)poolSize=(RTT×帶寬)/(64×1024),存儲(chǔ)在poolSize函數(shù)中,在進(jìn)行上傳時(shí),啟動(dòng)與服務(wù)器的線程連接.
因?yàn)榉?wù)器端會(huì)限制每次上傳文件的大小,所以需要在前端指定每片文件的值.如果分片較小,則分片數(shù)大,會(huì)導(dǎo)致多次建立傳輸請(qǐng)求,增大開(kāi)銷;如果分片較大,則會(huì)降低靈活度[11].綜合數(shù)據(jù)分析平臺(tái)服務(wù)器端的設(shè)置要求,本文設(shè)置每片的大小為2 MB,即chunkSize=1024×1024×2,此外還需要的參數(shù)有,chunkNumber 表示分片序數(shù),chunks 表示分片總數(shù),用于服務(wù)器端的文件合成.其合并流程如圖2所示.
圖2 服務(wù)器端文件合并流程
生物信息分析平臺(tái)中的分析工具所需生物數(shù)據(jù)文件大都在600 MB 以上,甚至大到十幾GB,如果采用整體計(jì)算文件MD5 值的方式,容易導(dǎo)致內(nèi)存占用過(guò)大,Web 端異常崩潰等情況,計(jì)算效率也相對(duì)較低.
由于MD5的計(jì)算特性,分片計(jì)算每部分的MD5值后,再進(jìn)行合并不改變?cè)募腗D5 值[12],因此本文采用新的計(jì)算方式.將文件分片后逐個(gè)傳入spark.appendBinary()方法來(lái)計(jì)算、最后通過(guò)spark.end()方法輸出MD5.這種方法節(jié)約內(nèi)存開(kāi)銷,在計(jì)算大文件MD5 值效果更好.根據(jù)前文設(shè)置,分片大小為2 MB,綜合文件大小得出總片數(shù),然后設(shè)置file.cmd5=true,即文件狀態(tài)改為MD5 計(jì)算.接著逐片讀取分片信息,并計(jì)算MD5 值,由spark.end()得出所有值后,將總文件的MD5 值賦給file.5;,作為該文件唯一標(biāo)識(shí),為秒傳和斷點(diǎn)續(xù)傳操作提供方便.最后取消計(jì)算狀態(tài),并開(kāi)始上傳文件.其相關(guān)代碼如下:
//計(jì)算MD5
computeMD5(file) {
chunkSize=2 097 152,//2 MB
chunks=Math.ceil(file.size/chunkSize),
currentChunk=0,
spark=new SparkMD5.ArrayBuffer(),
fileReader=new FileReader();
let time=new Date().getTime();
file.cmd5=true;//文件狀態(tài)為“計(jì)算md5…”
fileReader.onload=(e)=> {
spark.append(e.target.result);
currentChunk++;
if (currentChunk< chunks) {
loadNext();
} else {
console.log('finished loading');
let md5=spark.end();//得到md5
file.uniqueIdentifier=md5;//將文件md5 賦值給文件唯一標(biāo)識(shí)
file.cmd5=false;//取消計(jì)算md5 狀態(tài)
file.resume();//開(kāi)始上傳
}
loadNext();
}
受異常情況中斷后,整體文件重傳需要很大的代價(jià),本文利用已經(jīng)計(jì)算得出的文件MD5 值作為文件的特殊標(biāo)識(shí)符,在進(jìn)行文件重傳時(shí),通過(guò)MD5 校驗(yàn)判斷文件是否已經(jīng)上傳[12],然后再進(jìn)行傳輸操作.本文采用checkChunkUploadedByResponse()函數(shù)響應(yīng)后臺(tái)返回的信息,并檢測(cè)分片信息是否上傳完整.分片上傳前,前端會(huì)向后端發(fā)送一個(gè)攜帶文件信息的get 請(qǐng)求.如果文件已經(jīng)在服務(wù)器端存儲(chǔ),則返回obj.isExist,后續(xù)上傳操作不需要繼續(xù)執(zhí)行.如果返回的是文件分片信息,則表示該部分已經(jīng)上傳,執(zhí)行續(xù)傳操作.其相關(guān)代碼如下:
//續(xù)傳實(shí)現(xiàn)
checkChunkUploadedByResponse:(chunk,message)=> {
let obj=JSON.parse(message);
if (obj.isExist) {
this.statusTextMap.success='秒傳文件';
return true;
}
return(obj.uploaded||[]).indexOf(chunk.offset + 1)>=0
},
//檢測(cè)斷點(diǎn)和MD5
public function checkFile()
{
//檢測(cè)文件MD5是否已經(jīng)存在
$rs=$this->checkMd5($identifier,
$this->fileInfo['totalSize']);
if ($rs['isExist']===true) {
return $rs;
}
//檢查分片是否存在
$chunkExists=[];
for ($index=1;$index <=$totalChunks;$index++){
if (file_exists("{$filePath}_{$index}")) {
array_push($chunkExists,$index);
}
}
本文采用的是多線程傳輸,為了保證傳輸?shù)臏?zhǔn)確性,需等待所有分片傳輸成功再進(jìn)行合并.當(dāng)傳輸?shù)姆制瑪?shù)等于總分片數(shù)chunks 時(shí),會(huì)向后臺(tái)發(fā)送合并請(qǐng)求.onFileSuccess()方法接收從后臺(tái)返回的response 包含了是否需要合并的指令merge,如果resp.merge===true,則向后端發(fā)送合并請(qǐng)求.前端將文件的唯一 ID和拆分總數(shù)(或要傳遞的更多參數(shù))發(fā)送到合并文件的后端.后端受到合并指令后開(kāi)始進(jìn)行文件合并其相關(guān)代碼如下:
//文件合并
public function merge()
{
$filePath=self::$tmpDir.DIRECTORY_SEPARATOR.$this->fileInfo['identifier'];
$totalChunks=$this->fileInfo['totalChunks'];//總分片數(shù)
$filename=$this->fileInfo['filename];//文件名
$done=true;
//檢查所有分片是否都存在
for ($index=1;$index <=$totalChunks;$index++){
if(!file_exists("{$filePath}_{$index}")) { $done=false;
break;
}
}
if ($done===false) {
return $this->message(1005,'分片信息錯(cuò)誤');
}
//如果所有文件分片都上傳完畢,開(kāi)始合并
$timeStart=$this->getmicrotime();//合并開(kāi)始時(shí)間
$saveDir=self::$saveDir.
DIRECTORY_SEPARATOR.date('Y-m-d');
if (!is_dir($saveDir)) {
@mkdir($saveDir);
}
$uploadPath=$saveDir.DIRECTORY_SEPARATO R.$filename;
if (!$out=@fopen($uploadPath,"wb")) {
return $this->message(1004,'文件不可寫');
}
if (flock($out,LOCK_EX)) {// 進(jìn)行排他型鎖定
for($index=1;$index<=$totalChunks;$index++) {
if(!$in=@fopen("{$filePath}_{$index}","rb")) {
break;
}
while ($buff=fread($in,4096)) {
fwrite($out,$buff);
}
@fclose($in);
@unlink("{$filePath}_{$index}");//刪除分片
}
flock($out,LOCK_UN);// 釋放鎖定
}
@fclose($out);
return $res;
}
根據(jù)實(shí)際網(wǎng)絡(luò)情況,本文進(jìn)行了帶寬時(shí)延積BDP為75 KB和135 KB 兩種情況下的文件傳輸測(cè)試.
如表1所示,當(dāng)BDP大于64 KB 時(shí),該網(wǎng)絡(luò)情況可以進(jìn)行多線程傳輸,其效率相較單線程傳輸有一定的提高.
表1 不同BDP 下傳輸耗時(shí)的測(cè)試結(jié)果
本文提出利用帶寬時(shí)延積和最大窗口數(shù)計(jì)算得到網(wǎng)絡(luò)最大承載率,來(lái)決定并發(fā)線程數(shù)的文件上傳方法,充分利用網(wǎng)絡(luò)帶寬資源,采用MD5 值標(biāo)識(shí)已經(jīng)上傳過(guò)的文件,實(shí)現(xiàn)生物數(shù)據(jù)分析平臺(tái)用戶的文件高速上傳,節(jié)約了時(shí)間成本.該方法解決了一般分片上傳過(guò)程中,無(wú)法確定并發(fā)線程數(shù)的問(wèn)題,能夠提高大文件的上傳效率,增強(qiáng)傳輸穩(wěn)定性.