ES服务突发严重耗时,最终排查到这个原因...

在双十一时,有用户反馈推广平台物料列表出现了耗时严重的情况。筛选排序系统出现过耗时严重的情况,根据业务系统的筛选排序慢接口的traceId, 我们分析了一下请求链路上的瓶颈是ES.

问题排查

首选我们在监控平台上确认了一下ES的访问流量,发现流量曲线变化不大,说明不是ES读请求压力突增导致的。

接着我们看了ES的bigdesk监控,发现有不少Full GC,与此同时查看了GC日志,发现日志里有比较频繁的CMS。

然后分析了下日志的内容,发现cms remark这个阶段时间特别长,甚至有3-5s的情况,而且这个阶段是stop the world的,会影响用户线程的工作。

remark如果耗时较长,通常原因是在cms gc已经结束了concurrent-mark步骤后,旧生代的引用关系仍然发生了很多的变化,旧生代的引用关系发生变化的原因主要是:

在这个间隔时间段内,新生代晋升到旧生代的对象比较多;
在这个间隔时间段内,创建出来的对象又比较多,年轻带也是cms的gc root,cms会扫描年轻带中持有老年代对象的引用,如果年轻带有大量引用需要被扫描,会让Remark阶段耗时增加

这个阶段会导致第二次stop the word,该阶段的任务是完成标记整个年老代的所有的存活对象。

这个阶段,重新标记的内存范围是整个堆,包含younggen和oldgen。为什么要扫描新生代呢,因为对于老年代中的对象,如果被新生代中的对象引用,那么就会被视为存活对象,即使新生代的对象已经不可达了,也会使用这些不可达的对象当做cms的“gc root”,来扫描老年代;

因此对于老年代来说,引用了老年代中对象的新生代的对象,也会被老年代视作“GC ROOTS”:当此阶段耗时较长的时候,可以加入参数-XX:+CMSScavengeBeforeRemark,在重新标记之前,先执行一次ygc,回收掉年轻带的对象无用的对象,并将对象放入幸存带或晋升到老年代,这样再进行年轻带扫描时,只需要扫描幸存区的对象即可,一般幸存带非常小,这大大减少了扫描时间。

由于之前的预处理阶段是与用户线程并发执行的,这时候可能年轻带的对象对老年代的引用已经发生了很多改变,这个时候,remark阶段要花很多时间处理这些改变,会导致很长stop the word,所以通常CMS尽量运行Final 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、有时候出现性能问题,除了考虑技术上的升级,还可以考虑业务上相应的调整,在某些场景业务调整效果可能更佳 ?

你可能感兴趣的:(Java)