大文件下载中断问题的全链路排查与稳定性优化

现象 https://oss.example.com/storages/<storage_id>//firmware.zip 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 时会中断。 ...

May 8, 2025

OOMKilled 深度排查:内存、PageCache 与 I/O 的联合分析

1、现象 收到Memory hit original limit内存告警与CPU容量水位告警,随后发生OOM,容器无限重启 2、应急解决方案 最重要的事情是先保证生产可用,并增加JVM参数用来观测,做完以下调整后对容器状态进行观察。 1)我们需要快速调高内存,内存参数调整为-Xmx3g -Xms3g。 2)加上JVM参数 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/dump ,并挂载/app/logs/dump目录,确保堆内存OOM时可以将堆内存dump下来进行分析。 3)加上-XX:NativeMemoryTracking=summary,用来分析堆外内存。 3、收集相关数据信息 1)容器成功恢复健康状态,但是过了一段时间还是触发了OOMKilled,查看/app/logs/dump目录,并没有dump文件,说明有可能不是堆内存溢出,我们查看JVM状态,包括堆内存信息。 2)JVM状态分析 在容器重启之前,CPU占用在15%-30%之间,属于正常水平,堆内存也一直处于低水位状态,非堆内存也没有明显起伏,但是可以看到系统内存占用偏高,我们需要继续追踪堆外内存的情况。 3) 可以看到总的预留内存4.8GB,使用的物理内存3.56GB,而且通过在进程稳定时和内存到达上限时的快照信息分析,每项的值没有明显的变化。 4)查看容器详情 容器重启之前RSS全程没变,但是可以看到PageCache在重启之前飙升上去了。PageCache是文件缓存占用的内存,按道理来说,在内存达到瓶颈时,系统会自动回收才对。 4、问题分析,追究根因 想知道PageCache为什么在内存达到容器限制之前没被回收,我们需要知道OOMKilled机制,在什么情况下会触发。 1)OOMKilled是Linux内核的机制,当容器内存达到memory.limit_in_bytes(由k8s通过cgroup设置)且无法回收时会触发。我们找一个容器进行测试,将memory.limit_in_bytes调为524M(内部监控平台容器配额)。 查看memory.limit_in_bytes:cat /sys/fs/cgroup/memory/memory.limit_in_bytes watch -n 1 “cat /sys/fs/cgroup/memory/memory.usage_in_bytes” 观察内存增长 找一个大文件下载,wget “xxx”,并继续观察memory.usage_in_bytes,可以看到该值在文件下载的过程中一直上升,上升到接近memory.limit_in_bytes时触发了137错误码,即oomkilled。从现象可以得知,Linux内核会监控该cgroup内RSS、PageCache、mapped_file等内存的总和,RSS包含 JVM 进程的所有内存区域(如堆、非堆、JVM 自身代码、Native Memory等),当总和接近或超过memory.limit_in_bytes时,Linux内核会触发OOMKilled。 2)为什么没有触发PageCache的回收,PageCache回收的速度也不可能赶不上带宽下行网速,原因是PageCache的回收是基于全局物理内存压力,不是单个容器的内存限制。而我们是在docker容器内,可以用命令查看物理内存。 容器是 Cgroups 隔离的独立环境,内核无法感知容器内的内存超限风险。即使容器内存即将超限,内核也不会优先回收其 PageCache,导致容器因 PageCache 触发 OOMKilled。 5、项目分析 随着效能平台的迭代,目前已经有了很多大文件下载或转存的场景,目前主要有: 1)效能客户端 APK 数据流上传(蓝线) 使用 MultipartFile 上传文件,spring.servlet.multipart.file-size-threshold 设置为 10M 时,超过 10M 则会存到临时文件,所以当 APK 文件过大时,也有 PageCache 上升的风险。(注释翻译:将基础输出流从基于内存的流切换到由磁盘支持的流。这是我们意识到有过多数据正在写入,无法再保存在内存中,因此选择切换到基于磁盘的存储。) ...

April 10, 2025