现象

https://oss.example.com/storages/<storage_id>//firmware.zip

1)在浏览器下载固件文件经常出现中断,原本3g的文件,每次都在1g左右下载完成,文件不完整。

2)内网下载速度较慢,插网线为 20M/s 左右,WIFI 状态下为 8M/s 左右。

3)使用网线有部分用户产生问题 1 的中断现象,一部分用户则不会,使用 WIFI 则必现中断。

image-20241023203151910

分析

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

image-20250416161054478

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

image-20241023203251840

每下载 1G 中断一次

image-20250415170420210

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

image-20241024172843510

4)接下来需要定位具体是服务端哪个节点主动关闭连接,在哪个节点会限速。分析内部链路,并在每个节点进行测试,测试结果如下。

image-20250416173501202

直连对象存储服务 Pod IP,不会出现下载中断和限速的问题,问题都在网关上。

我们可以暂时定位中断问题在APISIX发生,而限速是在F5发生。如图,使用域名下载文件速度较慢,为18M/s左右,使用后端服务的 Pod IP 下载速度能达到 66M/s。

image-20250415163055303

5)验证猜想,在本地快速搭建Nginx服务进行验证,并查看 Nginx 日志。

可以看到 Nginx 主动 closed connection,这跟我们抓包的现象一致。还看到出现“upstream response is buffered to a temporary file”字样,说明 Nginx 有缓存文件的相关机制。

image-20241028170828289

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

image-20241028171024496

7)那么如何解释这个现象:使用网线有部分用户产生下载中断现象,一部分用户不会。使用 WIFI 则必现中断。

用户 A 测试结果是有线必现中断,用户 B 测试结果是不会中断。观察到两个用户的有线网速也有区别(用户 A 的拓展坞限速 100M ),用户 A 的下载速度大概是 12M/s,用户 B 大概 30M/s。Wifi 的下载速度都是 10M/s 左右。那么我们可以猜测网速跟下载中断有关。

我们可以测试一下,正常不限速时:

image-20250416191157506

使用 wget –limit-rate 限速时:

image-20250416191213827

8)F5 的限速问题通过协调相关负责人排查,最终确定是某机房的 F5 存在性能问题。

3、根因分析

3.1 Nginx 源码分析

Nginx 临时文件为什么跟客户端下载速度有关?官方文档和社区也没有相关的描述,我们可以看一下源码。

1)配置解析:我们直接搜 proxy_max_temp_file_size 这个配置,可以看到该值会从配置中读取后会设置到upstream.max_temp_file_size结构成员。

image-20250417114332831

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

image-20250417114413384

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

image-20250417115251695

image-20250417115319756

4)接下来是最关键的代码,根据配置值决定是否继续写入临时文件

A、proxy_cache 配置影响 p->cacheable 的值,默认是 off,这里只可能是 off 才可能走两个不同的判断分支。

B、下载速度影响条件分支的选择,我们可以看到代码里面的官方注释,Nginx优先使用内存缓冲区,只有在以下情况才使用临时文件:

  • 内存缓冲区已满

  • 客户端暂时无法接收更多数据

也就是说当下载速度较慢时,临时文件的增长会更快达到 1G.

image-20250417180115970

3.2 抓包分析

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

企业微信截图_cb85af03-cd26-4e6f-9209-f93255fb9ca9

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

企业微信截图_6c050da2-7158-41c3-9b77-1d3ea107631e

企业微信截图_b530ecbb-0561-4ca1-a5bc-b04c42ae0ebf

一开始下载时都是客户端与上游服务器之间的[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)

企业微信截图_0b722973-ee82-4dc6-a4af-289853dd6b03

到下载的第 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行为

  1. 临时文件达到限制(例如1GB)
  2. Nginx停止从上游读取数据(break退出读取循环)
  3. 但上游服务器不知道这一点,继续发送数据
  4. TCP接收窗口很快填满(ZeroWindow)
  5. 上游服务器被迫进行重传尝试(Fast Retransmission)

阶段三:连接状态变化期(44-93秒)

企业微信截图_8114a30c-bd98-4d91-81a8-773e2e210325

到第 44s 时,报文 ip 发生了变化,变成了上游服务器跟一个 ip 为"<private-ip>"的少量报文通信,并且这个阶段客户端没有报文没有出现

解释

  • 报文IP变化:"" 很可能是网络中的代理、负载均衡器或默认网关
  • 客户端IP报文消失:表明客户端和上游服务器之间的直接通信暂停
  • 少量报文通信:通过观察看到是对根路径/的 HTTP 请求,并且每 3 秒一次,这是内部配置的健康检查机制。

这个阶段上游服务器跟客户端之间没有数据传输,而是 Nginx 跟客户端之间的数据传输。

对应Nginx行为

  1. Nginx仍在等待客户端消费一些数据以释放缓冲区
  2. 此时上游可能已经开始超时倒计时

阶段四:连接中断期(93秒)

企业微信截图_b6172b57-92ce-406e-83dc-caaeac6d09fb

直到第 93s,出现了客户端发送的[TCP Window Update]报文,
接着上游服务器发送 RST 包回复客户端,此时刚好发生下载中断

解释

  • [TCP Window Update]:Nginx 和 客户端终于传输完临时文件数据,开始向上游服务器请求新的数据,增加了接收窗口,但是上游服务器超时机制已触发
  • 上游服务器发送RST包:表明上游服务器已经关闭或重置了连接,不再接受新的通信

对应Nginx行为

  1. 客户端终于消费了足够的数据
  2. Nginx准备恢复从上游读取
  3. 但为时已晚,上游连接已超时
  4. 上游服务器回应以RST包,重置连接
  5. 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针对性最强,且配置简单、风险可控。