很多人看到监控里的 Load Average 飙升,就认为CPU不够用了。但负载高和CPU高并不能直接画等号。线上很多性能问题,最后查出来和CPU几乎没有关系。
之前有遇到过一次线上告警,监控显示服务器负载从2一路涨到30,接口响应时间也明显变慢。开发同事第一时间怀疑是CPU打满了,结果登录服务器一看:
top
CPU利用率不到20%。负载30,CPU却很空闲,这种情况并不少见。后来排查发现,真正的问题是磁盘IO异常,大量进程都在等待磁盘响应,最终导致负载不断升高。
这也是很多人容易误解的地方。
Load到底是什么?
CPU利用率表示CPU有多忙,而Load Average表示系统里有多少任务正在运行或者等待资源。负载高意味着有很多任务在排队,但这些任务不一定是在等CPU。它们也可能在等磁盘、等网络、等锁资源,甚至在等待某个外部服务返回结果。
所以当负载升高时,第一步不是扩容CPU,而是先搞清楚到底是谁在拖后腿。
磁盘IO问题是最常见的原因
线上最容易遇到的情况就是磁盘性能瓶颈。例如数据库突然产生大量写入、日志异常增长、备份任务正在执行,都会导致磁盘响应变慢。这时候大量进程会进入等待状态,CPU可能很闲,但负载会不断上涨。
排查时可以执行:
iostat -x 1
重点看await和util两个指标。如果await持续升高,说明请求正在等待磁盘处理;如果util长期接近100%,说明磁盘已经接近满负荷运行。
网络阻塞也会导致高负载
现在大多数业务都是微服务架构,一个请求往往需要经过数据库、缓存、消息队列以及多个服务接口。如果其中某个依赖响应变慢,业务线程就会被阻塞。比如数据库连接超时、Redis响应缓慢、第三方接口卡顿,都可能导致大量请求堆积。
这种情况下CPU依然可能很低,但系统响应时间会越来越长,负载也会不断升高。可以通过下面的命令查看连接状态:
ss -s
如果连接数量异常增长,或者大量连接长时间无法释放,就需要进一步分析网络和应用层问题。
应用锁竞争也会拉高负载
还有一种情况经常出现在Java应用里。服务器资源看起来都正常,但接口响应时间却越来越慢。最后发现并不是系统问题,而是程序内部出现了锁竞争。例如数据库锁等待、线程锁冲突、连接池耗尽等问题。
线程虽然还活着,但实际上都在等待资源释放。从系统角度看,大量线程都处于运行状态,因此负载会上升,但CPU利用率却不一定高。这时候就需要结合线程栈、数据库状态以及应用日志一起分析。
不要忽略异常进程
有时候问题更简单。某个脚本进入死循环、批处理任务异常、程序不断创建子进程,都可能导致系统负载异常。这类问题可以通过:
ps -ef
或者:
top -H
查看具体进程和线程情况。很多看起来很复杂的故障,最后可能只是某个异常任务没有正常退出。
正确的排查思路是什么?
当发现系统负载升高时,不要急着重启服务,更不要第一时间扩容服务器。先用top看CPU和负载情况,再通过vmstat判断系统是否存在资源等待。如果怀疑磁盘问题,就查看iostat;如果怀疑网络问题,就检查连接状态;最后结合应用日志和线程信息分析程序是否存在阻塞。
大部分线上性能问题,其实都能通过这个思路找到原因。负载只是结果,而不是根因。
真正重要的是提前发现问题
很多企业在收到用户投诉之后,才发现服务器负载已经持续高了几个小时。实际上,大部分故障在发生之前都会有征兆。磁盘IO开始升高、连接数持续增长、线程池逐渐耗尽,这些指标往往比负载更早出现异常。因此相比事后排查,更重要的是建立一套完善的监控和告警体系。
据了解,像江苏立维这样的运维服务商,在为企业提供云运维和业务稳定性服务时,会持续监控服务器、数据库、中间件以及应用运行状态,对负载、磁盘IO、连接数、线程池等关键指标进行统一监控和告警分析。对于研发团队规模不大的企业来说,很多问题并不是解决不了,而是发现得太晚。
所以,下次再看到系统负载升高时,别急着把锅甩给CPU。真正的问题,有可能在磁盘里,在网络里,也有可能就在应用程序自己身上。