作為最流行的輕量級(jí)Web服務(wù)器的翹楚,lighttpd提供了良好的sendfile支持,JavaEye網(wǎng)站服務(wù)器使用的就是lighttpd。在Linux操作系統(tǒng)上面,只需要在lighttpd.conf配置文件如下配置,lighttpd就會(huì)使用sendfile方式處理靜態(tài)資源的下載,效率非常高:
引用
server.network-backend = "linux-sendfile"
但是在某些情況下,我們卻無(wú)法直接讓lighttpd處理文件的下載,比方說(shuō)JavaEye網(wǎng)站需要統(tǒng)計(jì)帖子附件的下載次數(shù),博客相冊(cè)的點(diǎn)擊次數(shù),比方說(shuō)需要對(duì)下載的文件進(jìn)行權(quán)限的控制,特別是對(duì)于一些多用戶系統(tǒng),你不能讓用戶上傳的私密文件被其他用戶隨便下載到,例如JavaEye圈子的共享文件不能夠?qū)θψ油獾挠脩糸_放下載。因此,文件下載目錄千萬(wàn)不能放到public目錄下,不能讓用戶直接通過(guò)瀏覽器的URL地址訪問(wèn)到。在這種情況下,文件下載必須由服務(wù)器端應(yīng)用程序來(lái)處理。
在RoR應(yīng)用當(dāng)中,我們可以在controller中使用send_file方法來(lái)控制文件的下載。send_file方法將下載的文件以4KB為單位寫到一個(gè)輸出流去。如果我們使用mongrel應(yīng)用服務(wù)器的話,mongrel會(huì)在內(nèi)存當(dāng)中創(chuàng)建一個(gè)StringIO對(duì)象,把整個(gè)下載文件完整的讀入內(nèi)存,然后再向客戶端或者前端的Web服務(wù)器寫出。如果我們使用fcgi來(lái)運(yùn)行RoR的話,fcgi會(huì)直接把輸出流的內(nèi)容向前端的Web服務(wù)器寫出。
毫無(wú)疑問(wèn),我們可以看到這種下載處理方式有很大的性能缺陷:
1、當(dāng)使用mongrel的時(shí)候,如果下載文件很大,會(huì)導(dǎo)致mongrel內(nèi)存暴漲!
mongrel創(chuàng)建一個(gè)StringIO對(duì)象緩存整個(gè)輸出內(nèi)容,我們假設(shè)用戶下載的是一個(gè)100MB的文件,該用戶又很喜歡用多線程下載工具,他開了10個(gè)線程并發(fā)下載,那么mongrel的內(nèi)存占用會(huì)暴漲1GB以上。而且最可怕的是,即使當(dāng)用戶下載結(jié)束以后,mongrel的內(nèi)存都不會(huì)迅速回落,而是一直保持如此高的內(nèi)存占用。這個(gè)缺陷非常容易被別有用心的黑客利用,攻擊網(wǎng)站。這也是JavaEye網(wǎng)站為什么始終不用mongrel的原因之一。
2、當(dāng)使用fcgi的時(shí)候,如果前端Web服務(wù)器沒有足夠大buffer,會(huì)導(dǎo)致fcgi進(jìn)程被掛住
fcgi自己不開output buffer,而是實(shí)時(shí)寫出輸出內(nèi)容,如果前端Web服務(wù)器用的是lighttpd,那么你很幸運(yùn),lighttpd會(huì)照單全收,一個(gè)字節(jié)都不拉下;如果前端Web服務(wù)器用的是nginx/apache,那么你很不幸,nginx/apache默認(rèn)只開8K的buffer,收不下的那就對(duì)不起了,您慢點(diǎn)嘞,fcgi進(jìn)程就被掛住了,只要客戶端瀏覽器下載不結(jié)束,fcgi進(jìn)程就被一直占用。
3、即使使用lighttpd+fcgi,也會(huì)對(duì)服務(wù)器造成不小的性能開銷
lighttpd+fcgi是最理想的Rails部署環(huán)境,JavaEye網(wǎng)站使用的就是lighttpd+fcgi。當(dāng)ruby程序執(zhí)行send_file開始下載的時(shí)候,fcgi會(huì)以4KB為單位讀入文件內(nèi)容,然后立刻寫出到lighttpd去,而lighttpd照單全收。因此當(dāng)下載文件被完整的通過(guò)fcgi被flush到lighttpd的內(nèi)存里面去以后,即使你殺掉fcgi進(jìn)程,都絲毫不會(huì)影響文件下載。
也許你會(huì)問(wèn),lighttpd都吃下來(lái)文件內(nèi)容,內(nèi)存會(huì)不會(huì)暴漲?會(huì)的,我們假設(shè)同樣的用戶場(chǎng)景,某用戶啟動(dòng)10個(gè)線程下載100MB的文件,fcgi進(jìn)程內(nèi)存不會(huì)發(fā)生變化,但是lighttpd會(huì)暴漲1GB。但所幸的是lighttpd的內(nèi)存管理的不錯(cuò),一旦用戶取消下載,或者下載完畢,lighttpd立刻釋放掉1GB的內(nèi)存。
但是無(wú)論怎么說(shuō),ruby還是需要完整的讀取下載文件,而lighttpd也需要開辟足夠大的內(nèi)存,處理整個(gè)文件的下載過(guò)程,對(duì)服務(wù)器開銷還是很大的。我們的問(wèn)題是,能不能讓帶權(quán)限控制的文件下載像lighttpd下載靜態(tài)資源文件那樣快,開銷那樣小呢?答案就是X-sendfile!
使用X-sendfile方式,服務(wù)器端應(yīng)用程序不需要讀取下載文件了,只需要設(shè)置response的header信息就足夠了,此外還要附加一個(gè)信息“X-LIGHTTPD-send-file”信息給lighttpd,告訴lighttpd,文件下載我就不管了,你自己看著辦吧:
- response.headers['Content-Type'] = @attachment.content_type
- response.headers['Content-Disposition'] = "attachment; filename=\"#{URI.encode(@attachment.filename)}\""
- response.headers['Content-Length'] = @attachment.size
- response.headers["X-LIGHTTPD-send-file"] = @attachment.public_filename
- render :nothing => true
X-LIGHTTPD-send-file告訴lighttpd,去硬盤的哪個(gè)路徑找要下載的文件,最后一行啥都不輸出了,下載不用ruby來(lái)管了。
而lighttpd收到X-LIGHTTPD-send-file信息以后,就會(huì)找到硬盤該文件,以靜態(tài)資源文件的下載方式處理,絲毫不消耗lighttpd的內(nèi)存。還是以某用戶啟動(dòng)10個(gè)線程下載100MB文件為例,10個(gè)fcgi進(jìn)程發(fā)送了response信息就處理完畢了,而lighttpd知道下載的是硬盤的靜態(tài)文件,會(huì)以sendfile方式下載,文件內(nèi)容就會(huì)被操作系統(tǒng)內(nèi)核直接送到網(wǎng)卡的buffer里面,既不消耗ruby進(jìn)程,也不消耗lighttpd,皆大歡喜。
在lighttpd-1.4.18版本里面,fastcgi方式已經(jīng)內(nèi)置X-sendfile支持,僅僅需要你在配置文件打開就可以了:
引用
"allow-x-send-file"="enable"
JavaEye網(wǎng)站在使用了X-sendfile功能之后,lighttpd的內(nèi)存占用有明顯的下降。未使用X-sendfile之前,lighttpd有時(shí)候內(nèi)存占用會(huì)到200MB以上(有用戶多線程下載附件),在使用X-sendfile之后,lighttpd的內(nèi)存占用還從未突破20MB。
最后要提醒大家?guī)讉(gè)問(wèn)題:
1、lighttpd-1.4.x不認(rèn)X-sendfile這個(gè)header,只認(rèn)X-LIGHTTPD-send-file
按照l(shuí)ighttpd網(wǎng)站自己的文檔,以及各種各樣流行的X-sendfile文檔,設(shè)置的header都是X-sendfile,但是經(jīng)過(guò)我們n次失敗的摸索,才發(fā)現(xiàn)原來(lái)必須使用X-LIGHTTPD-send-file,這一點(diǎn)請(qǐng)不要被文檔迷惑,目前好像也只有我們提出這個(gè)解決辦法,互聯(lián)網(wǎng)上面尚未看到其他人提出過(guò),看來(lái)我們又首開先河了。用RoR就是這點(diǎn)好,你動(dòng)不動(dòng)就得自己先去當(dāng)嘗螃蟹的那個(gè)人。
2、lighttpd-1.5.0版本的X-sendfile設(shè)置有所改變
lighttpd-1.5.0版本還未發(fā)布正式版本,據(jù)說(shuō)1.5.0已經(jīng)認(rèn)識(shí)X-sendfile這個(gè)header了,這個(gè)大家有興趣自己測(cè)試吧。
安徽新華電腦學(xué)校專業(yè)職業(yè)規(guī)劃師為你提供更多幫助【在線咨詢】