[转载] 记一次 ES 的 GC 问题
目录
一。问题背景
二。问题排查
三。解决方案
四。问题总结
一。问题背景
在双十一时,有用户反馈推广平台物料列表出现了耗时严重的情况。筛选排序系统出现过耗时严重的情况,根据业务系统的筛选排序慢接口的 traceId, 我们分析了一下请求链路上的瓶颈是 ES.
二。问题排查
首选我们在监控平台上确认了一下 ES 的访问流量,发现流量曲线变化不大,说明不是 ES 读请求压力突增导致的。
接着我们看了 ES 的 bigdesk 监控,发现有不少 Full GC,与此同时查看了 GC 日志,发现日志里有比较频繁的 CMS。
然后分析了下日志的内容,发现 cms remark 这个阶段时间特别长,甚至有 3-5s 的情况,而且这个阶段是 stop the world 的,会影响用户线程的工作。
remark 如果耗时较长,通常原因是在 cms gc 已经结束了 concurrent-mark 步骤后,旧生代的引用关系仍然发生了很多的变化,旧生代的引用关系发生变化的原因主要是:
在这个间隔时间段内,新生代晋升到旧生代的对象比较多;
在这个间隔时间段内,创建出来的对象又比较多,年轻带也是 cms 的
这个阶段会导致第二次 stop the word,该阶段的任务是完成标记整个年老代的所有的存活对象。
这个阶段,重新标记的内存范围是整个堆,包含_young_gen 和_old_gen。为什么要扫描新生代呢,因为对于老年代中的对象,如果被新生代中的对象引用,那么就会被视为存活对象,即使新生代的对象已经不可达了,也会使用这些不可达的对象当做 cms 的 “gc root”,来扫描老年代; 因此对于老年代来说,引用了老年代中对象的新生代的对象,也会被老年代视作 “GC ROOTS”: 当此阶段耗时较长的时候,可以加入参数 - XX:+CMSScavengeBeforeRemark,在重新标记之前,先执行一次 ygc,回收掉年轻带的对象无用的对象,并将对象放入幸存带或晋升到老年代,这样再进行年轻带扫描时,只需要扫描幸存区的对象即可,一般幸存带非常小,这大大减少了扫描时间
由于之前的预处理阶段是与用户线程并发执行的,这时候可能年轻带的对象对老年代的引用已经发生了很多改变,这个时候,remark 阶段要花很多时间处理这些改变,会导致很长 stop the word,所以通常 CMS 尽量运行 Final Remark 阶段在年轻代是足够干净的时候。
gc root,cms 会扫描年轻带中持有老年代对象的引用,如果年轻带有大量引用需要被扫描,会让 Remark 阶段耗时增加
为什么 remark 阶段这么长时间?就是一次 cms 周期内,并发标记后到 remark 这个期间 jvm 堆内存对象变化很大。看了下 remark 的时间,对应我们的业务日志里就是一大波 es bulk 的操作。对应 Bigdesk 观察,几秒的卡顿基本都出现在一大波 es bulk 操作时间吻和。
分析了 bulk 引起了 remark 耗时是因为数据流的物料同步时有些地方写的不够好导致的。
三。解决方案
1、对 GC 参数的调整
a. 增加了 CMS 回收的线程数
因为我们是 32 核的 cpu ,cpu 利用率用 bigdesk 观察还是很低的,
5% 左右。-XX:ParallelGCThreads= N
-XX:ParallelCMSThreads= M
调整这 2 个参数都可以,它们的关系:
ParallelCMSThreads = (ParallelGCThreads + 3)/4)
调整后情况缓解了一些,remark 还是有 3,4 秒的情况。
b. 开启了并行 remark
-XX:+CMSParallelRemarkEnabled
c. 强制 remark 之前开始一次 minor gc,从而减少 remark 阶段扫描年轻代 gc root 的开销
-XX:+CMSScavengeBeforeRemark
2、对业务场景中 bulk 操作的调整
部分场景一次 bulk 操作就写 1 条数据或者很少的数据,在业务里修改了代码逻辑,将 bulk 操作合并写入更多的数据,减少 bulk 操作 IO 的操作对 CMS GC 的影响。
四。问题总结
1、遇到问题可以借助监控和日志,分析特征帮助快速定位问题
2、有时候出现性能问题,除了考虑技术上的升级,还可以考虑业务上相应的调整,在某些场景业务调整效果可能更佳?
作者:zxcodestudy
来源:CSDN
原文链接:blog.csdn.net/qq_16681169/article/...
推荐文章: