现象
https://oss.example.com/storages/<storage_id>/
1)在浏览器下载固件文件经常出现中断,原本3g的文件,每次都在1g左右下载完成,文件不完整。
2)内网下载速度较慢,插网线为 20M/s 左右,WIFI 状态下为 8M/s 左右。
3)使用网线有部分用户产生问题 1 的中断现象,一部分用户则不会,使用 WIFI 则必现中断。

分析
1)分析业务:在内部平台构建完成之后,后台会自动对整个固件文件夹进行转存,上传到对象存储服务,后续可以随时下载,那么这里就涉及上传和下载,我们可以对多次下载的同一个固件进行 hash 对比,发现他们并不是一致的,首先可以排除是上传文件的问题。

2)我们使用 wget 进行下载,表现为连接关闭重试,自动重新连接之后能成功下载完整文件。所以确实不是因为上传的问题,那么我们从下载方面找问题。

每下载 1G 中断一次

3)我们抓包查看具体细节,可以看到是服务器发送了一个 FIN 包主动断开连接,而且没有出现RST包,可以排除网络错误或网络波动,也不是客户端关闭的连接,而是服务端主动关闭的。

4)接下来需要定位具体是服务端哪个节点主动关闭连接,在哪个节点会限速。分析内部链路,并在每个节点进行测试,测试结果如下。
直连对象存储服务 Pod IP,不会出现下载中断和限速的问题,问题都在网关上。
我们可以暂时定位中断问题在APISIX发生,而限速是在F5发生。如图,使用域名下载文件速度较慢,为18M/s左右,使用后端服务的 Pod IP 下载速度能达到 66M/s。

5)验证猜想,在本地快速搭建Nginx服务进行验证,并查看 Nginx 日志。
可以看到 Nginx 主动 closed connection,这跟我们抓包的现象一致。还看到出现“upstream response is buffered to a temporary file”字样,说明 Nginx 有缓存文件的相关机制。

6)查看官网的相关配置文档,可以看到 proxy_max_temp_file_size 这个参数,配置文件默认不会显示该配置,但是默认是 1G,我们的现象也是 1G 中断,基本可以确认就是这个配置导致的,我们可以尝试修改为1200M,并验证,发现在下载到 1200M 时会中断。

7)那么如何解释这个现象:使用网线有部分用户产生下载中断现象,一部分用户不会。使用 WIFI 则必现中断。
用户 A 测试结果是有线必现中断,用户 B 测试结果是不会中断。观察到两个用户的有线网速也有区别(用户 A 的拓展坞限速 100M ),用户 A 的下载速度大概是 12M/s,用户 B 大概 30M/s。Wifi 的下载速度都是 10M/s 左右。那么我们可以猜测网速跟下载中断有关。
我们可以测试一下,正常不限速时:

使用 wget –limit-rate 限速时:

8)F5 的限速问题通过协调相关负责人排查,最终确定是某机房的 F5 存在性能问题。
3、根因分析
3.1 Nginx 源码分析
Nginx 临时文件为什么跟客户端下载速度有关?官方文档和社区也没有相关的描述,我们可以看一下源码。
1)配置解析:我们直接搜 proxy_max_temp_file_size 这个配置,可以看到该值会从配置中读取后会设置到upstream.max_temp_file_size结构成员。

2)配置合并:继续查看upstream.max_temp_file_size_conf 这个成员变量,可以看到相关的初始化设置,这也印证了我们之前的猜测,如果没有这个配置,Nginx 默认会设置一个 1G 的值。

3)请求处理:在具体的请求中,会初始化事件管道并传递配置值


4)接下来是最关键的代码,根据配置值决定是否继续写入临时文件
A、proxy_cache 配置影响 p->cacheable 的值,默认是 off,这里只可能是 off 才可能走两个不同的判断分支。
B、下载速度影响条件分支的选择,我们可以看到代码里面的官方注释,Nginx优先使用内存缓冲区,只有在以下情况才使用临时文件:
-
内存缓冲区已满
-
客户端暂时无法接收更多数据
也就是说当下载速度较慢时,临时文件的增长会更快达到 1G.

3.2 抓包分析
尝试在服务器端进行抓包,客户端下载截图如下,对抓到的包分阶段分析。

阶段一:正常传输期 0-35秒(31:06 ~ 31:41)


一开始下载时都是客户端与上游服务器之间的[PSH,ACK]和[ACK]包,
偶尔出现[TCP Window Full]和[TCP ZeroWindow]
解释:
- [PSH,ACK]和[ACK]:表示正常的数据传输
- 偶尔出现[TCP Window Full]和[TCP ZeroWindow]:表明客户端处理速度偶尔跟不上接收速度
- 这与我们之前讨论的情况一致:当客户端下载速度较慢时,数据会暂时积累
- 此时Nginx可能开始使用临时文件存储数据
- 但由于这只是偶尔发生,临时文件可能还未达到限制
对应Nginx行为:
- 此阶段,内存缓冲区和临时文件交替使用
- 临时文件大小逐渐增长,但尚未达到
proxy_max_temp_file_size限制
阶段二:临时文件限制触发期 35-44秒(31:41~31:50)

到下载的第 35 s,开始出现大量上游服务器发送的 HTTP Continuation 包,
以及大量的[TCP Fast Retransmission]和[TCP Dup ACK]
解释:
- 大量HTTP Continuation包:表明上游服务器在持续发送大量数据
- [TCP Fast Retransmission]:表示数据包丢失或网络拥塞
- [TCP Dup ACK]:客户端重复发送相同的ACK,表明期望接收到特定序列号的数据
这个时间点(35秒)很可能是临时文件达到proxy_max_temp_file_size限制的时刻!
对应Nginx行为:
- 临时文件达到限制(例如1GB)
- Nginx停止从上游读取数据(
break退出读取循环) - 但上游服务器不知道这一点,继续发送数据
- TCP接收窗口很快填满(ZeroWindow)
- 上游服务器被迫进行重传尝试(Fast Retransmission)
阶段三:连接状态变化期(44-93秒)

