并发下载导致设备重启排查记录

复现条件:

10个用户同时下载800MB的文件,下载到一半,发现串口有重启日志

测试结果:

下载的10个文件,本地有10个文件,但是文件内容不完整,大概200MB左右

排查

排查1:在Java的启动命令上加上-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof
重启后未发现dump文件。

排查2:可能是由于系统资源耗尽导致。
1、CPU资源:使用top命令,观察CPU占用,并没有拉满
2、内存耗尽:可能是物理内存和交换内存被完全占用,但是暂未找到工具监控该指标(使用top命令时,返回结果并没有看见swap信息)

使用命令观察:watch -n 1 free -m,每秒刷新一次
返回信息:

Every 1.0s: free -m                                                                                                                                                                                                  2024-07-29 15:28:46

              total        used        free      shared  buff/cache   available
Mem:          15947        2838        8273        3096        4835        8673
Swap:             0           0           0

在下载过程中,free与available都在逐渐减小

Every 1.0s: free -m                                                                                                                                                                                                  2024-07-29 15:28:02

              total        used        free      shared  buff/cache   available
Mem:          15947        6135         148        3096        9662          11
Swap:             0           0           0


nginx报错:2024/07/29 15:28:03 [alert] 6525#6525: worker process 4061 exited on signal 9
此时free内存又上升了

Every 1.0s: free -m                                                                                                                                                                                                  2024-07-29 15:28:03

              total        used        free      shared  buff/cache   available
Mem:          15947        3865        6287        3096        5794        6169
Swap:             0           0           0

发现下载客户端也报错了,设备也没有进行重启, 即未复现了。

增加并发量:20个用户同时下载800MB文件,又复现了该问题
最后显示的内存信息:

Every 1.0s: free -m                                                                                                                                                                                                  2024-07-29 15:37:20

              total        used        free      shared  buff/cache   available
Mem:          15947        4790         134        3096       11021           0
Swap:             0           0           0

此时available 值为0
设备自动重启后查看nginx的error日志,也发现了
2024/07/29 15:37:22 [alert] 6525#6525: worker process 25749 exited on signal 9

使用命令查看:dmesg | grep -i memory

root@localhost:~# dmesg | grep -i memory
[  727.746017] Out of memory: Kill process 6533 (nginx) score 102 or sacrifice child
[ 2409.509693]  ? out_of_memory+0x1a7/0x490
[ 2409.509879] Out of memory: Kill process 4062 (nginx) score 148 or sacrifice child
[ 2657.723430]  ? out_of_memory+0x1a7/0x490
[ 2657.723619] Out of memory: Kill process 4061 (nginx) score 143 or sacrifice child
[ 3214.595561]  ? out_of_memory+0x1a7/0x490
[ 3214.595749] Out of memory: Kill process 32282 (ncore) score 114 or sacrifice child
[ 3214.846882]  ? out_of_memory+0x1a7/0x490
[ 3214.847071] Out of memory: Kill process 32282 (ncore) score 114 or sacrifice child
[ 3215.073249]  ? out_of_memory+0x1a7/0x490
[ 3215.073428] Out of memory: Kill process 32282 (ncore) score 114 or sacrifice child
[ 3216.056706]  ? out_of_memory+0x1a7/0x490
[ 3216.056886] Out of memory: Kill process 25749 (nginx) score 70 or sacrifice child

3、IO资源耗尽:

原因分析:

java操作导致内存占用持续增加
1、堆内存:由于设置了堆内存溢出生成dump文件,但是复现时并未生成该文件
2、栈内存:用同样的并发量测试小文件下载,并没有出现异常,栈内存应该没有问题。
3、元空间:启动命令设置:-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=128M
4、直接内存

-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8257 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=x.x.x.x

开启jmx,用于远程监控,监控发现元空间正常。

那么有可能是直接内存的问题,查看下载的代码:

ServletOutputStream outputStream = httpServletResponse.getOutputStream();
IOUtils.copyLarge(inputStream, outputStream);

getOutputStream() 方法返回的输出流对象 (ServletOutputStream) 通常是一个普通的 Java 对象,存储在堆内存中。

如果你在使用的输出流实现了直接内存(通常通过 ByteBuffer.allocateDirect() 来实现),那么数据可能会存放在 Java 的直接内存中。输出流如 ServletOutputStream 可能会使用底层的 I/O 库,这些库可能会使用直接内存来提高性能。
但是IOUtils.copyLarge并没有使用直接内存,而是使用的大小为8192的数组作为缓冲区。

排查3:
将项目复制到自己虚拟机上,使用相同并发量测试,并使用free监控内存变化,发现 free内存与available内存一直保持在稳定状态,最终文件下载成功。所以内存不是java项目占用的。

排查4:
执行top命令后,按s,监控RSS的变化,发现nginx的一个进程飙升。

Mem total:16329908 anon:3377392 map:3205580 free:162544
 slab:134520 buf:196564 cache:10758988 dirty:96 write:0
Swap total:0 free:0
  PID   VSZ^VSZRW^  RSS (SHR) DIRTY (SHR) STACK COMMAND
 6537 1832m 1815m 1480m  7556 1480m  7552   132 nginx: worker process

确定是nginx吃内存
修改nginx配置:

location /downloads/ {  
    proxy_buffering off;  # 关闭缓存
}

关闭缓存后,10的并发量下载确实正常了,但是20的并发量测试的时候,项目中出现有关数据库的连接超时的异常,经过定位发现是由于下载时一直开着事务,而数据库连接池的默认大小是10,导致后续的请求无法获取到数据库连接(刚开始一直以为是由于内存的问题,导致数据库操作无法正常进行,后续发现内存还未升高时,数据库操作也失败,才开始定位数据库连接异常的问题)。

