传统的Web服务器在处理文件下载的时候,总是先读入文件内容到应用程序内存,然后再把内存当中的内容发送给客户端浏览器。这种方式在应付当今大负载网站,音频视频网站力不从心。sendfile是现代操作系统支持的一种高性能网络IO方式,操作系统内核的sendfile调用可以将文件内容直接推送到网卡的buffer当中,从而避免了Web服务器读写文件的开销,实现了“零拷贝”模式。

作为最流行的轻量级Web服务器的翘楚,lighttpd提供了良好的sendfile支持,JavaEye网站服务器使用的就是lighttpd。在Linux操作系统上面,只需要在lighttpd.conf配置文件如下配置,lighttpd就会使用sendfile方式处理静态资源的下载,效率非常高:

引用

server.network-backend = "linux-sendfile"

但是在某些情况下,我们却无法直接让lighttpd处理文件的下载,比方说JavaEye网站需要统计帖子附件的下载次数,博客相册的点击次数,比方说需要对下载的文件进行权限的控制,特别是对于一些多用户系统,你不能让用户上传的私密文件被其他用户随便下载到,例如JavaEye圈子的共享文件不能够对圈子外的用户开放下载。因此,文件下载目录千万不能放到public目录下,不能让用户直接通过浏览器的URL地址访问到。在这种情况下,文件下载必须由服务器端应用程序来处理。

在RoR应用当中,我们可以在controller中使用send_file方法来控制文件的下载。send_file方法将下载的文件以4KB为单位写到一个输出流去。如果我们使用mongrel应用服务器的话,mongrel会在内存当中创建一个StringIO对象,把整个下载文件完整的读入内存,然后再向客户端或者前端的Web服务器写出。如果我们使用fcgi来运行RoR的话,fcgi会直接把输出流的内容向前端的Web服务器写出。

毫无疑问,我们可以看到这种下载处理方式有很大的性能缺陷:

1、当使用mongrel的时候,如果下载文件很大,会导致mongrel内存暴涨!

mongrel创建一个StringIO对象缓存整个输出内容,我们假设用户下载的是一个100MB的文件,该用户又很喜欢用多线程下载工具,他开了10个线程并发下载,那么mongrel的内存占用会暴涨1GB以上。而且最可怕的是,即使当用户下载结束以后,mongrel的内存都不会迅速回落,而是一直保持如此高的内存占用。这个缺陷非常容易被别有用心的黑客利用,攻击网站。这也是JavaEye网站为什么始终不用mongrel的原因之一。

2、当使用fcgi的时候,如果前端Web服务器没有足够大buffer,会导致fcgi进程被挂住

fcgi自己不开output buffer,而是实时写出输出内容,如果前端Web服务器用的是lighttpd,那么你很幸运,lighttpd会照单全收,一个字节都不拉下;如果前端Web服务器用的是nginx/apache,那么你很不幸,nginx/apache默认只开8K的buffer,收不下的那就对不起了,您慢点嘞,fcgi进程就被挂住了,只要客户端浏览器下载不结束,fcgi进程就被一直占用。

3、即使使用lighttpd fcgi,也会对服务器造成不小的性能开销

lighttpd fcgi是最理想的Rails部署环境,JavaEye网站使用的就是lighttpd fcgi。当ruby程序执行send_file开始下载的时候,fcgi会以4KB为单位读入文件内容,然后立刻写出到lighttpd去,而lighttpd照单全收。因此当下载文件被完整的通过fcgi被flush到lighttpd的内存里面去以后,即使你杀掉fcgi进程,都丝毫不会影响文件下载。

也许你会问,lighttpd都吃下来文件内容,内存会不会暴涨?会的,我们假设同样的用户场景,某用户启动10个线程下载100MB的文件,fcgi进程内存不会发生变化,但是lighttpd会暴涨1GB。但所幸的是lighttpd的内存管理的不错,一旦用户取消下载,或者下载完毕,lighttpd立刻释放掉1GB的内存。

