最近遇到了一个很诡异的问题,有用户反馈从文件下载服务测试环境下载一个视频文件,每次MD5都不一样。。。

对于文件下载服务来说,下载文件内容错乱是个很严重的问题了,但是之前一直也没遇到过文件内容错乱的问题。看了一下问题文件,是一个视频文件,大小为1.08GB。第一个反应就是可能是一个大文件下载才会触发的问题。接着问用户如何发现这个问题的,答曰因为这个视频文件播放到最后很卡,第二个反应是下载到最后存在数据错乱。

自己测试了一下,测试环境是100%复现,每次的MD5都是不一样的。改用另外的大于1G的文件,一样能复现,排除了特定文件的可能。接着测试500MB的文件和900MB,发现没问题,推测问题是出在大于1GB的文件上。但是,生产环境却没有这个问题。。。

本地起Tomcat测试,竟然发现没有复现,结合该应用稳定运行多年,线上也没有人反馈文件异常问题,推测应用本身的逻辑应该是正常的。怀疑的焦点转移到了Nginx上。直接访问测试环境的Tomcat,发现也是正常的,确定是Nginx问题。

查看Nginx日志,发现有很重要的信息:

1
2019/06/28 11:28:27 [error] 15032#15032: *5973942305 upstream prematurely closed connection while reading upstream, client: 192.168.237.155, server: debug.web.com, request: "GET /v0.1/static/test_mzb/bigtest-d072bc5950f767009175668f781faaf2.rar HTTP/1.1", upstream: "http://172.xx.xx.xx:8092/v0.1/static/test_mzb/bigtest-d072bc5950f767009175668f781faaf2.rar", host: "debug.web.com"

upstream prematurely closed connection while reading upstream这个错误信息的意思是Nginx读取Tomcat请求时,后端服务关闭了连接。网上找到了对应错误的讨论:Upstream prematurely closed connection while reading upstream · Issue #5706 · owncloud/client,这篇issue中,有人说通过设置proxy_request_buffering off;proxy_buffering off;可以解决这个问题。通过这个线索,怀疑是Nginx的缓存功能导致的问题。

查看Nginx文档,Module ngx_http_proxy_module,其中描述了在作为代理服务器时,是否缓存响应的相关配置。总的开关配置是proxy_buffering,表示是否开启反向代理服务对Response的缓存,默认开启。开始缓存时,Nginx会以最快的速度从后端服务读取Response内容,缓存到缓冲区中,缓冲区的大小由proxy_buffer_sizeproxy_buffers配置项指定。如果缓冲区无法装下所有的响应,则响应的内容会写入到缓存文件中,缓存文件的最大大小由proxy_max_temp_file_size配置项指定,默认为1G。Nginx这个缓存行为会进行到Response读取完毕,或者是缓存文件写满。

这个缓存文件大小为1G,与出问题的文件是大于1G相吻合。所以可以推论问题的原因是因为用户下载文件,Nginx会以最快速度从Tomcat获取文件流,因为Nginx与Tomcat处于一个内网,带宽很高,很快就会把1G的缓存文件写满,然后用户慢慢下载,当用户下载完这缓存的内容后,Nginx继续从Tomcat获取剩余的内容,但是这会儿连接因为长时间没有读写过期关闭了,导致Nginx提示upstream prematurely closed connection while reading upstream错误。至于为什么用户会继续下载到后续的错误字节数据,这个要看Nginx的具体代码才能清楚了。

通过将proxy_max_temp_file_size配置为2G,发现问题解决了,可以确定是这个问题。

参考资料