当关闭缓存时,Nginx不再将文件内容存储在内存中,而是直接从磁盘或上游服务器传输到客户端。这意味着,如果客户端的下载速度较慢,Nginx可能需要维持打开的连接状态更长时间,从而占用更多的内存资源(可能会影响大文件下载)。

proxy_max_temp_file_size指令用于设置单个临时文件的最大允许大小。如果代理服务器的响应内容超过了这个值,Nginx将停止将更多内容写入临时文件,而是选择与上游服务器同步传递内容,而不是继续缓冲到硬盘.

proxy_temp_file_write_size指令定义了一次访问能写入临时文件的大小。这个值通常是proxy_buffer_size和proxy_buffers中设置的缓冲区大小的两倍。当临时文件的内容达到这个限制时,Nginx会等待现有数据被传输出去后再继续写入新的数据

排查4:
在排查3中,部署到虚拟机上测试时,并没有配置nginx,后来配置nginx的反向代理,下载时used内存一直处于小幅度波动,属于稳定状态。
甚至将排查3中认为是有关buffer的配置也复制到虚拟机的nginx配置中,下载内存还是稳定状态。
最后,领导说将设备上看不懂的nginx配置都注释掉,再次进行测试,发现此时下载内存处于稳定状态了。
通过控制变量,最后确定是由于modsecurity on;这个配置导致内存一直不释放,used内存持续增长。

反思与总结:

1、对linux的命令不熟悉,当从逻辑上确定了项目不会持续吃内存后,没有想到去查看到底是什么服务吃内存。
当找到是nginx吃内存后,也没有想到去使用工具定位内存的具体情况(后来有了解到hcache工具,但是并没有进行安装)
2、对nginx的配置不熟悉。由于内存持续上升,一直以为是nginx有关缓存区配置的问题,通过网络搜索后,修改了相关配置:

proxy_max_temp_file_size 0m;
proxy_buffers 4 128k; 
proxy_busy_buffers_size 256k;

甚至是关闭了缓存:proxy_buffering off;,发现现实结果与理论中的结果不一致,内存还是持续上升。由于之前没有测试过这几个参数的使用,并不确定是配置配错了还是没生效。
3、对报错没有认真分析,java有关数据库的报错,没有及时分析报错内容,有一定程度上误导了解决问题的方向。但是收获就是对proxy_buffers相关配置更加了解了。
4、考虑不周全:当使用虚拟机进行测试时,只考虑到了buffer相关配置,没有考虑到将其他配置进行对比测试。有一定程度上是由于总结3中的分析结果导致的方向错误。
如果这里能相信自己的配置没有问题的话,就应该排除是buffer的影响了,要去排查其他配置了。还是领导排查能力强。
5、modsecurity on:
ModSecurity是一个开源的Web应用防火墙(WAF),它通过配置和规则来提供安全保护。在ModSecurity中,规则是核心组成部分,它们决定了如何处理HTTP流量,以检测和阻止恶意活动。

常见的规则配置场景

  • 防御SQL注入攻击:通过检查请求参数中的特定模式来阻止SQL注入尝试。
  • 防止跨站脚本攻击(XSS):检测请求中的脚本代码,并阻止这些代码被执行。
  • 文件上传防护:限制允许上传的文件类型,防止上传潜在危险的文件。
  • IP地址黑名单/白名单:允许或阻止来自特定IP地址的流量。
  • HTTP协议违规检测:确保HTTP请求遵守既定的协议规范。

由于此处是下载大文件的问题,主要看防火墙规则中有关响应体的配置

SecResponseBodyAccess On     # 启用了ModSecurity对HTTP响应体的访问
SecResponseBodyMimeType text/plain text/html text/xml  # 指定了ModSecurity将检查响应体的MIME类型。
SecResponseBodyLimit 524288  # 设置了ModSecurity可以缓存的最大响应体大小,单位为字节。这里的数值表示524,288字节,即512KB。
SecResponseBodyLimitAction ProcessPartial # 当响应体超过预设限制时的处理行为。ProcessPartial选项指示ModSecurity处理它已经收到的响应体部分,并允许剩余的部分继续传递。

其他的规则也看不懂,也没发现对响应体做了什么规则检测,不知道为什么一直占用内存。啊啊啊啊啊

询问ai后,给的结果:
当SecResponseBodyAccess On指令被激活后,ModSecurity Web应用防火墙会在响应体发送到客户端之前对其进行缓冲和分析。这个过程涉及到以下几个方面的检测:

  • 响应内容检测:ModSecurity会检查响应体中是否含有潜在的安全威胁,如SQL注入、跨站脚本(XSS)、敏感信息泄露等。这是通过内置的规则集或自定义规则来实现的。

  • 响应状态码和头部分析:ModSecurity可能会检查响应的HTTP状态码和头部信息,以确保它们符合预期,并且没有被篡改。

  • 响应内容大小限制:通过SecResponseBodyLimit指令设置的响应体大小限制,ModSecurity可以防止过大的响应体被发送,这可能是由于恶意活动或系统配置错误造成的。

  • 性能影响:启用SecResponseBodyAccess会增加内存消耗和响应延迟,因为ModSecurity需要在内存中缓冲整个响应体以进行分析.

  • 日志记录:ModSecurity会记录有关响应体的安全事件,这些信息可以用于事后分析和安全审计。

  • 响应内容修改:在某些情况下,ModSecurity可能会修改响应内容,以移除或替换掉检测到的恶意内容,这通常是通过配置规则来实现的.

综上所述,SecResponseBodyAccess On的激活允许ModSecurity对HTTP响应进行深度检查,以增强Web应用的安全性。

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值