跳到主要内容

生产环境CPU过高如何排查?

这里我先写一个小的Demo, 开启两个线程,其中一个死循环,占满CPU

image-20240728104812770

运行:

image-20240728111733054

可以看到此时CPU明显提升了很多,单核已经满了

image-20240728111814342

我们切换到tmux的另一个会话:

使用top命令查看CPU占用情况

image-20240728111900936

可以看到有一个进程1335655已经占用100了,我们知道进程里面运行着的是线程,因此我们要去看线程的占用情况

ps H 查看对应的线程信息,-eo输出指定的一些信息

 ps h - ep pid ,tid, %cpu | grep pid 查指定进程中线程所占CPU

这里我输入:

 ps H -eo pid,tid,%cpu |grep 1335655

image-20240728112150197

也就是我们看到了这个进程对应的线程占用情况,可以看到是1335674占用达到了100

下面我们就要看这个线程的情况

jstack 1335655

注意后面是pid,而不是刚刚的线程id

image-20240728112635294

可以看到每个线程都有一个tid和nid,并且nid都是16进制,那我们现在要做的就是去找刚刚CPU占用高的线程对应的16进制是什么

16进制查看可以使用printf %x 数字

1335674对应的16进制是14617a,如图所示:

image-20240728113013511

可以看到代码是在第4行有问题,我们再回头看源码,第四行对应的正是我们while死循环的地方,而一个while循环就把我们的单核CPU跑满了。

网关排查

https://blog.csdn.net/banzizong2551/article/details/102042347

https://developer.jboss.org/thread/274758

https://blog.csdn.net/m0_37055174/article/details/115435867

网关项目:

image-20240731094903468

进入Docker容器查看:

image-20240731095054294

ps H -eo pid,tid,%cpu |grep 7

找到对应的线程:

    7    65 87.7
7 66 22.7

获取65对应的16进制printf %x 65

查看找到位置的后几行:grep -A n n表示后几行

 jstack 7 |grep 0x41 -A 10

image-20240731095708906

"XNIO-1 I/O-3" #56 prio=5 os_prio=0 tid=0x00007f5de17e1000 nid=0x41 runnable [0x00007f5d4f9fa000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x00000000c1d0ab68> (a sun.nio.ch.Util$3)
- locked <0x00000000c1d0ab58> (a java.util.Collections$UnmodifiableSet)
- locked <0x00000000c1d0aa40> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101)
at org.xnio.nio.WorkerThread.run(WorkerThread.java:532)

保存所有信息:jstack 7 > 7cpu.log

看有多少RUNNABLE线程:

[root@dev141 logs]# grep  RUNNABLE 7cpu.log |wc -l
19

找不到问题,使用https://arthas.aliyun.com/doc/quick-start.html#_2-%E5%90%AF%E5%8A%A8-arthas

image-20240731102829488

https://github.com/spring-cloud/spring-cloud-gateway/issues/1908

解决办法Gateway采用undertow带来的问题.md

排除掉:

                <exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</exclusion>

修改webclient去掉mono.block

@Override
public BaseResult<VerifyTokenResult<UserInfo>> verifyUserToken(String userToken) {
Mono<BaseResult> mono = webClient.get()
.uri(uriBuilder -> uriBuilder.path("/auth/user/verifyUserToken")
.queryParam("userToken", userToken).build())
.retrieve()
.bodyToMono(BaseResult.class);

return (BaseResult<VerifyTokenResult<UserInfo>>) mono.block();
}

修改为:

public Mono<BaseResult<VerifyTokenResult<UserInfo>>> verifyUserToken(String userToken) {
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("/auth/user/verifyUserToken")
.queryParam("userToken", userToken).build())
.retrieve()
.bodyToMono(new ParameterizedTypeReference<BaseResult<VerifyTokenResult<UserInfo>>>() {});
}