记一次线上CPU100%的问题排查记录

某一天中午,线上突然收到告警,提示某一个服务的部分集群 CPU 使用率长时间超出100%,

由于我们是三集群部署,假设三个集群分别是A、B、C,其中 ABC 都可以被下游服务 S1 调用,而 B 集群是由于是就近访问,只能被 S2 服务调用,问题恰好出现在 B 集群。

该服务是一个典型的IO密集型应用,平时的CPU基本处于10%以下

查看服务的发版记录,发现最近一次发版记录是一天前,所以初步排除是最近的需求导致的。

太长不看版,直接说原因

用 Java 进行解压缩流的时候,有一个空循环,在流的数据出现损坏的时候,会出现空循环。

开始排查

排查思路

1)请求量太大?

由于是一天前发版本后再也没有进行变更,所以首先猜测是突然增加的请求量导致的,查看后请求量十分的平稳,无明显波动。

于是决定先恢复业务,重新按批次发布B集群,但是在新发布的几台机器上,CPU也迅速被打满了,这个时候情况就变的很紧急了。

2)Redis中含有大Key?

此时另一个小伙伴说Redis连接有异常,部分超时,但是 Redis 连接异常是导致CPU被打满的原因吗?不是。

首先项目中是使用连接池在管理 Redis 的连接,所以不会出现无限创建连接的情况,其次如果是Redis导致的CPU打满,那么会是什么原因呢?

大Key:序列化、反序列化

大Key 的序列化和反序列化,虽然会导致 CPU 使用率上升,但是也一定会导致 GC 的上升。但是那么服务当时GC非常正常,所以暂时可以排出这个原因。

但是 CPU 被打满以后,是可以导致Redis连接出现异常的

现代 CPU 的调度是分时调度,如果某一个线程已运行时间太长,是会减少其他线程的使用时间的,而恰好如果是 Redis 的线程时间被挤占了,超出了socketTimeOut时间,那么就会出现超时。

3)线程Dump

于是查看线程,发现有几个线程占用 CPU时间非常长,而且代码一直在读取流,

image-20240413152539082

查看出现死循环的代码如下:

1
2
3
4
while (!inflater.finished()) {
int count = inflater.inflate(buff);
baos.write(buff, 0, count);
}

解决方案也很简单:

1
2
3
4
5
6
7
while (!inflater.finished()) {
int count = inflater.inflate(buffer);
if (count == 0) {
break;
}
baos.write(buffer, 0, count);
}

解决方案参考:java-inflater-will-loop-infinitely-sometimes

最后

遇到 CPU 打满的问题,可以先看看请求量,如果没有请求量的突增,基本就是代码出现了死循环

作者

Somersames

发布于

2024-04-04

更新于

2024-04-13

许可协议

评论