ElasticSearch性能优化实践(JVM调优+ES调优)

ElasticSearch性能优化实践(JVM调优+ES调优),第1张

近一年内对公司的 ELK 日志系统做过性能优化,也对 SkyWalking 使用的 ES 存储进行过性能优化,在此做一些总结。本篇主要是讲 ES 在 ELK 架构中作为日志存储时的性能优化方案。

随着接入ELK的应用越来越多, 每日新增索引约 230 个,新增 document 约 3000万到 5000万

每日上午和下午是日志上传高峰期,在 Kibana 上查看日志,发现问题:
(1) 日志会有 5-40 分钟的延迟
(2) 有很多日志丢失,无法查到

数据先是存放在 ES 的内存 buffer,然后执行 refresh *** 作写入到 *** 作系统的内存缓存 os cache,此后数据就可以被搜索到。

所以,日志延迟可能是我们的数据积压在 buffer 中没有进入 os cache 。

查看日志发现很多 write 拒绝执行的情况

从日志中可以看出 ES 的 write 线程池已经满负荷,执行任务的线程已经达到最大16个线程,而200容量的队列也已经放不下新的task。

查看线程池的情况也可以看出 write 线程池有很多写入的任务

所以我们需要优化 ES 的 write 的性能。

ES 的优化分为很多方面,我们要根据使用场景考虑对 ES 的要求。

根据个人实践经验,列举三种不同场景下的特点

这三类场景的特点如下:

关于实时性

可以从三方面进行优化:JVM性能调优、ES性能调优、控制数据来源

可以从三方面进行优化:JVM 性能调优、ES 性能调优、控制数据来源

第一步是 JVM 调优。
因为 ES 是依赖于 JVM 运行,没有合理的设置 JVM 参数,将浪费资源,甚至导致 ES 很容易 OOM 而崩溃。

(1) 查看 GC 日志

(2) 使用 jstat 看下每秒的 GC 情况

用下面几种方式都可查看新、老年代内存大小
(1) 使用 jstat -gc pid 查看 Eden 区、老年代空间大小
(2) 使用 jmap -heap pid 查看 Eden 区、老年代空间大小
(3) 查看 GC 日志中的 GC 明细

上面的几种方式都查询出,新生代总内存约1081M,即1G左右;老年代总内存为19864000K,约19G。新、老比例约1:19,出乎意料。

这真是一个容易踩坑的地方。
如果没有显示设置新生代大小,JVM 在使用 CMS 收集器时会自动调参,新生代的大小在没有设置的情况下是通过计算得出的,其大小可能与 NewRatio 的默认配置没什么关系而与 ParallelGCThreads 的配置有一定的关系。

所以: 新生代大小有不确定性,最好配置 JVM 参数 -XX:NewSize、-XX:MaxNewSize 或者 -xmn ,免得遇到一些奇怪的 GC,让人措手不及。

新生代过小,老年代过大的影响

32G 的内存,分配 20G 给堆内存是不妥当的,所以调整为总内存的50%,即16G。
修改 elasticsearch 的 jvmoptions 文件

设置要求:

因为指定新生代空间大小,导致 JVM 自动调参只分配了 1G 内存给新生代。

修改 elasticsearch 的 jvmoptions 文件,加上

老年代则自动分配 16G-8G=8G 内存,新生代老年代的比例为 1:1。修改后每次 Young GC 频率更低,且每次 GC 后只有少数数据会进入老年代。

ES默认使用的垃圾回收器是:老年代(CMS)+ 新生代(ParNew)。如果是JDK19,ES 默认使用G1垃圾回收器。

因为使用的是 JDK18,所以并未切换垃圾回收器。后续如果再有性能问题再切换G1垃圾回收器,测试是否有更好的性能。

优化前

