java pod 启动 前3分钟 CPU 占用接近100%

82 阅读3分钟

背景: 商业系统 RPS ,也可以简单的理解成 QPS 每秒 30万,k8s 部署 20个Pod, 每个pod 平均每秒1万5的请求。pod 配置 8C16G。

症状: 监控系统显示域名可用性降低

image.png

排查: 每次发布后,前3分钟cpu 占用高 image.png

CPU为什么高,哪些线程在消耗CPU?

第一种方法:不推荐比较繁琐

image.png 第二种方法:github.com/oldratlee/u… 这个其实很好,团队没有运维的自己可以试试。

第三种方法:运维搞的Arthas,命令 : dashboard 直接打开看监控

image.png

image.png 原来是java Jit 编译线程导致的。要注意下code_cache ,这是jit 编译后的缓存,占满了也不行,要扩大。

解决方案一:修改 JIT 编译参数,最终没什么效果。

增加JIT编译打印日志 -XX:+PrintCompilation

更详细的日志: -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation

jps 命令不知道的,先去学习下这部分知识吧。 jps 查看pid

调整编译器线程数量 -XX:CICompilerCount=4

查看编译线程数量 jinfo -flag CICompilerCount pid

启用分层编译 -XX:+TieredCompilation 查看是否启用分层编译 jinfo -flag TieredCompilation
如果输出为: -XX:+TieredCompilation 则表示分层编译已启用 -XX:-TieredCompilation 则表示分层编译已禁用

调整编译任务超时时间 -XX:TieredCompileTaskTimeout 50(默认值)单位为毫秒。

查看是几层分层编译 jinfo -flag TieredStopAtLevel pid

查看所有 JVM 参数及其值 jcmd pid VM.flags

设置非分层编译模式方法被编译的调用次数阈值

-XX:CompileThreshold

默认值:C1 编译器 1500, C2 编译器 server 模式是 10000,clent 模式默认是5000。 说明:当方法的调用次数达到这个阈值时,JIT 编译器会将该方法编译为优化的机器码。这个参数适用于非分层编译模式。

设置分层编译模式方法被编译的调用次数阈值:

-XX:Tier4MinInvocationThreshold:方法被调用的最小次数,达到该次数后才会被考虑编译。 默认值:1000
-XX:Tier4InvocationThreshold:方法被调用的次数,达到该次数后会被编译。 默认值:1500。
-XX:Tier4CompileThreshold:方法被编译的次数,达到该次数后会被优化。 默认值:2000
-XX:MaxInlineLevel 参数限制方法内联的深度。内联深度越大,编译器生成的代码越复杂,CPU 占用也会越高。默认9
-XX:ReservedCodeCacheSize 代码缓存的大小 默认值通常是 240m

解决方案二:代码预热。 查出3分钟内调用次数最多的接口和代码。 然后在服务启动后,使用代码调用这些接口几千次,触发jit编译。 调用完成后,通知k8s就绪检查,程序已经就绪,可以放流量进来。

查询里加入一个fiter ,打印日志,用于请求数据的统计。

日志格式: 时间 ip 域名 端口 GET 请求路径

写个python 程序统计日志文件里3分钟内,每个接口的总数

image.png

上得到需要预热的接口,编写预热插件,写个 spring-boot-starter,集成进去

写个就绪检查接口,k8s 每隔几秒请求一次这个接口,接口里去查询预热是否完成。

image.png

最终:CPU 平稳啦

image.png