但是无论怎么说,ruby还是需要完整的读取下载文件,而lighttpd也需要开辟足够大的内存,处理整个文件的下载过程,对服务器开销还是很大的。我们的问题是,能不能让带权限控制的文件下载像lighttpd下载静态资源文件那样快,开销那样小呢?答案就是X-sendfile!

使用X-sendfile方式,服务器端应用程序不需要读取下载文件了,只需要设置response的header信息就足够了,此外还要附加一个信息“X-LIGHTTPD-send-file”信息给lighttpd,告诉lighttpd,文件下载我就不管了,你自己看着办吧:

Ruby代码

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  

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,去硬盘的哪个路径找要下载的文件,最后一行啥都不输出了,下载不用ruby来管了。

而lighttpd收到X-LIGHTTPD-send-file信息以后,就会找到硬盘该文件,以静态资源文件的下载方式处理,丝毫不消耗lighttpd的内存。还是以某用户启动10个线程下载100MB文件为例,10个fcgi进程发送了response信息就处理完毕了,而lighttpd知道下载的是硬盘的静态文件,会以sendfile方式下载,文件内容就会被操作系统内核直接送到网卡的buffer里面,既不消耗ruby进程,也不消耗lighttpd,皆大欢喜。

在lighttpd-1.4.18版本里面,fastcgi方式已经内置X-sendfile支持,仅仅需要你在配置文件打开就可以了:

引用

"allow-x-send-file"="enable"

JavaEye网站在使用了X-sendfile功能之后,lighttpd的内存占用有明显的下降。未使用X-sendfile之前,lighttpd有时候内存占用会到200MB以上(有用户多线程下载附件),在使用X-sendfile之后,lighttpd的内存占用还从未突破20MB。

最后要提醒大家几个问题:

1、lighttpd-1.4.x不认X-sendfile这个header,只认X-LIGHTTPD-send-file

按照lighttpd网站自己的文档,以及各种各样流行的X-sendfile文档,设置的header都是X-sendfile,但是经过我们n次失败的摸索,才发现原来必须使用X-LIGHTTPD-send-file,这一点请不要被文档迷惑,目前好像也只有我们提出这个解决办法,互联网上面尚未看到其他人提出过,看来我们又首开先河了。用RoR就是这点好,你动不动就得自己先去当尝螃蟹的那个人。

2、lighttpd-1.5.0版本的X-sendfile设置有所改变

lighttpd-1.5.0版本还未发布正式版本,据说1.5.0已经认识X-sendfile这个header了,这个大家有兴趣自己测试吧。

补充几句:

1、lighttpd-1.4.18仅支持fastcgi情况下的X-sendfile,而1.5.0版本则支持各种情况包括fastcgi, scgi, http proxy, ajp等协议下的X-sendfile

2、apache不支持X-sendfile,可以通过第三方mod添加支持,但据说不太好用

3、nginx有类似lighttpd的X-sendfile,叫做X-Accel-Redirect,但是使用的限制比较大,必须实现在nginx上面配置到某个目录下的文件才具备这样的功能,而不像lighttpd,任意目录文件不需要配置都可以支持,因而很受人批评。

当然,我还是推荐大家使用lighttpd fcgi,这是性能最佳Rails方案,我会另外写文章剖析Rails部署方案的优劣。

  

  discuz论坛中很多附近要下载,或图片附件要显示,这些附件都会被php-cg直接读给lighttpd,lighttpd会全部接收到内存,并且不会迅速释放,从而导致其内存占用增大,我一个中等访问量的论坛lighttpd占用内存有时会达到1.4G。

       x-sendfile可以解决这个问题!!php通过x-sendfile头告诉lighttpd文件的路径,lighttpd通过系统调用sendfile直接发送文件到客户端,这样避免了内存占用!!效果很明显!!

1、修改lighttpd配置:

"allow-x-send-file" => "enable"  

2、修改discuz的attachment.php:

查找代码:getlocalfile($attachdir.'/'.$attach['attachment'].'.thumb.jpg');

注释掉,然后在下面添加代码

dheader("X-LIGHTTPD-send-file: " . $attachdir.'/'.$attach['attachment'].'.thumb.jpg');