每秒打印一次 GC 数据。可以看出,年轻代增长速度很快,几秒钟年轻代就满了,导致 Young GC 触发很频繁,几秒钟就会触发一次。而每次 Young GC 很大可能有存活对象进入老年代,而且,存活对象多的时候(看上图中第一个红框中的old gc数据),有(5144-5108)/100 19000M = 约68M。每次进入老年代的对象较多,加上频繁的 Young GC,会导致新老年代的分代模式失去了作用,相当于老年代取代了新生代来存放近期内生成的对象。当老年代满了,触发 Full GC,存活的对象也会很多,因为这些对象很可能还是近期加入的,还存活着,所以一次 Full GC 回收对象不多。而这会恶性循环,老年代很快又满了,又 Full GC,又残留一大部分存活的,又很容易满了,所以导致一直频繁 Full GC。

优化后

每秒打印一次 GC 数据。可以看出,新生代增长速度慢了许多,至少要60秒才会满,如上图红框中数据,进入老年代的对象约(1568-1560)/100 10000 = 8M,非常的少。所以要很久才会触发一次 Full GC 。而且等到 Full GC 时,老年代里很多对象都是存活了很久的,一般都是不会被引用,所以很大一部分会被回收掉,留一个比较干净的老年代空间,可以继续放很多对象。

ES 启动后,运行14个小时

优化前

Young GC 每次的时间是不长的,从上面监控数据中可以看出每次GC时长 1467995/27276 约等于 005秒。那一秒钟有多少时间实在处理Young GC 计算公式:1467秒/ (60秒×60分 14小时)= 约0028秒,也就是100秒中就有28秒在Young GC,也就是有28S的停顿,这对性能还是有很大消耗的。同时也可以算出多久一次Young GC, 方程是: 60秒×60分14小时/ 27276次 = 1次/X秒,计算得出X = 054,也就是054秒就会有一次Young GC,可见 Young GC 频率非常频繁。

优化后

Young GC 次数只有修改前的十分之一,Young GC 时间也是约八分之一。Full GC 的次数也是只有原来的八分之一,GC 时间大约是四分之一。

GC 对系统的影响大大降低,性能已经得到很大的提升。

上面已经分析过ES作为日志存储时的特性是:高并发写、读少、接受30秒内的延时、可容忍部分日志数据丢失。
下面我们针对这些特性对ES进行调优。

本人整理了一下数据写入的底层原理

refresh
ES 接收数据请求时先存入 ES 的内存中,默认每隔一秒会从内存 buffer 中将数据写入 *** 作系统缓存 os cache,这个过程叫做 refresh;

到了 os cache 数据就能被搜索到(所以我们才说 ES 是近实时的,因为1s 的延迟后执行 refresh 便可让数据被搜索到)

fsync
translog 会每隔5秒或者在一个变更请求完成之后执行一次 fsync *** 作,将 translog 从缓存刷入磁盘,这个 *** 作比较耗时,如果对数据一致性要求不是跟高时建议将索引改为异步,如果节点宕机时会有5秒数据丢失;

flush
ES 默认每隔30分钟会将 os cache 中的数据刷入磁盘同时清空 translog 日志文件,这个过程叫做 flush。

merge

ES 的一个 index 由多个 shard 组成,而一个 shard 其实就是一个 Lucene 的 index ,它又由多个 segment 组成,且 Lucene 会不断地把一些小的 segment 合并成一个大的 segment ,这个过程被称为 段merge 。执行索引 *** 作时, ES会先生成小的segment ,ES 有离线的逻辑对小的 segment 进行合并,优化查询性能。但是合并过程中会消耗较多磁盘 IO,会影响查询性能。

为了保证不丢失数据,就要保护 translog 文件的安全:

该方式提高数据安全性的同时, 降低了一点性能

==> 频繁地执行 fsync *** 作, 可能会产生阻塞导致部分 *** 作耗时较久 如果允许部分数据丢失, 可设置异步刷新 translog 来提高效率,还有降低 flush 的阀值,优化如下:

