记一次Elasticsearch优化总结

目录

背景介绍

JVM知识回顾

ES配置说明回顾

现状分析

调优实战

总结与展望

一. 背景介绍

项目中的服务集成了springboot-admin作服务监控,最近一直收到邮件告警,提示es出错。错误信息以下:html

org.elasticsearch.ElasticsearchTimeoutException: java.util.concurrent.TimeoutException: Timeout waiting for task.
复制代码

频繁收到这个告警,因此决定花时间研究一下。从报错信息看,并发超时异常。ES做为java开发的中间件,咱们没有对任何代码作过修改,因此就从JVM开始着手尝试解决,同时还涉及到部分ES知识和springboot的知识。java

二. JVM知识回顾

可参考另外一篇学习笔记: 深刻理解java虚拟机算法

1. JVM内存模型

  • JVM gc的对象:堆

2. 堆内存

2.1 堆内存划分

  • 堆区分为新生代和老年代
  • 新生代又分为Eden区,from survivor区,to survivor区
  • Eden区和两块较小的survivor空间。大小比例为8:1:1
  • java8已经没有持久代了,改成元数据区,主要存放元数据,例如Class、Method的元信息,与垃圾回收要回收的Java对象关系不大

2.2 堆内存查看

使用 jstat -gc(-gccapacity, -gcutil)命令查看堆分配状况spring

  • S0C:survivor0区总内存大小(Capacity)
  • S1C: survivor1区总内存大小
  • S0U: survivor0区当前内存大小(Used)
  • S1U: survivor1区当前内存大小
  • EC:Eden区总内存大小
  • EU:Eden区当前内存大小
  • OC:老年代总内存大小
  • OU:老年代当前内存大小
  • MC:meta data区总内存大小
  • MU:meta data区当前内存大小

2.3 内存分配和回收策略

2.3.1 分配策略

  • 大部分对象建立时,在eden区分配
  • 大的对象直接进入老年代,好比很长的字符串或数组。这些对象对垃圾回收不友好。
  • 长期存活的对象,将重新生代晋升到老年代

2.3.2 回收策略

  • eden区满:触发一次minor gc,存活的对象复制到其中一个survivor。对象的年龄+1
  • 一个survivor区满:知足晋升条件的,进入老年代。不知足的,复制到另外一个survivor区

2.3.3 晋升条件的判断

  • Serial和ParNew GC中经过MaxTenuringThreshold参数设定,默认为15
  • Parallel收集器自动调全年龄:survivor空间中相同年龄全部对象大小大于空间的一半,大于等于该年龄的对象就直接进入老年代

2.3 关于堆划分的思考

2.3.1 大堆和小堆堆程序的影响

  • 堆太大:垃圾回收时STW的时间过长,影响程序响应时间。听说ZGC(java11发布)回收器能解决这个问题。java11中ZGC的介绍
  • 堆过小:垃圾回收太频繁

2.3.2 为何要划分为不一样的年代

  • 每一个对象的生命周期是不同的,将不一样存活时间的对象划分到不一样的区,而后采用不一样的垃圾回收算法
  • java不少对象都是朝生夕死的,这些对象不会进入老年代。

2.3.3 为何要有survivor区

  • 没有survivor区,只有eden区的话,每进行一次minor gc,对象就被送入老年代。很容易触发full gc,影响性能
  • survivor存在的目的就是减小送入老年代的对象数量,减小full gc的发生

2.3.4 为何要设置两个survivor区

每次minor gc,经过将eden和一个survivor的内容复制到另外一个survivor, 避免碎片化问题chrome

3. 垃圾回收算法

3.1 标记-清除算法

  • 最基础的收集算法
  • 分为标记和清除两个阶段
  • 不足之处:
    • 效率问题
    • 产生大量不连续的内存碎片

3.2 复制算法

  • 将内存分为大小相等的两块,每次使用其中的一块
  • 一块用完时,将存活的对象复制到另外一块
  • 现代虚拟机新生代都用该算法
  • 不足:
    • 内存利用率不高