查找代码:$attach['remote'] ? getremotefile($attach['attachment']) : getlocalfile($filename, $readmod, $range);

注释掉,然后添加代码

$attach['remote'] ? getremotefile($attach['attachment']) : dheader("X-LIGHTTPD-send-file: " . $filename);

  

lighttpd虚拟主机配置

[ 2009/09/16 23:39 | by suibing ]

  $HTTP["host"] == "bbs.xxx.com" {

server.name = "bbs.xxx.com"

server.document-root = "/var/www/bbs"

server.errorlog = "/var/www/bbs/error.log"

accesslog.filename = "/var/www/bbs/access.log"

}

else

lighttpd.conf解释

server.use-ipv6 = "disable" # 缺省为禁用

server.event-handler = "linux-sysepoll" # Linux环境下epoll系统调用可提高吞吐量

#server.max-worker = 10 # 如果你的系统资源没跑满,可考虑调高 lighttpd进程数

server.max-fds = 4096 # 默认的,应该够用了,可根据实际情况调整

server.max-connections = 4096 # 默认等于 server.max-fds

server.network-backend = "linux-sendfile"

server.max-keep-alive-requests = 0 # 在一个keep-alive会话终止连接前能接受处理的最大请求数。0为禁止

# 设置要加载的module

server.modules = (

  "mod_rewrite",

  "mod_redirect",

# "mod_alias",

  "mod_access",

# "mod_cml",

# "mod_trigger_b4_dl",

  "mod_auth",

  "mod_expire",

# "mod_status",

# "mod_setenv",

  "mod_proxy_core",

  "mod_proxy_backend_http",

  "mod_proxy_backend_fastcgi",

# "mod_proxy_backend_scgi",

# "mod_proxy_backend_ajp13",

# "mod_simple_vhost",

  "mod_evhost",

# "mod_userdir",

# "mod_cgi",

  "mod_compress",

# "mod_ssi",

# "mod_usertrack",

# "mod_secdownload",

# "mod_rrdtool",

  "mod_accesslog" )

# 网站根目录

server.document-root = "/var/www/"

# 错误日志位置

server.errorlog = "/var/log/lighttpd/error.log"

# 网站Index

index-file.names = ( "index.php", "index.html",

  "index.htm", "default.htm" )

# 访问日志, 以及日志格式 (combined), 使用X-Forwarded-For可越过代理读取真实ip

accesslog.filename = "/var/log/lighttpd/access.log"

accesslog.format = "%{X-Forwarded-For}i %v %u %t \"%r\" %s %b \"%{User-Agent}i\" \"%{Referer}i\""

# 设置禁止访问的文件扩展名

