JVM调优笔记(一)–Nacos GC引发的服务批量下线问题

故障背景

线上批量发服务下线的告警邮件,偶发nacos连接超时。采用了spring boot admin(以下称sba)进行服务监控。

原因分析

因为sba服务是基于nacos对其它服务进行监控,所以遇到这个问题,第一怀疑对象是nacos发生问题,但不清楚具体是什么问题。由于服务过一段事件会恢复,所以nacos肯定是没有挂掉的,那么排查方向应该是针对nacos的配置,或者是服务器性能。

排查过程

首先查看nacos的堆情况,使用命令jmap -heap PID,得到如下信息:

Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2147483648 (2048.0MB)
   NewSize                  = 1073741824 (1024.0MB)
   MaxNewSize               = 1073741824 (1024.0MB)
   OldSize                  = 1073741824 (1024.0MB)
   NewRatio                 = 2 
   SurvivorRatio            = 8    
   MetaspaceSize            = 134217728 (128.0MB)
   CompressedClassSpaceSize = 327155712 (312.0MB)
   MaxMetaspaceSize         = 335544320 (320.0MB)
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 1058013184 (1009.0MB)
   used     = 660154960 (629.5728302001953MB)
   free     = 397858224 (379.4271697998047MB)
   62.39572152628298% used
From Space:
   capacity = 7864320 (7.5MB)
   used     = 6914048 (6.59375MB)
   free     = 950272 (0.90625MB)
   87.91666666666667% used
To Space:
   capacity = 7864320 (7.5MB)
   used     = 0 (0.0MB)
   free     = 7864320 (7.5MB)
   0.0% used
PS Old Generation
   capacity = 1073741824 (1024.0MB)
   used     = 455548152 (434.44457244873047MB)
   free     = 618193672 (589.5554275512695MB)
   42.426227778196335% used

可以看到Heap Configuration部分还是比较正常的,最大堆内存是2G,新生代和老年代各1G。Eden区和Survivor的比例是默认的8:2。但是观察Heap Usage发现了不对劲的部分,From和To区怎么只有7.5M,这点很奇怪,按道理来说是102M才对。于是发动搜索大法,找到一篇文章,描述如下:

JDK 1.8 默认使用 UseParallelGC 垃圾回收器,该垃圾回收器默认启动了 AdaptiveSizePolicy。
AdaptiveSizePolicy(自适应大小策略) 是 JVM GC Ergonomics(自适应调节策略) 的一部分。
如果开启 AdaptiveSizePolicy,则每次 GC 后会重新计算 Eden、From 和 To 区的大小,计算依据是 GC 过程中统计的 GC 时间、吞吐量、内存占用量。

于是马上查看使用的是什么垃圾回收器,使用命令jinfo -flags PID查看JVM的启动参数配置:

JVM version is 25.331-b09
Non-default VM flags: -XX:CICompilerCount=3 -XX:CompressedClassSpaceSize=327155712 -XX:GCLogFileSize=104857600 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=2147483648 -XX:MaxHeapSize=2147483648 -XX:MaxMetaspaceSize=335544320 -XX:MaxNewSize=1073741824 -XX:MetaspaceSize=134217728 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=1073741824 -XX:NumberOfGCLogFiles=10 -XX:OldSize=1073741824 -XX:-OmitStackTraceInFastThrow -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseGCLogFileRotation -XX:-UseLargePages -XX:+UseParallelGC 
Command line:  -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m -XX:-OmitStackTraceInFastThrow -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/nacos/logs/java_heapdump.hprof -XX:-UseLargePages -Dnacos.member.list= -Djava.ext.dirs=/jdk1.8.0_331/jre/lib/ext:/jdk1.8.0_331/lib/ext -Xloggc:/nacos/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -Dloader.path=/nacos/plugins/health,/nacos/plugins/cmdb -Dnacos.home=/nacos

以上信息可以看到确实使用的是UseParallelGC垃圾回收器。问题解决了吗?并没有哦,真是只是AdaptiveSizePolicy配置的问题么?

Tips:
由于From和To区只有7.5M,当每次新生代GC时,如果在这一次GC中存活下来的对象内存大于7.5M那么会将存不下的那部分将直接放入老年代,就会导致老年代快速增长,触发Full GC。

由于From和To区太小可能会导致Full GC过于频繁,于是我去查看了一下nacos的GC日志,发现YGC之间的间隔时间只有10s左右(非原服务器数据):