3.3 标记-整理算法

  • 对象存活率高时大量的复制会影响效率,老年代使用该算法
  • 标记过程与标记-清除算法同样
  • 后续步骤并非清理对象,而是让全部存活的对象都向一段移动,清理边界之外的内存

3.4 分代收集算法

  • 根据对象存活周期不一样,采用不一样的收集算法
  • 新生代大量对象死亡,少许存活,采用复制算法
  • 老年代对象存活率高,采用标记-清理或者标记-收集算法

4. 垃圾回收器

4.1 年代划分

  • 新生代收集器有:Serial,ParNew,Paraller Scavenge
  • 老年代收集器有:CMS Serial old,Parallel Old
  • G1收集器可做用与新生代和老年代
  • 没有连线的两个收集器不能共存,好比CMS和Paraller Scavenge

4.2 工做机制划分

  • 串行收集器:Serial,Serial Old,单线程的一个回收器,简单、易实现、效率高
  • 并行收集器:ParNew,Serial的多线程版,能够充分的利用CPU资源,减小回收的时间
  • 吞吐量优先收集器:Parallel Scavenge
  • 并发收集器:CMS(Concurrent Mark Sweep),停顿时间少优先,基于“标记-清除”算法实现。

4.3 其余说明

  • java11 新出了一款ZGC收集器,性能比G1更高效(还在实验阶段)
  • java5默认采用CMS收集器,java9默认收集器被G1代替
  • 用户可本身指定使用哪一种垃圾收集器
  • 各个垃圾收集器详细介绍参考深刻理解java虚拟机

4.4 CMS工做原理

  • 不会等到老年代空间快满了才回收(和用户线程并发,留内存给用户线程)。配置参数为-XX:CMSInitiazingOccupanyFraction。默认为75%
  • 使用标记-清除算法。整个过程分为四步:
    • 初始标记:STW,标记GC Roots能关联到的对象,速度很快
    • 并发标记:GC Roots Tracing过程。耗时。和用户线程一块儿执行(并行)
    • 从新标记:STW,标记并发标记过程当中程序运行致使标记变化的对象,时间比初始标记长,远比并发标记短
    • 并发清除:耗时。和用户线程一块儿执行(并行)

三. ES配置说明回顾

可参考另一篇笔记:Elasticsearch学习笔记json

主要介绍es官网手册特别说明的一些注意点centos

1. 关于配置的说明

1.1 ES使用的垃圾回收器

  • 默认为CMS,2.x版本官方推荐不要修改成G1,某些版本JAVA G1存在的Bug,会形成Lucene的段文件损坏。
  • 不过5.x以及以后版本,没有明确说推荐或不推荐G1,默认仍是用的CMS

1.2 ES内存分配要求

  • 不超过32G。由于每一个对象的指针都变长了,就会使用更多的 CPU 内存带宽,也就是说你实际上失去了更多的内存。
  • 不要超过内存的一半,由于Lucene也须要内存,且这些内存不被JVM管理
  • 若是不须要对分词作聚合运算,可下降堆内存。堆内存越小,Elasticsearch(更快的 GC)和 Lucene(更多的内存用于缓存)的性能越好。

2. 关于滚动重启的说明

  • 保证不停集群功能的状况下逐一对每一个节点进行升级或维护
  • 先中止索引新的数据
  • 禁止分片分配。cluster.routing.allocation.enable" : "none"
    curl -XPUT http://{ip}:9200/_cluster/settings -d' { "transient" : { "cluster.routing.allocation.enable" : "none" } }'
    复制代码
  • 关闭单个节点,并执行升级维护
  • 启动节点,并等待加入集群
  • 重启分片分配。cluster.routing.allocation.enable" : "all"
    curl -XPUT http://{ip}:9200/_cluster/settings -d' { "transient" : { "cluster.routing.allocation.enable" : "all" } }'
    复制代码
  • 对其余节点重复以上步骤
  • 恢复索引更新数据