url.access-deny = ( """, ".inc", ".tpl" )

# 服务监听端口

server.port = 80

# 进程id记录位置

server.pid-file = "/var/run/lighttpd.pid"

# virtual directory listings 如果没有找到index文件就列出目录。建议disable。

dir-listing.activate = "disable"

# 服务运行使用的用户及用户组

server.username = "www"

server.groupname = "www"

# gzip压缩存放的目录以及需要压缩的文件类型

compress.cache-dir = "/tmp/lighttpd/cache/compress/"

compress.filetype = ("text/plain", "text/html")

# fastcgi module

# for PHP don't forget to set cgi.fix_pathinfo = 1 in the php.ini

$HTTP["url"] =" "\.php$" {

  proxy-core.balancer = "round-robin"

  proxy-core.allow-x-sendfile = "enable"

# proxy-core.check-local = "enable"

  proxy-core.protocol = "fastcgi"

  proxy-core.backends = ( "unix:/tmp/php-fastcgi1.sock","unix:/tmp/php-fastcgi2.sock" )

  proxy-core.max-pool-size = 16

}

# 权限控制

auth.backend = "htpasswd"

auth.backend.htpasswd.userfile = "/var/www/htpasswd.userfile"

# 基于 evhost 的虚拟主机 针对域名

$HTTP["host"] == "a.lostk.com" {

  server.document-root = "/var/www/lostk/"

  server.errorlog = "/var/log/lighttpd/lostk-error.log"

  accesslog.filename = "/var/log/lighttpd/lostk-access.log"

  # 设定文件过期时间

  expire.url = (

  "/css/" => "access 2 hours",

  "/js/" => "access 2 hours",

  )

  # url 跳转

  url.redirect = (

  "^/$" => "/xxx/index.html",

  )

  # url 重写 (cakephp可用)

  url.rewrite = (

  "^/(css|js)/(.*)$" => "/$1/$2",

  "^/([^.] )$" => "/index.php?url=$1",

  )

  # 权限控制

  auth.require = ( "" =>

  (

  "method" => "basic",

  "realm" => "admin only",

  "require" => "user=admin1|user=admin2" # 允许的用户, 用户列表文件 在上面配置的auth.backend.htpasswd.userfile 里

  ),

  )

}

# 针对端口的虚拟主机

$SERVER["socket"] == "192.168.0.1:8000" {

  server.document-root = "/var/www/xxx/"

  server.errorlog = "/var/log/lighttpd/test-error.log"

  accesslog.filename = "/var/log/lighttpd/test-access.log"

  # ...

}

  

lighttpd的X-sendfile  

[ 2009/09/16 23:39 | by suibing ]

  今天才知道原来lighttpd or ngix都有X-sendfile这一类的好东西阿!!!

用这一类手法可以有复杂的认证/检查, 然后档案还可以送得又快又好

用php输出静态文件跟用lighttpd输出静态文件的效率实在差太多了啊。

有兴趣的人可以看以下兩個link or google找更多資料:有兴趣的人可以看以下两个link or google找更多资料:

http://blog.lighttpd.net/articles/2006/07/02/x-sendfile

http://trac.lighttpd.net/trac/wiki/Docs:ModFastCGI

以lighttpd为例, 用法如下:以lighttpd为例,用法如下:

lighttpd.conf fastcgi部分加上: lighttpd.conf fastcgi部分加上:

"allow-x-send-file" => "enable" "allow-x-send-file" => "enable"

sample code: sample code:

$path="/home/data/ooxx";

$file="actual_file.zip";

$file_to_download='filename.zip';

//X-Sendfile sample: X-Sendfile (lighttpd 1.5 ) or X-LIGHTTPD-send-file ( //X-Sendfile sample: X-Sendfile (lighttpd 1.5 ) or X-LIGHTTPD-send-file (

header("Content-Type: Text/Plain"); header("Content-Type: Text/Plain");

header("Content-Length: ".filesize("$path/$file")); header("Content-Length: ".filesize("$path/$file"));

header( "Content-Disposition: attachment; filename=\"" . $file_to_download . '"' ); header( "Content-Disposition: attachment; filename=\"" . $file_to_download . '"' );

if($_SERVER['SERVER_SOFTWARE']

  

  从wnmp发布以来,很多朋友就问Windows下如何将Nginx加入服务中,以便于服务器重启后自动运行,我在朋友的留言中回复了,可能不是很好找,便整理在此,便于需要的朋友查找。

假设nginx安装在c:\nginx\下:

1.下载微软服务注册工具srvany.exe, instsrv.exe, srvany-instsrv存放到c:\nginx\目录下

2.安装Nginx服务, 将命令行切换到c:\nginx\,执行下列命令

instsrv NGINX c:\nginx\srvany.exe

3.在c:\nginx\下,新建一个nginx.reg文件,输入一下内容:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NGINX\Parameters]

“Application”=”C:\\nginx\\nginx.exe”

“AppParameters”=”"

“AppDirectory”=”C:\\nginx\\”

5.让服务与程序关联起来, 命令行执行

regedit /s nginx.reg

6.编辑启动nginx脚本start-nginx.bat(关闭脚本不用变), 让程序以服务方式运行

@ECHO OFF

net stop nginx

net start nginx

EXIT

完成"

最近一直在研究Linux下的Nginx自动安装,呵呵,鼓励大家迁移到Linux平台…

  

分页: 10/40 第一页 上页 5 6 7 8 9 10 11 12 13 14 下页 最后页 [ 显示模式: 摘要 | 列表 ]