前言
前几日早上打开邮箱收到一封监控报警邮件:某某 ip 服务器 CPU 负载较高,请研发尽快排查解决,发送时间正好是凌晨。
其实早在去年我也处理过类似的问题,并记录下来:《一次生产 CPU 100% 排查优化实践》
不过本次问题产生的原因却和上次不太一样,大家可以接着往下看。
问题分析
收到邮件后我马上登陆那台服务器,看了下案发现场还在(负载依然很高)。
于是我便利用这类问题的排查套路定位一遍。
首先利用 top -c
将系统资源使用情况实时显示出来 (-c
参数可以完整显示命令)。
接着输入大写 P
将应用按照 CPU
使用率排序,第一个就是使用率最高的程序。
果不其然就是我们的一个 Java
应用。
这个应用简单来说就是定时跑一些报表使的,每天凌晨会触发任务调度,正常情况下几个小时就会运行完毕。
常规操作第二步自然是得知道这个应用中最耗 CPU
的线程到底再干嘛。
利用 top -Hp pid
然后输入 P
依然可以按照 CPU
使用率将线程排序。
这时我们只需要记住线程的 ID 将其转换为 16 进制存储起来,通过 jstack pid >pid.log
生成日志文件,利用刚才保存的 16 进制进程 ID
去这个线程快照中搜索即可知道消耗 CPU
的线程在干啥了。
如果你嫌麻烦,我也强烈推荐阿里开源的问题定位神器 arthas
来定位问题。
比如上述操作便可精简为一个命令 thread -n 3
即可将最忙碌的三个线程快照打印出来,非常高效。
更多关于 arthas 使用教程请参考官方文档。
由于之前忘记截图了,这里我直接得出结论吧:
最忙绿的线程是一个 GC
线程,也就意味着它在忙着做垃圾回收。
GC 查看
排查到这里,有经验的老司机一定会想到:多半是应用内存使用有问题导致的。
于是我通过 jstat -gcutil pid 200 50
将内存使用、gc 回收状况打印出来(每隔 200ms 打印 50次)。
从图中可以得到以下几个信息:
Eden
区和 old
区都快占满了,可见内存回收是有问题的。
fgc
回收频次很高,10s 之内发生了 8 次回收((866493-866485)/ (200 *5)
)。
- 持续的时间较长,fgc 已经发生了 8W 多次。
内存分析
既然是初步定位是内存问题,所以还是得拿一份内存快照分析才能最终定位到问题。
通过命令 jmap -dump:live,format=b,file=dump.hprof pid
可以导出一份快照文件。
这时就得借助 MAT
这类的分析工具出马了。
问题定位
通过这张图其实很明显可以看出,在内存中存在一个非常大的字符串,而这个字符串正好是被这个定时任务的线程引用着。
大概算了一下这个字符串所占的内存为 258m 左右,就一个字符串来说已经是非常大的对象了。
那这个字符串是咋产生的呢?
其实看上图中的引用关系及字符串的内容不难看出这是一个 insert
的 SQL
语句。
这时不得不赞叹 MAT
这个工具,他还能帮你预测出这个内存快照可能出现问题地方同时给出线程快照。
最终通过这个线程快照找到了具体的业务代码:
他调用一个写入数据库的方法,而这个方法会拼接一个 insert
语句,其中的 values
是循环拼接生成,大概如下:
<insert id="insert" parameterType="java.util.List">
insert into xx (files)
values
<foreach collection="list" item="item" separator=",">
xxx
</foreach>
</insert>
所以一旦这个 list 非常大时,这个拼接的 SQL 语句也会很长。
通过刚才的内存分析其实可以看出这个 List
也是非常大的,也就导致了最终的这个 insert
语句占用的内存巨大。
优化策略
既然找到问题原因那就好解决了,有两个方向:
- 控制源头
List
的大小,这个 List
也是从某张表中获取的数据,可以分页获取;这样后续的 insert
语句就会减小。
- 控制批量写入数据的大小,其实本质还是要把这个拼接的
SQL
长度降下来。
- 整个的写入效率需要重新评估。
总结
本次问题从分析到解决花的时间并不长,也还比较典型,其中的过程再总结一下:
- 首先定位消耗
CPU
进程。
- 再定位消耗
CPU
的具体线程。
- 内存问题
dump
出快照进行分析。
- 得出结论,调整代码,测试结果。
最后愿大家都别接到生产告警。
你的点赞与分享是对我最大的支持
相关推荐
本文记录了一次线上容器因内存占用过高而自动扩容的过程。该事件发生在一个部署于腾讯TAF平台的Node.js服务上,该服务主要负责接口查询及使用socket.io进行通信。尽管服务功能简单且并未遇到大流量或高并发的压力,...
2. CPU存储器复位与完全再启动:通过模式开关进行,复位有助于消除临时性问题,而完全再启动则会执行一次完整的系统初始化。 3. 使用SIMATIC管理器进行监控:可以检查输入输出的状态,如数字量模块上的LED指示灯,...
- 实践案例:在检查一台经常自动关机的计算机时,发现CPU散热器温度异常高,更换新的散热器后问题得以解决。 7. **对比法**:将故障设备与相同型号的正常设备进行对比,查找差异点。 - 实践案例:在维修一台空调...
1. **第一次握手**:客户端向服务器发送SYN报文,请求建立连接。 2. **第二次握手**:服务器接收到SYN报文后,向客户端发送SYN+ACK报文,确认客户端的请求。 3. **第三次握手**:客户端收到SYN+ACK报文后,再发送ACK...
4. **监控CPU使用率**:如果CPU占用率过高,且启用了CPU监视功能,当利用率超出设定阈值时,应用程序池会被关闭。优化服务端代码以降低CPU占用是根本解决办法。 对于**普通访问者**: 1. **刷新页面**:503错误...
16. 其他特殊故障如A32、A03、ABF和AC8等,涉及回生过载、主电路译码器异常、系统报警和绝对值编码器多次转动设定异常,需根据具体故障代码的定义和手册指示进行排查。 在处理这些故障时,首先要确保安全,断开电源...
- **应对措施**:进一步排查服务器配置和硬件资源,确保其能满足高并发场景下的需求。 **5. 系统稳定性分析** - **表现形式**:如果事务响应时间先缓慢上升后趋于平缓,再突然下降,这通常表示系统稳定性下降。 ...
同一组内的消费者互相竞争消息,确保每条消息只被消费一次。 6. **Kafka命令行工具**:在测试中,通常会使用`kafka-console-producer.sh`和`kafka-console-consumer.sh`来发送和接收消息,以及`kafka-topics.sh`和`...
23. 变量表(VAT)窗口中的图标变量,当单击时,可以每个扫描周期刷新变量一次,确保数据实时更新。 24. 不能以位为单位存取的存储区是内部存储器,通常以字节或字为单位进行存取。 25. 通电延时定时器SD线圈接通时...
- 异步或队列处理:对于大量文件操作,可以采用异步处理或排队,避免一次性占用过多资源。 综上所述,FileLocker.zip提供的FileLocker.exe工具可以帮助用户进行Windows环境下的文件占用测试,了解应用程序如何处理...
2. **LVS(Linux Virtual Server)负载均衡**:LVS是一种开源的负载均衡技术,提供IP负载均衡和TCP负载均衡,其策略包括轮询(Round Robin)、最少连接(Least Connections)、源IP哈希(Source IP Hash)等。...
如果没有特别设置,`sa1`会每日运行一次,确保每天的数据被独立存储,便于后续分析。而`sa2`脚本则在每天结束时运行,读取当天的二进制日志文件,利用`sar`命令将其转换为文本格式,再存入相应的文本文件,同时清理...
- **CountDownLatch 和 CyclicBarrier**:两者都是并发工具类,CountDownLatch用于一次性释放多个线程,CyclicBarrier则允许一组线程等待彼此到达某个点再继续执行。 2. **数据库(MySQL)**: - **索引**:包括...
这个时候我们可以通过产品逻辑的方式来优化,比如,在用户点击查询之后将“按钮置灰”,或者通过 JS 控制 xx 秒只能只能提交一次请求等,有效的拦截了 80% 的无效流量。 四、服务化 服务化是将系统拆分成多个独立...
21. **变压器运行模式**:一次侧绕组接电源,二次侧负载运行称为负载运行。 22. **数控系统故障排查**:地址线无时序可能涉及地址线驱动器、地址线逻辑、存储器周边电路和CPU及周边电路。 23. **接触电压触电**:...
3. **服务器运维经历**:描述一次印象深刻的运维经历,可以展现处理复杂问题的能力和经验。 4. **安全问题处理**:面对服务器安全问题,应先进行日志分析、安全扫描,定位问题源头,然后修复漏洞、加固系统,必要时...
这种攻击方式利用了TCP三次握手过程中的漏洞,因为建立一个完整的TCP连接需要客户端发送SYN、服务器回应SYN+ACK,然后客户端再发送ACK,但攻击者只发送SYN而不回应,造成服务器端等待超时,资源被持续占用。...
第一次中断是因为VRRP状态切换过程中,第二次中断是因为新的Master路由器需要重新建立三层转发表项。 **解决方案:** - 检查导致接口down的原因,并及时修复。 - 考虑采用更快的故障检测机制,如BFD(Bidirectional...
在计算机科学中,进程是程序的一次执行实例,包含了程序的运行状态和资源分配。每个打开的应用程序、服务或后台运行的程序都是一个进程。通过进程管理器,我们可以查看这些进程的详细信息,包括它们的ID、内存占用、...