四. 现状分析

1. 版本及硬件状况介绍

  • java:1.8.0_131
  • elasticsearch:5.5.1
  • es集群:4个数据节点
  • os: centos7 24核 128G
  • 垃圾回收器:老年代(CMS)+ 新生代(ParNew)

2. 目前堆分配状况

要针对jvm调优,必不可少的是先查看堆内存情况,有如下几种查看方法数组

2.1 jstat -gc命令查看堆分配状况

2.2 统计ES各个节点堆分配信息

节点 堆总大小 新生代 survivor eden 老年代 元数据区
节点A 32G 1.46G 0.146G 1.16G 30.5G 81M
节点B 32G 1.46G 0.146G 1.16G 30.5G 85M
节点C 32G 1.46G 0.146G 1.16G 30.5G 81M
节点D 20G 1.46G 0.146G 1.16G 18.5G 76M

3. 监控工具对比

工具名称 各分区状况 数据是否直观 是否可查看历史数据 是否免费 备注
jstat 主要用于查看各分区大小
ElasticHQ 主要用于浏览es总体信息
cerebro 主要用于浏览es总体信息
x-pack 试用期一年 试用期到相关功能不可用,不影响现有功能。6.3版本x-pack已经开源,后续版本可能会免费
  • 因为线上报异常邮件的时间是不肯定的,不可能随时盯着监控面板看,全部必须有查看历史数据的功能,所以x-pack是咱们监控的首选工具
  • x-pack监控功能只是其中之一,可是真的很是强大,强烈推荐!!同时期待ES官方尽快使之免费
  • 网上有破解x-pack的方法,将jar包反编译以后修改代码,再打包回去,还没作尝试。

x-pack安装过程的一些小问题总结

第一步:证书申请

curl -XPUT 'http://{ip}:9200/_xpack/license?acknowledge=true' -H "Content-Type: application/json" -d @sivabalan-nagarajan-2327c0fa-f56b-443a-a3d6-abef7ecf2220-v5.json
复制代码

第二步:安装x-pack插件, 包括es和kibana

./bin/kibana-plugin install x-pack 安装很慢,先把文件下载下来,用下一个命令安装
./bin/elasticsearch-plugin install file:///home/breakpad/softs/x-pack-5.5.1.zip
复制代码

第三步:修改配置文件

es配置文件里,只启用监控功能浏览器

xpack.security.enabled: false
xpack.monitoring.enabled: true
xpack.graph.enabled: false
xpack.watcher.enabled: false
复制代码

kibana配置文件里,只启用监控功能缓存

xpack.security.enabled: false
xpack.monitoring.enabled: true
xpack.graph.enabled: false
xpack.reporting.enabled: false
复制代码

报错问题解决

rpm安装后,systemctl方式启动kibana报权限不足的问题?

  1. 卸载x-pack,以kibana用户去安装 sudo -u kibana bin/kibana-plugin install file:///usr/share/kibana/x-pack-5.5.1.zip
  2. 仍是报权限错误,修改报错的文件权限都为kibana
  3. 安装包权限报错,修改安装包权限为kibana

kibana启动后网页打不开怎么解决?

  1. 在config/kibana.yml里配置日志路径:logging.dest: /var/log/kibana.log
  2. 修改日志权限 touch /var/log/kibana.log chown kibana:kibana /var/log/kibana.log
  3. 日志也没有错,可是浏览器就是打不开。最后无心间换了个浏览器居然正常了,再从新把以前打不开的chrome浏览器升级以后,也能正常打开了!!

4. x-pack监控状况分析(以节点B,周期为7天为例)

曲线中每一天大概24个点,即计算的是每一个小时的数据

4.1 gc次数:平均值为250次/h 左右

4.2 minor gc耗时:平均值为10000ms/h

4.3 full gc后:剩余堆大小:4.2G,两次full gc的时间分别为2.224s,2.438s