写入 Lucene 的数据,并不是实时可搜索的,ES 必须通过 refresh 的过程把内存中的数据转换成 Lucene 的完整 segment 后,才可以被搜索。

默认1秒后,写入的数据可以很快被查询到,但势必会产生大量的 segment,检索性能会受到影响。所以,加大时长可以降低系统开销。对于日志搜索来说,实时性要求不是那么高,设置为5秒或者10s;对于SkyWalking,实时性要求更低一些,我们可以设置为30s。

设置如下:

indexmergeschedulermax_thread_count 控制并发的 merge 线程数,如果存储是并发性能较好的 SSD,可以用系统默认的 max(1, min(4, availableProcessors / 2)),当节点配置的 cpu 核数较高时,merge 占用的资源可能会偏高,影响集群的性能,普通磁盘的话设为1,发生磁盘 IO 堵塞。设置max_thread_count 后,会有 max_thread_count + 2 个线程同时进行磁盘 *** 作,也就是设置为 1 允许3个线程。

设置如下:

该方式可对已经生成的索引做修改,但是对于后续新建的索引不生效,所以我们可以制作 ES 模板,新建的索引按模板创建索引。

因为我们的业务日志是按天维度创建索引,索引名称示例:user-service-prod-20201212,所以用通配符 202 匹配对应要创建的业务日志索引。

前文已经提到过,write 线程池满负荷,导致拒绝任务,而有的数据无法写入。

而经过上面的优化后,拒绝的情况少了很多,但是还是有拒绝任务的情况。

所以我们还需要优化write线程池。

从 prometheus 监控中可以看到线程池的情况:

为了更直观看到ES线程池的运行情况,我们安装了 elasticsearch_exporter 收集 ES 的指标数据到 prometheus,再通过 grafana 进行查看。

经过上面的各种优化,拒绝的数据量少了很多,但是还是存在拒绝的情况,如下图:

write 线程池如何设置:

参考: ElasticSearch线程池

write 线程池采用 fixed 类型的线程池,也就是核心线程数与最大线程数值相同。线程数默认等于 cpu 核数,可设置的最大值只能是 cpu 核数加1,也就是16核CPU,能设置的线程数最大值为17。

优化的方案:

config/elasticsearchyml文件增加配置

优化后效果

Swap 交换分区 :

参考: ElasticSearch官方解释为什么要禁用交换内存

有三种方式可以实现 ES 不使用Swap分区

执行命令

可以临时禁用 Swap 内存,但是 *** 作系统重启后失效

执行下列命令

正常情况下不会使用 Swap,除非紧急情况下才会 Swap。

config/elasticsearchyml 文件增加配置

分片

索引的大小取决于分片与段的大小,分片过小,可能导致段过小,进而导致开销增加;分片过大可能导致分片频繁 Merge,产生大量 IO *** 作,影响写入性能。

因为我们每个索引的大小在15G以下,而默认是5个分片,没有必要这么多,所以调整为3个。

分片的设置我们也可以配置在索引模板。

副本数

减少集群副本分片数,过多副本会导致 ES 内部写扩大。副本数默认为1,如果某索引所在的1个节点宕机,拥有副本的另一台机器拥有索引备份数据,可以让索引数据正常使用。但是数据写入副本会影响写入性能。对于日志数据,有1个副本即可。对于大数据量的索引,可以设置副本数为0,减少对性能的影响。

分片的设置我们也可以配置在索引模板。

有的应用1天生成10G日志,而一般的应用只有几百到1G。一天生成10G日志一般是因为部分应用日志使用不当,很多大数量的日志可以不打,比如大数据量的列表查询接口、报表数据、debug 级别日志等数据是不用上传到日志服务器,这些 即影响日志存储的性能,更影响应用自身性能

优化后的两周内ELK性能良好,没有使用上的问题:

参考


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zz/10846374.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-11
下一篇 2023-05-11

发表评论

登录后才能评论

评论列表(0条)

保存