16:08:31.801+0800: 478.123: [GC (Allocation Failure) [PSYoungGen: 256704K->2784K(258048K)] 381385K->127497K(520192K), 0.0057120 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
16:08:41.812+0800: 488.133: [GC (Allocation Failure) [PSYoungGen: 256736K->2720K(258048K)] 381449K->127457K(520192K), 0.0074081 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 

再执行jstat -gc PID命令,得到如下信息(运行6天的GC情况):

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU       CCSC     CCSU    YGC     YGCT    FGC    FGCT     GCT   
7680.0 7680.0  0.0   6176.0 1033216.0 834105.2 1048576.0   564595.0  87936.0 83749.9 10624.0  9990.6  29316  835.838    25    148.564  984.403

参数解释:

S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

平均一天需要进行4次FGC,每次FGC大约需要6s,这很不正常哦。sba配置的是5s收不到心跳就触发告警,平均FGC却需要6s,这里问题的关键已经比较明显了,有可能就是Full GC导致的nacos停顿时间过长,导致sba服务收不到其他服务的心跳,于是发生服务批量下线问题。
那为什么FGC会需要这么久?我把主意打到了服务器身上,执行top命令,观察服务器的CPU使用情况,还真让我发现了一个问题,CPU使用率会突然飙升到300%,一看发现是es服务。
到这里问题比较清晰了,es服务飙升的CPU使用率导致占满了服务器的线程,nacos的FGC获取不到线程来执行就需要等待,所以造成了长时间的服务停顿,于是发生了服务批量下线和nacos不可用问题。后来我在收到告警邮件之后,立马上服务器看CPU占用情况,果然CPU占用率高达300%,然后查找nacos的GC日志,发现有FGC时间长达45s(以下非原服务器数据):

14:06:09.452+0800: 3794735.773: [Full GC (Ergonomics) [PSYoungGen: 215040K->81154K(218112K)] [ParOldGen: 261606K->261605K(262144K)] 476646K->342760K(480256K), [Metaspace: 83823K->83823K(1126400K)], 45.4329403 secs] [Times: user=125.24 sys=0.58, real=45.43 secs] 

到这儿,已经可以确定是由于nacos的JVM配置问题+es问题的组合拳导致的此次故障。

需要优化的点

  1. YGC和FGC过于频繁,需要降低频率
  2. CPU占用率过高
  3. sba服务心跳检测时间短了点

解决方案

  1. 针对FGC频率过高问题,调整nacos的启动脚本,新增配置-XX:-UseAdaptiveSizePolicy,禁用AdaptiveSizePolicy策略
  2. 迁移es到单独的服务器上运行
  3. 调整sba的配置,将服务下线的心跳配置为60s

JVM优化过程

禁用AdaptiveSizePolicy之后,继续观察nacos的内存使用情况和GC情况,堆内存的From和To区已经正常,但是YGC和FGC的频率并没有下降多少。
怀疑是每次YGC之后活下来的对象太多了,From区100M也不够,于是每次执行完jmap -heap PID命令之后,立马再执行一次该命令,观察堆内存的增长速度。发现每次新生代大概增长200-300M,老年代增长3%左右。新生代仅有1G,每次增长却有200-300M,老年代增长速度过快,From区还是小了点,于是调整JVM的启动参数,调整Xms和Xmx为4g,指定Xmn为3g,如下:

-Xms4g -Xmx4g -Xmn3g

此次调整过后YGC间隔时间由10s变成了40s,运行3.5天未发生FGC,老年代使用率66%。按这个速度,大约5天多一点会发生一次FGC,这个频率就还算正常。

总结

  1. ParallelGC垃圾回收器默认开启AdaptiveSizePolicy有点坑,需要注意一下
  2. es这种服务最好使用单独的机器部署,比较吃CPU和内存。这里es的CPU占用率这么高还有一个坑,这里就不赘述了,下次再聊
  3. YGC和FGC频率不能太高,过高时需要调整JVM参数来降低频率,这个过程可能会比较繁琐,因为调整之后还要持续观察之后再次进行调整
  4. 还是有必要部署针对JVM的监控服务(比如Prometheus),不然每次都需要手动执行命令观察JVM变化,有可能会错过关键信息

参考链接

https://blog.csdn.net/Sqdmn/article/details/106986762
https://blog.csdn.net/dhj199181/article/details/108415771

原文链接:https://www.cnblogs.com/yywf/p/17330932.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JVM调优笔记(一)–Nacos GC引发的服务批量下线问题 - Python技术站

(0)
上一篇 2023年4月23日
下一篇 2023年4月23日

相关文章

  • Java中批处理框架spring batch详细介绍

    Java中批处理框架Spring Batch详细介绍 什么是Spring Batch? Spring Batch是一个轻量级、全面的批处理框架,用于开发企业级批处理应用程序。它旨在帮助开发人员管理和执行大规模批处理任务,其中包括读取大量数据、处理复杂计算和写回结果等任务。Spring Batch提供了许多功能,如任务调度、处理日志和抽象化数据源的读取和写入,…

    Java 2023年5月19日
    00
  • 保障MySQL数据安全的一些建议

    保障MySQL数据安全是网站开发者必备的一项知识点。以下是几条建议可以帮助你加强MySQL的安全保障,避免出现各种潜在的风险和安全隐患。 1. 管理权限 为了确保MySQL数据库的安全,我们应该合理分配权限。为用户设置合适的权限,如仅赋予只读权限而不给予修改、删除等高级操作的权限,这样可以避免误操作或者恶意操作导致数据的丢失以及安全隐患等问题。示例1:在Wo…

    Java 2023年6月16日
    00
  • JS+DIV+CSS实现仿表单下拉列表效果

    下面是JS+DIV+CSS实现仿表单下拉列表效果的完整攻略: 一、实现思路 在HTML中定义一个表单元素 使用CSS美化表单元素的样式 使用JavaScript控制下拉列表的显示与隐藏,并将选中的值显示到表单元素中 二、具体实现步骤 1. HTML结构 在HTML中定义一个表单元素,并使用div元素模拟下拉列表的选项: <div class=&quot…

    Java 2023年6月15日
    00
  • SpringSecurity 默认表单登录页展示流程源码

    Spring Security 是 Spring 框架的安全模块,用于对应用的安全性进行配置和管理。Spring Security 提供了多种身份验证和授权方式,其中最常用的是表单登录方式。 Spring Security 的默认登录表单页面展示流程可以归纳为以下几个步骤: 1.用户访问需要进行身份认证的页面时,Spring Security 会检查用户是否…

    Java 2023年5月20日
    00
  • SpringMVC+Mybatis实现的Mysql分页数据查询的示例

    接下来我将详细讲解“SpringMVC+Mybatis实现的Mysql分页数据查询的示例”的完整攻略,过程中将给出两条示例说明。 准备工作 在开始实现分页数据查询之前,需要确保以下几点: JDK版本在1.8及以上 Maven依赖管理工具 SpringMVC框架 Mybatis持久层框架 Mysql数据库 添加依赖 在Maven的pom.xml文件中添加Spr…

    Java 2023年6月15日
    00
  • java实现简易超市管理系统 附源码下载

    Java实现简易超市管理系统攻略 实现思路 本超市管理系统的主要功能包括如下: 实现商品的库存管理功能,包括商品的入库、出库和查询等操作。 实现商品的销售管理功能,包括购物车、结算和打印小票等操作。 实现系统的用户管理功能,包括用户登录和登出、用户信息修改等操作。 根据上述需求,我们可以把整个系统分成三大模块:商品管理模块、销售管理模块和用户管理模块。每个模…

    Java 2023年5月18日
    00
  • Java 实现协程的方法

    Java 实现协程的方法有很多种,下面会介绍其中两种方式。 一、基于协程库的实现方式 使用协程库实现协程是一种比较常见的方式,常用的协程库有Quasar、Kotlin协程等。这里以Quasar为例来讲解。 Quasar Quasar是一个基于ASM技术的协程库,Quasar可以在Java和Kotlin上实现协程。Quasar提供了协程的核心API和一些常用场…

    Java 2023年5月18日
    00
  • 如何清除网页上的不明的浮动广告和漂浮的图片

    清除网页上的浮动广告和漂浮图片可以通过CSS实现。以下是实现步骤: 步骤一:找到不明的浮动广告和漂浮的图片的CSS选择器 首先,需要找到这些浮动元素的CSS选择器名称,这可以通过浏览器开发者工具中的“选取元素”功能找到。开发者工具会显示当前选定元素的CSS选择器。如果找到的是一个广告插件,则可以通过插件名称或插件代码中的唯一标识符来查找。 例如,如果要删除百…

    Java 2023年5月23日
    00
合作推广
合作推广
分享本页
返回顶部