问题:nginx+fastcgi+php+某论坛程序的环境下,通过论坛上传的比较大(500KB)的文件下载后体积变小了。而较小的文件(100KB)则安然无恙。
用WinHex比较,可以看到文件从中间被截断了,并且没有多出任何内容。使用经多次测试,每次下载的大小在64KB左右(小于64KB),但不相 同。下载到64KB左右的时候卡住了,几秒钟之后下载进度直接跳到100%,显示下载完成。为排除伟大的墙的因素,翻 墙测试,每次下载的大小在127KB左右。
找到论坛源代码,可以看到使用的是php的readfile()进行文件输出,而在输出前已经写入了content-length header。通过HttpFox进行观察,content-length的大小是正确的文件大小。
由于64KB这个大小比较敏感,所以去代码里找了有没有循环缓存读取文件的地方,并没有发现。readfile()直接读取全部内容进行输出。
为了排查,先将服务器切换到httpd(mod_php),则恢复正常,也就是说问题很可能在nginx和fastcgi上。
检查nginx.conf,注意到这个地方:
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
它的值正好等于64K,也就是说问题可能和它有关。将其修改为16K,重启nginx服务,重新下载,这次文件的大小变成了16KB左右。
检查日志发现这么一行:
2010/07/15 02:11:40 [crit] 6064#0: *112 open() "/var/lib/nginx/tmp/fastcgi/1/00/0000000001" failed (13: Permission denied)
nginx会使用fastcgi_buffer_size指定的大小的缓冲区用于缓存fastcgi流的内容。当大小超出此大小时会继续用fastcgi_buffers指定的数量和大小申请缓冲区。如果依然超出此大小,会将多出的内容写入临时文件。
也就是说,在本情况下,nginx首先会使用一个64K的缓冲区缓冲fastcgi流的第一部分,超出后最多申请4*64K=256K的缓冲区用于缓冲。如果继续超出,则写入临时文件。
下载100KB的文件时,内存完全足够,不用写入临时文件,所以没有问题。
下载500KB的文件,64KB+64KB*4已经装不下这个文件,需要使用临时文件。
而日志中反应的就是nginx无权写入临时文件。我不清楚nginx在这里具体是如何实现的,个人猜测可能在写入失败后就放弃了后面4个缓冲区,发 送完第一个缓冲区后就停止发送。如果是这样,一旦超过keep-alive的时间,服务器会断开tcp连接,浏览器认为文件下载完成。考虑header的 大小,浏览器下载的文件略小于64KB。这样一来就能够解释通观察到的现象。
将目录权限问题修正后,问题解决。
有时间研究下nginx的源码,进一步确认该解决方法的有效性。