5. 观察到的现象

  • 新生代和老年代分配比例不合理,新生代过小,老年代太大
    • 网上不少文章指出新生代和老年代的默认比例为1:2,可是经过观察发现并非这样(咱们的机器上约是1:20)。
    • 具体缘由在网上目前只找到这样一篇文章有过说明。CMS默认新生代是多大?
    • 大体就是:取默认NewRatio计算出来的值和另一个公式计算出来的值对比,取小的那一个
    • 计算公式为:计算机核数*某个参数(64M)*13/10。咱们的机器算出来的值为2G,勉强符合这个说法。
  • 新生代垃圾回收频繁
  • 老年代收集后:有效内存只达4G左右(活跃数据)

6. 针对观察到现象的初步分析和解决

  • 新生代过小:致使minior gc回收频繁,可适当加大新生代大小
  • 老年代太大:致使major gc或full gc回收时间过长,可适当减小老年代大小
  • 如何肯定新生代老年代大小:根据美团gc优化实战文章所述:
    • 总大小:3-4倍活跃数据大小:
      • 节点B为4.2G*4,咱们设置为20G。
      • 其余机器大概为3.6G*4,咱们设置为16G
    • 新生代:1-1.5倍活跃数据大小
    • 老年代:总大小-新生代。综上,咱们设置新生代:老年代=1:4

五. 调优实战

经过以上分析发现的问题,而后尝试调整参数,而且观察调整后的监控结果,验证咱们的推测是否正确。

1. 修改配置参数

  • 文件为{es_home}/config/jvm.options

2. 修改后的堆配置参数

节点 堆总大小 新生代 survivor eden 老年代 元数据区
节点A 16G 3.2G 320M 2.56G 12.8G 77M
节点B 20G 4G 400M 3.2G 16G 66M
节点C 16G 3.2G 320M 2.56G 12.8G 77M
节点D 16G 3.2G 320M 2.56G 12.8G 77M

3. 修改后的监控信息

3.1 gc次数总体呈降低趋势

3.2 gc耗时总体呈降低趋势,full gc时间大体在900ms左右

4. springboot参数的调整

  • 经过对JVM参数调整后,发现仍是有不定时报警的状况。因而研究了一下springboot-admin的原理,它是基于springboot-actuator作了封装,而后看了一下springboot-actuator的原理,找到了其中一个很重要的参数:
    management.health.elasticsearch.response-timeout
    复制代码
  • 该参数表示监控程序向es集群发送心跳,容许最大的响应时间的多少? 聪明的你应该明白了,若是发送心跳的时候,es的JVM正在执行垃圾回收,STW致使响应迟迟得不到回复,就会收到邮件告警。它的默认值是100ms,因此必须将该值设置为至少超过minior gc的时间。
  • 可是,若是发送心跳的时候,刚好JVM正在执行full gc,由于STW的时间通常比较长,因此你必然会收到告警邮件,除非你把response-timeout的值设置为比full gc的时间还长
  • 综上分析,须要根据垃圾回收的时间,给该值设置合理的值。

六. 总结与展望

1. 最终优化总结

1.1 关于JVM的调优

  • 减小ES节点分配给JVM的堆大小
  • 调整新生代和老年代的比例

1.2 关于springboot的调优

  • 加大springboot-actuator针对Elasticsearch健康检查时的响应时间(默认为100ms)

2. 展望

  • 业务优化:这次优化仅仅从JVM的角度作了参数调整,es官方文档其实给出不少高效使用es的方法。后续优化能够从业务的角度去分析,包括:
    • 不少不须要分词的字段,都没有作配置,默认都分词了。特别影响性能。
    • 不少查询没有用filter,用了query。没法缓存。
  • 同时,期待未来ZGC投入使用,同时ES很好的兼容ZGC,或许那时就不须要任何调优了(无论多大的堆,gc时间在10ms内)。

七. 参考

相关文章
相关标签/搜索