上周晚上,某环境 ES 出现阻塞, 运行缓慢。于是开始排查问题的过程。

开始

思路:现象是阻塞,通常是 CPU 彪高,导致业务线程分配不到 CPU 时间片,或者内存吃紧,频繁 GC 导致的 STW。

登录到目标服务器,由于 ES 的用户不是 LZ,因此找运维要了 root 权限,登录到服务器。sudo -i 切到 root,使用 ps -ef | grep Elasticsearch 找到该用户,然后 su - es 切到 es 用户(不切是无法处理 es 用户的 Java 进程的,例如打印 jstack 日志)。

top 查看服务器状态,发现 pid 4335 进程的 CPU 占用达到 180%,查看 CPU 核数:cat /proc/cpuinfo| grep "processor"| wc -l, 核数为 4,根据经验,通常是 C2 编译器,或者 GC 线程,最后是业务代码导致。因此需要定位该线程。使用 top -Hp 4335,得到线程号 30785,使用 printf "%x" 得到 16 进制数字 7841,方便在 jstack 日志查找线程。

使用 jstack -l 4335 > jstacklog.txt 打印日志,然后找线程,vim jstacklog.txt, 开始查找,gg,/7841,enter,n, 找到 "Concurrent Mark-Sweep GC Thread" os_prio=0 tid=0x00007fd380063800 nid=0x7841 runnable 这个 CMS GC 线程,看来是内存不够了。

使用 jps -l 找到 es 启动类名称,然后使用 ps aux | grep Elasticsearch 找到启动详细信息,发现启动配置为 -Xmx2g -Xms2g, -XX:CMSInitiatingOccupancyFraction=50 ,这里为了防止串行 FGC,让 CMS 在 old 区达到 50% 时就开始 GC,所以 CMS 非常繁忙。为了验证此问题,使用 jstat -gcutil 4335 1000 查看 gc 状态,发现 fgc 频繁(5 秒一次),ygc 正常(3 秒一次) ,这里说一下,CMS 的 fgc 此时和我们想象的不一样,CMS GC 只工作在老年代,每次 GC 会对 FGC 次数加 2,一次是 init mark,一次是 remark,这两个阶段会影响暂停应用,其他的清理阶段是并行清理的,对业务线程无影响,所以,当使用 CMS GC ,如果 jstat 看到 FGC 次数很多,不用在意。但当 CMS 出现 concurrent mode failure(CMS GC 的速度赶不上对象晋升到 old 区的速度),则会使用备用收集器 Serial,开始串行 GC,此时将会彻底 STW。 因此,这个 ES 将 CMS 的阈值调的很低,就是为了防止出现 concurrent mode failure。

原因

原因已经找出,由于 CMS GC 频繁,导致 CPU 彪高,ES 查询速度变慢,最后业务阻塞。

继续

但是为什么频繁 CMS 呢?肯定是内存不够,为什么不够呢? 通常是通过 dump 内存文件查看,但是注意,jmap 和 jcmd dump 文件时,会导致 fgc,线上需要注意,我这里由于不是业务高峰,使用 jmap -dump:format=b,file=/tmp/dump.hprof 3445 , 导出该文件到 tmp 目录,方便从跳板机上下载。

从跳板机下载文件,权限不够,使用 sudo chown -R username dump.hprof 修改权限,然后下载。

LZ 使用的是 mac,使用 mat(Mac 的 mat 有 bug,需要替换一个 jar 包,具体自行搜索) 开始分析,发现内存中有 1个多 g 的 indexService 对象(600 多),即 ES 的索引对象,通过和 ES 同学的沟通,确实发现有 600 多索引,有很多都是国企索引,通常一台 ES 差不多配置 100-200 索引,大量索引引起更新,频繁更新索引对象。

最后,将ES 内存升级到 4g,-XX:CMSInitiatingOccupancyFraction 修改为75,减少 CMS GC。删除 ES 无效索引。问题解决。

后期为了检验结果,使用 jvisualvm 连上服务器 Java 进程: 登录到目标服务器,创建文件 jstatd.all.policy,文件内容:

grant codebase "file:${java.home}/../lib/tools.jar" {
   permission java.security.AllPermission;
};

执行 jstatd -J-Djava.security.policy=jstatd.all.policy -p 1222 & , 启动 jstatd 后台服务,方便 jvisualvm 连接到这台服务器的 Java 进程。找到本机 Java 目录下的 VisualVM ,连接上目标 ip 和 1222 端口服务,安装 GC 插件,查看 GC 状态。

01-25 16:40