本文基于OpenJDK 11
最近使用Spring Cloud Gateway
的时候,遇到了一个奇怪的问题:
线上有3个 API 网关实例,压力均衡,平稳运行3天后,突然有一个实例,CPU飚高,并且响应时间增加很多,从几十毫秒涨到了几分钟。
线上是 k8s 管理容器,立刻停掉了这个 pod,重建,恢复正常。
线上我们开启了 JFR
记录(可以参考我的另外系列文章:Java 监控 JFR),通过 JMC 查看下出问题的 JFR 记录。
首先我们来看 GC,我们的 GC 算法是 G1,主要通过 G1 Garbage Collection这个事件查看:
发现 GC 全部为 Young GC,且耗时比较正常,频率上也没有什么明显异常。
接下来来看,CPU 占用相关。直接看 Thread CPU Load 这个事件,看每个线程的 CPU 占用情况。发现reactor-http-epoll
线程池的线程,CPU 占用很高,加在一起,接近了 100%。
这些线程是 reactor-netty 处理业务的线程,观察其他实例,发现正常情况下,并不会有这么高的 CPU 负载。那么为啥会有这么高的负载呢?通过 Thread Dump 来看一下线程堆栈有何发现.
通过查看多个线程堆栈 dump,发现这些线程基本都处于 Runnable,并且执行的方法是原生方法,和StackWalker
相关,例如:
1 | 复制代码"reactor-http-epoll-2" #75 daemon prio=5 os_prio=0 cpu=25100145.64ms elapsed=306507.26s tid=0x0000556eddcbd000 nid=0x61 runnable [0x00007f8605443000] |
1 | 复制代码"reactor-http-epoll-4" #79 daemon prio=5 os_prio=0 cpu=25266372.53ms elapsed=306415.58s tid=0x0000556eddcc4800 nid=0x65 runnable [0x00007f85e2ff9000] |
主要和这两个原生方法有关:
java.lang.StackStreamFactory$AbstractStackWalker.callStackWalk
java.lang.StackStreamFactory$AbstractStackWalker.fetchStackFrames
虽然一直有OpenJDK 11 之后, StackWalker
性能有问题,不如new Throwable()
获取堆栈快的问题。但是考虑到压力是均衡的,其他两个实例并没有这个问题,应该不是本身对于这个类的应用,导致的CPU消耗突然变大。查看 JDK 相关的 JIRA,发现一个有意思的 BUG:Application on JDK11 consume 100% CPU after a few hours of uptime
这里面说,java.lang.StackStreamFactory$AbstractStackWalker.callStackWalk
这个原生方法,在 JVM 运行几小时后,突然 CPU 就会飚高到 100%。看来可能确实有些问题。针对这个问题,我也提了个 Issue 到 Log4j 的 JIRA:High CPU consumption using StackWalker
那么为什么会调用这个类呢?Log4j2 打异步日志的时候,如果需要保留日志产生的类还有行号,需要缓存堆栈,那么需要配置includeLocation = true
。这个堆栈,如果环境是 java 9 之前,那么通过 new Throwable()
实现, 参考:StackLocator.java 如果环境是 java 9 之后,那么通过 StackWalker
实现:StackLocator.java 这个如果你配置了includeLocation = true
,就会缓存堆栈。
对于网关,我们可以不用打印类和行号,配置includeLocation = false
可以避免再出现类似的问题。
以后在应用中,
本文转载自: 掘金