目录
背景介绍
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报权限不足的问题?
- 卸载x-pack,以kibana用户去安装 sudo -u kibana bin/kibana-plugin install file:///usr/share/kibana/x-pack-5.5.1.zip
- 仍是报权限错误,修改报错的文件权限都为kibana
- 安装包权限报错,修改安装包权限为kibana
kibana启动后网页打不开怎么解决?
- 在config/kibana.yml里配置日志路径:logging.dest: /var/log/kibana.log
- 修改日志权限 touch /var/log/kibana.log chown kibana:kibana /var/log/kibana.log
- 日志也没有错,可是浏览器就是打不开。最后无心间换了个浏览器居然正常了,再从新把以前打不开的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参数的调整
六. 总结与展望
1. 最终优化总结
1.1 关于JVM的调优
- 减小ES节点分配给JVM的堆大小
- 调整新生代和老年代的比例
1.2 关于springboot的调优
- 加大springboot-actuator针对Elasticsearch健康检查时的响应时间(默认为100ms)
2. 展望
- 业务优化:这次优化仅仅从JVM的角度作了参数调整,es官方文档其实给出不少高效使用es的方法。后续优化能够从业务的角度去分析,包括:
- 不少不须要分词的字段,都没有作配置,默认都分词了。特别影响性能。
- 不少查询没有用filter,用了query。没法缓存。
- 同时,期待未来ZGC投入使用,同时ES很好的兼容ZGC,或许那时就不须要任何调优了(无论多大的堆,gc时间在10ms内)。
七. 参考
- 《深刻理解java虚拟机》
- 《elasticsearch权威指南》
- 美团gc优化实战