云主机应用崩溃排查指南?
- 来源:纵横数据
- 作者:中横科技
- 时间:2026/6/8 14:31:55
- 类别:新闻资讯
说实话,干我们这行的,最怕半夜手机响。那种感觉,就像是有人在你的神经上弹了一记脑瓜崩。上个月某个周末,我正陪孩子搭积木,电话就炸了。客户那边说,业务系统又崩了,这已经是本周第三次了。电话那头声音很急,我这边积木塔也跟着塌了。
没办法,赶紧打开电脑,连上云主机。屏幕上的报错信息密密麻麻的,应用进程已经退出了,只剩下系统还在勉强喘气。这种场景,做运维和开发的兄弟应该都不陌生。今天就跟大家聊聊,在云主机上遇到应用崩溃,到底该怎么下手。不是那种教科书式的教条,都是这些年一步一步踩坑踩出来的经验。
先说说我总结的一个基本心法,叫“先看系统后看应用,先看资源后看日志”。很多人一上来就翻应用日志,这个习惯其实不太好。日志记录的是应用视角发生的事情,但很多时候应用崩溃是因为操作系统层面的资源被耗尽了。不从根上看,光盯着日志容易走偏。
那次帮客户排查的过程就很典型。登录云主机后,我第一个命令敲的是free。为什么先看内存呢,因为根据客户描述,应用运行一段时间就会变慢,然后突然崩溃,重启之后又能撑一阵子,这个症状特别像内存泄漏。果然,free一看,四GB的内存已经用得差不多了,available那一栏只有不到两百兆。再用top看一下进程列表,按内存排序,一个Java进程稳稳地排在第一位,物理内存占用超过了三个GB。
这时候心里大概有数了,但还不能急着下结论。我用top再看了下CPU,发现这个进程的CPU使用率也不低,而且大部分是系统态的CPU。这个细节很有意思,系统态CPU高通常意味着进程在做大量的文件IO或者网络IO,结合内存占用异常,我猜可能跟GC有关。Java应用的垃圾回收如果频繁触发,会占用大量CPU,而且如果GC线程一直回收不了内存,进程就会慢慢卡死。
为了验证这个猜测,我用jstat看了一下GC的情况。结果让人触目惊心,Full GC在短短几分钟内触发了上百次,每次停顿时间都在几百毫秒以上。更关键的是,每次Full GC之后,堆内存的使用量几乎没有下降。这就坐实了,确实是内存泄漏。后面的步骤就是生成heap dump文件,用分析工具找出来到底是哪个对象一直在增长。最后定位到的是一个缓存模块,开发同事用了静态集合来存储临时数据,但忘了在合适的时机清理。这个案例给我的启发是,排查崩溃不能只看表象,要顺着线索一层一层往深处挖,直到找到确凿的证据。
除了内存问题,CPU被打满也是特别常见的崩溃诱因。我另一个客户是做电商导购的,他们有一次搞促销活动,流量涨了大概三倍,结果应用在活动开始后半小时就崩溃了。重启之后能撑几分钟,然后又崩,反复了好几次。我登录进去一看,CPU使用率百分之百,四个核全部跑满。load average更是夸张,飙到了十五以上。
这种情况,首先要区分是计算密集型的任务导致的CPU高,还是程序逻辑有问题导致的死循环。我用perf命令采样了一下,发现大部分的CPU时间都消耗在一个序列化函数上。顺着这个线索查代码,发现他们在每个请求里都会把一个很大的对象图用JSON序列化一次,而且这个序列化是同步执行的。对象图里面还嵌套了多层循环依赖,序列化的开销随着数据量的增长呈指数级上升。流量一上来,CPU直接就烧穿了。解决方法其实不复杂,一个是把序列化结果缓存起来,避免重复计算,另一个是把大对象的序列化改成异步或者延迟加载的方式。改完之后,同样的流量下CPU使用率降到了百分之五十以下,应用再也没有崩溃过。
还有一种让人特别头疼的情况,就是应用崩溃之后没有任何报错日志,进程就这么悄无声息地消失了。我一个朋友遇到的就是这种。他们有一套定时任务系统,每天晚上跑数据处理,但经常跑到一半就没了,日志里干干净净,什么都没有。他们排查了好几周都没找到原因,一度以为是云主机不稳定。
我帮着他一起看的时候,首先想到了Linux的OOM Killer机制。当系统内存不足时,内核会挑一个占用内存最多的进程杀掉,而且默认情况下不会记录详细的日志,只在dmesg里留一条简短的记录。我们查看dmesg,果然发现了线索,内核在崩溃时间点附近把他们的Java进程杀掉了,原因就是内存不足。但奇怪的是,他们当时的内存配置看起来是够用的,为什么会触发OOM呢。
继续深挖,发现他们的任务会从一个很大的数据库表里面拉取数据,然后加载到内存里做处理。正常情况下每天增量数据不大,但那天上游系统出了一些问题,产生了一大批异常数据,数据量是平时的十倍。应用没有对数据量做边界检查,直接全部加载进来,内存瞬间就爆了,触发了OOM Killer。知道原因之后,他们在代码里增加了数据量的前置检查,同时改成了分批处理的方式,每次只加载一小批数据到内存,处理完再加载下一批。从那以后,这个问题再也没有出现过。
网络层面的问题也容易引起崩溃,而且往往比较隐蔽。有一个做实时通信的服务,他们发现每到业务高峰期,就有部分连接会被突然断开,然后整个进程变得极其不稳定,有时还会崩溃。我们开始在业务高峰的时候盯着云主机的各项指标看,发现网络收发包的pps在崩溃前会突然蹿升到一个很高的值,然后应用就开始异常。
仔细检查了应用的网络模型,发现他们用的是传统的同步阻塞IO,每个连接都分配一个独立的线程。虽然总连接数不算太多,但每个线程都会占用一定的内核资源,而且在高并发场景下,线程切换的开销非常大。更重要的是,他们代码里有一个隐藏的bug,某些异常情况下连接没有被正确关闭,导致大量的TIME_WAIT状态的连接堆积。这些僵尸连接占用了文件描述符和本地端口,当文件描述符耗尽时,新的连接请求就会被拒绝,应用也就无法正常工作了。
解决这个问题走了两步。第一步是修复连接泄漏的bug,确保所有的连接在使用完毕后都能被正确关闭。第二步是把网络模型从同步阻塞改成了基于epoll的异步非阻塞模型,这样就大大减少了线程数量,同时也降低了上下文切换的开销。改完之后,同样的流量下,应用的稳定性和吞吐量都有了质的提升。
磁盘空间耗尽也是一个经典的老问题。有一个客户做的是图片处理服务,用户上传图片后,应用会对图片进行裁剪、压缩、加水印等一系列操作,然后生成多个尺寸的缩略图。原本跑得好好的,突然有一天,上传图片总是失败,应用也频繁崩溃。
登录云主机一看,根分区已经百分之百用满了。用du逐级排查,发现是应用的一个临时目录里面塞满了未清理的中间文件。原来他们的代码里,每处理一张图片就会在临时目录里生成几个中间文件,正常情况下处理完成后会立即删除。但如果处理过程中发生了异常,清理逻辑没有被执行,这些临时文件就永远留在了磁盘上。日积月累,把整个磁盘撑爆了。解决方案有两个层面,第一是修复代码,用try-catch-finally或者try-with-resources确保无论如何都会执行清理操作,第二是增加一个定时任务,定期清理超过一定时间的临时文件,作为兜底的保障。
还有一种崩溃是最难排查的那种,就是在测试环境一切正常,一到生产环境就崩。一个做推荐引擎的团队就被这个问题折磨了很久。他们的服务在上线之前做了充分的压力测试,各项指标都符合预期,但部署到生产环境之后,每隔几个小时就会莫名其妙地崩溃一次,而且时间点完全没有规律。
我们花了很大力气才找到真相。原来生产环境的云主机和测试环境的云主机,底层硬件存在一些差异。生产环境用的是某一种型号的CPU,而测试环境用的是另一种。他们的应用里有一段高度优化的向量计算代码,用到了AVX指令集。这种指令集在不同代际的CPU上支持程度不一样,老一些的CPU支持AVX2,新一些的还支持AVX512。他们的代码在编译时开启了自动向量化,编译器根据测试环境的CPU特性生成了特定的指令。但生产环境的CPU微架构不太一样,某些指令的执行效率极低,甚至会出现异常。在高负载下,这个性能瓶颈被无限放大,最终导致整个应用卡死崩溃。
解决这个问题的方法说起来也简单,在编译的时候指定一个通用的CPU架构目标,不要过度依赖特定型号的CPU特性。当然这会牺牲一些性能,但换来了更好的兼容性和稳定性,对于大多数业务场景来说,这个权衡是值得的。
这些年积累下来,我慢慢总结出一套适合自己的排查流程,分享给大家参考。
当应用崩溃的时候,第一件事不是急着重启,而是先尝试保留现场。如果可能的话,用gcore命令给进程做个core dump,或者用jmap生成heap dump,这些文件对于事后分析非常有价值。如果进程已经退出了,那就看看操作系统日志,dmesg和/var/log/messages里经常藏着关键信息。
第二件事是做一次快速的资源巡检。用uptime看平均负载,用free -h看内存使用,用df -h看磁盘空间,用ss -tunp看网络连接状态。这四个命令花不了几秒钟,但能帮你快速定位到到底是哪一类资源出了问题。很多时候看到这里,心里就有数了。
第三件事是根据初步判断的方向,选择合适的工具深入分析。内存相关的用valgrind、AddressSanitizer或者各种语言专属的内存分析工具,CPU相关的用perf、火焰图工具,磁盘相关的用iotop、iostat,网络相关的用tcpdump、wireshark。每个工具都有自己擅长的地方,平时多练练手,关键时刻才能信手拈来。
第四件事,如果应用确实崩溃了,把崩溃那一刻的完整上下文记录下来。包括但不限于系统版本、内核参数、运行的应用版本、依赖的中间件版本、当时的流量情况、以及所有相关的日志。这些信息越完整,事后复盘的时候就越容易找到根因。
最后想说一点心态上的话。应用崩溃这件事,真的很难完全避免。无论你的代码写得多么优雅,测试做得多么充分,生产环境的复杂性和不确定性永远存在。重要的是建立一套完善的观测和应急体系。监控告警要足够灵敏但不能太敏感,否则容易狼来了。日志要留得足够详细但不能太冗余,否则排查的时候根本看不过来。混沌工程的思想也值得借鉴,定期主动搞一些破坏,看看系统能不能扛得住,在可控的环境下暴露问题,总比在生产环境被动崩溃要好。
每次帮客户搞定一个疑难杂症,看到应用重新稳定地跑起来,用户那边的业务恢复正常,那种感觉还是很踏实的。这个工作确实累,也经常熬夜,但它让你看清了系统的每一个细节,理解了软件工程的复杂和精妙。




使用微信扫一扫
扫一扫关注官方微信 