到第 44s 时,报文 ip 发生了变化,变成了上游服务器跟一个 ip 为"<private-ip>"的少量报文通信,并且这个阶段客户端没有报文没有出现
解释:
- 报文IP变化:"
" 很可能是网络中的代理、负载均衡器或默认网关 - 客户端IP报文消失:表明客户端和上游服务器之间的直接通信暂停
- 少量报文通信:通过观察看到是对根路径/的 HTTP 请求,并且每 3 秒一次,这是内部配置的健康检查机制。
这个阶段上游服务器跟客户端之间没有数据传输,而是 Nginx 跟客户端之间的数据传输。
对应Nginx行为:
- Nginx仍在等待客户端消费一些数据以释放缓冲区
- 此时上游可能已经开始超时倒计时
阶段四:连接中断期(93秒)

直到第 93s,出现了客户端发送的[TCP Window Update]报文,
接着上游服务器发送 RST 包回复客户端,此时刚好发生下载中断
解释:
- [TCP Window Update]:Nginx 和 客户端终于传输完临时文件数据,开始向上游服务器请求新的数据,增加了接收窗口,但是上游服务器超时机制已触发
- 上游服务器发送RST包:表明上游服务器已经关闭或重置了连接,不再接受新的通信
对应Nginx行为:
- 客户端终于消费了足够的数据
- Nginx准备恢复从上游读取
- 但为时已晚,上游连接已超时
- 上游服务器回应以RST包,重置连接
- Nginx检测到上游连接关闭,中断下载
总结
| 时间 | 事件 | 临时文件状态 | 连接状态 |
|---|---|---|---|
| 0s | 开始下载 | 逐渐增长阶段 | 正常 |
| 35s | 触发临时文件限制 | 达到限制(如1GB) | Nginx停止读取上游 |
| 44s | Nginx 停止接收数据,但仍给客户端传输临时文件数据 | 无变化(已停止增长) | 上游等待,开始超时计时 |
| 93s | 客户端更新窗口,服务器RST | 无变化(已停止增长) | 上游超时,连接重置,下载中断 |
正常下载 → 临时文件达到限制 → Nginx停止读取 → 上游缓冲区填满 → 等待客户端消费 → 上游超时 → 连接重置 → 下载中断
所以,客户端下载速度决定是否产生中断,如果下载速度比较快,在第 4 阶段客户端更新窗口时,服务器可能还没达到超时,这时不会重置连接。下载速度更快的话,可能都不会使 Nginx 临时文件增长到 1G,那么就永远处于第1 阶段。
4、解决方案
| 方案 | 描述 | 优点 | 缺点 |
|---|---|---|---|
| 1) 绕过 Nginx | 客户端直接从上游服务器(如应用服务器)下载文件,不经过Nginx代理。 | 1、彻底避免Nginx限制:完全绕过临时文件问题和代理超时机制,下载过程不受Nginx配置影响。2、性能提升:减少代理层开销,下载速度可能更快(尤其对高带宽客户端)。3、实现简单:无需修改Nginx或上游配置,仅需调整客户端URL或DNS指向上游服务器。 | 1、安全性风险:Nginx的安全功能(如SSL/TLS终止、WAF、IP黑名单)丢失,上游服务器直接暴露,易受攻击。2、可维护性差:需在上游服务器实现Nginx的负载均衡、缓存等功能,增加运维复杂度。 |
2) 修改 Nginx 配置,调高 proxy_max_temp_file_size 的值 |
在Nginx配置中增加临时文件大小上限(例如,从1GB调整为10GB或更高)。 | 1、直接解决问题根源:增大临时文件容量,延迟或避免达到限制,为慢速客户端争取更多下载时间。2、配置简单:仅需修改Nginx配置文件(如 nginx.conf),添加 proxy_max_temp_file_size 10240m。 |
1、资源消耗增加:临时文件占用更多磁盘空间,可能影响服务器性能(尤其I/O密集型场景)。3、潜在风险:文件过大时,可能耗尽磁盘空间或导致Nginx内存溢出。 |
| 3) 修改后台服务器超时时间,使服务器不会发出 RST | 增加上游服务器的超时设置(如调整TCP keepalive或应用层超时参数),避免连接重置。 | 1、改动范围小:不影响其他应用,只针对提供服务的应用做变更。 | 1、配置复杂:需修改上游服务器配置(如Tomcat的connectionTimeout或Nginx的proxy_read_timeout),可能涉及多服务重启。2、资源压力:连接长时间保持占用更多内存和线程,高并发时可能导致资源耗尽。3、延迟问题:过长的超时设置可能掩盖其他问题(如网络故障),造成僵尸连接。 |
方案总结
- 方案1(绕过Nginx)虽彻底但牺牲安全性和可维护性,仅适用于简单测试或内部环境,不推荐作为生产解决方案。
- 方案3(修改超时)由于客户端下载速度无法确定,所以也无法确定超时时间应该设置为多少合适,没办法解决根本问题。可作为补充,增强鲁棒性。
- 方案2在保留Nginx优势的前提下,解决主要瓶颈,且易于实施。
基于问题总结和方案分析,采用方案2(修改Nginx配置,调高 proxy_max_temp_file_size)作为首选方案,方案2针对性最强,且配置简单、风险可控。