ES时序数据库的性能优化

本文主要是讲解了Elasticsearch数据库的优化,大家可以看一下。因为当时实操中涉及了6版本和7版本的一起优化,所以内容上大家自行区分一下。

一、基础设置

1. jvm.options参数详解 不同版本java配置会不一样

-Xms12g

-Xmx12g

说明:

  • 将 Xms 和 Xmx 设置成一样大 避免JVM堆的动态调整给应用进程带来"不稳定",具体内存使用大小计算,参数上述计算法则。
  • 但是需要预留足够的内存空间给page cache。假设一台32GB内存的机器,配置16GB内存给ES进程使用,另外16GB给page cache,而不是将 xmx
    设置成32GB。因为还是预留给lucene

-XX:+AlwaysPreTouch

  • 配置JVM参数:-XX:+AlwaysPreTouch 减少新生代晋升到老年代时停顿。

JDK官方文档关于 AlwaysPreTouch 的解释是

在启动时就把参数里说好了的内存全部都分配了,会使得启动慢上一点,但后面访问时会更流畅,比如页面会连续分配,比如不会在晋升新生代到老生代时才去访问页面,导致GC停顿时间加长。

  1. 我想说一下Linux内存分配、Linux OOM Killer、内存交换vm.swappiness参数 之间的一些理解:当ES进程向操作系统MMU申请分配内存时,操作系统内核分配的是虚拟内存,比如指定 -Xms=16G

那么只是告诉内核这个ES进程在启动的时候最需要16G内存,但是ES进程在启动后并不是立即就用了16G的内存。因此,随着ES的运行,ES进程访问虚拟内存时产生缺页错误(page

fault),然后内核为之分配实际的物理页面(这个过程也是需要开销的)。而如果在JVM启动时指定了AlwaysPreTouch,就会分配实际的物理内存,这样在发生YGC的时候,新生代对象晋升到老年代,减少老年代空间分配产生的缺页异常,从而减少YGC停顿时间。

-XX:CMSInitiatingOccupancyFraction 设置成75

  • 主要是因为CMS是并发收集,垃圾回收线程和用户线程同时运行,用户线程运行时可能继续无用的垃圾对象,如果到90%再去回收就太晚了。老年代使用到75%就回收可减少OOM异常发生的概率。

-XX:MaxTenuringThreshold 设置成6

  • 这个值默认为15,即Survivor区对象经历15次Young GC后才进入老年代,设置成6就只需6次YGC就晋升到老年代了。默认情况下ES新生代采用 -XX:+UseParNewGC,老年代采用CMS垃圾回收器。

-Xss 配置为1M。

  • 线程占用栈内存,默认每条线程为1M。从这个参数可看出一个进程下可创建的线程数量是有限制的,可视为创建线程的开销。

2. Swapping内存交换设置

2.1 禁用内存交换(/proc/sys/vm/swappiness),解释为什么要禁用内存交换:

内存交换到磁盘对服务器性能来说是致命的。想想看一个内存的操作必须是快速的。如果内存交换到磁盘上,一个100微秒的操作可能变成10毫秒,再想想那么多10微秒的操作时延累加起来。不难看出swapping对于性能是多么可怕。 最好的办法就是在你的操作系统中完全禁用swapping

  • 第一步:禁用操作系统swapping

    • 暂时禁用:sudo swapoff -a
    • 永久禁用:修改参数的方法是修改/etc/sysctl.conf文件,加入vm.swappiness=xxx,并重起系统。这个操作相当于是修改虚拟系统中的/proc/sys/vm/swappiness文件,将值改为XXX数值。
      如果不想重起,可以通过sysctl -p动态加载/etc/sysctl.conf文件,但建议这样做之前先清空swap。
  • 第二步:以上系统参数配置完成,还要修改elasticsearch.yml配置:

    bootstrap.memory_lock : true 锁定内存,防止进行内存的交换使用swapping

  • 第三步:对于系统也要一些设置,不然可能无法启动。修改完需要重新启动机器。

    • 修改文件/etc/security/limits.conf,最后添加以下内容。
    bash 复制代码
    * hard memlock unlimited
    * soft memlock unlimited
  • 修改文件 /etc/systemd/system.conf ,分别修改以下内容。

    bash 复制代码
    DefaultLimitNOFILE=65536
    DefaultLimitNPROC=32000
    DefaultLimitMEMLOCK=infinity

3. elasticsearch.yml设置

3.1 index设置

index 缓冲区大小 ,默认为整个堆空间的10%

yml 复制代码
indices.memory.index_buffer_size: 20%

3.2 搜索设置

数据检索和计数请求线程池设置它的类型默认为fixed,size默认为(可用处理器的数量* 3) / 2) + 1,

yml 复制代码
thread_pool.search.size: 5

数据检索和计数请求线程池队列,队列的size默认为1000。

yml 复制代码
thread_pool.search.queue_size: 1000

3.3 写入设置

⚠️这个参数慎用!强制修改cpu核数,以突破写线程数限制

如果CPU性能开销有余,可以设置为部署机器的CPU核数

bash 复制代码
# processors: 16
# Bulk pool
thread_pool.bulk.size: 16     es6写法
thread_pool.write.size: 16     es7写法

4.其他设置

4.1 集群熔断内存比例设置

bash 复制代码
PUT /_cluster/settings
{
   "persistent" : {    
        "indices.breaker.fielddata.limit": "60%",
        "indices.breaker.request.limit": "40%",
        "indices.breaker.total.limit": "70%"
。。。
   }

在elasticsearch.yml中也可以设置

⚠️谨慎设置,会影响查询和插入,产生错误

  • 错误提示
bash 复制代码
 

{
  "statusCode": 429,
  "error": "Too Many Requests",
  "message": "[circuit_breaking_exception] 
  [parent] Data too large, data for [<http_request>] would be [2087772160/1.9gb], 
  which is larger than the limit of [1503238553/1.3gb], 
  real usage: [2087772160/1.9gb],
  new bytes reserved: [0/0b], 
  usages [request=0/0b, fielddata=1219/1.1kb, in_flight_requests=0/0b, accounting=605971/591.7kb], 
  with { bytes_wanted=2087772160 & bytes_limit=1503238553 & durability=\"PERMANENT\" }"
}
  • 重要解决办法
    关闭circuit检查:
    indices.breaker.type: none
4.1.1 fielddata内存限制设置

indices.breaker.fielddata.limit:fielddata的内存限制,默认60%?? 7.0版本默认是 40%

  • 参数 indices.fielddata.cache.size 控制有多少堆内存是分配给fielddata。当你执行一个查询需要访问新的字段值的时候,将会把值加载到内存,然后试着把它们加入到fielddata。如果结果的fielddata大小超过指定的大小 ,为了腾出空间,别的值就会被驱逐出去。
  • 默认情况下,这个参数设置的是无限制 --- Elasticsearch将永远不会把数据从fielddata里替换出去。
  • 这个默认值是故意选择的:fielddata不是临时的cache。它是一个在内存里为了快速执行必须能被访问的数据结构,而且构建它代价非常昂贵。如果你每个请求都要重新加载数据,性能就会很差。
4.1.2 request内存限制设置

查询本身也会对响应的延迟产生重大影响。为了在查询时不触发熔断并导致Elasticsearch集群处于不稳定状态,

indices.breaker.request.limit:执行聚合的内存限制,默认40% 7.0版本默认是 60%

4.1.3 总体内存限制设置

可以根据查询的复杂性将indices.breaker.total.limit设置为适合您的JVM堆大小。此设置的默认值是JVM堆的70%。

indices.breaker.total.limit:综合上面两个,限制在70%以内 7.0版本默认是 90%

4.2 避免脑裂

网络异常可能会导致集群中节点划分出多个区域,区域发现没有 Master 节点的时候,会选举出了自己区域内 Maste 节点,导致一个集群被分裂为多个集群,使集群之间的数据无法同步,我们称这种现象为脑裂。

为了防止脑裂,我们需要在 Master 节点的配置文件中添加如下参数:

discovery.zen.minimum_master_nodes=(master_eligible_nodes/2)+1 //默认值为1

  • 其中 master_eligible_nodes 为 Master 集群中的节点数。这样做可以避免脑裂的现象都出现,最大限度地提升集群的高可用性。
  • 只要不少于 discovery.zen.minimum_master_nodes 个候选节点存活,选举工作就可以顺利进行。

二、优化篇

1、写入速度优化

1.1 设置持久化异步延时提交

bash 复制代码
XPUT http://xxxx:9200/m_pd_cu_id_gps_2es_inc_hi_out/_settings -d '
{
   "index.translog.durability" : "async",
   "index.translog.sync_interval" : "30s",
   "index.translog.flush_threshold_size" : "1024mb"
}
 

说明:

  • (1)加大 Translog Flush ,目的是降低 Iops、Writeblock。
    默认情况下,将 index.translog.durability 设置为 request ,这意味着 Elasticsearch 仅在成功对主分片和每个已分配副本进行事务同步并提交事务后,才将索引、删除、更新或批量请求的成功报告给客户端。
    如果将 index.translog.durability 设置为 async ,则 Elasticsearch 同步和提交事务日志仅在每个 index.translog.sync_interval 时执行,这意味着当节点恢复时,在崩溃之前执行的任何操作都可能会丢失。
  • (2)增加 Index Refresh 间隔,目的是减少 Segment Merge 的次数。
    "index.translog.sync_interval" : "30s" 结合业务对于数据及时性要求,实时性要求不高的,可以设置更大。
    将 Translog 多长时间同步到磁盘并提交一次。默认为5s。不允许小于100ms的值。
  • (3)加大 Flush 设置
    主要目的是把文件缓存系统中的段持久化到硬盘,当Translog的数据量达到 512MB 或者 30 分钟时,会触发一次 Flush。
    "index.translog.flush_threshold_size" : "1024mb"
    事务日志存储尚未安全地持久化在 Lucene 中的所有操作(即不是 Lucene 提交点的一部分)。尽管可以读取这些操作,但是如果分片已停止并且必须恢复,则需要重播它们。此设置控制这些操作的最大总大小,以防止恢复花费太长时间。一旦达到最大大小,将进行刷新,从而生成新的 Lucene 提交点。默认为 512mb 。

1.2设置写入并发数,调整 Bulk 线程池和队列

修改elasticsearch.yml

yml 复制代码
thread_pool.bulk.size: 16         //es6写法 
thread_pool.write.size: 16        //es7写法

1.3 段合并优化

(1) 归并线程的速度优化:

⚠️ 7.0版本不再支持

Lucene 以段的形式存储数据。当有新的数据写入索引时,Lucene 就会自动创建一个新的段。随着数据量的变化,段的数量会越来越多,消耗的多文件句柄数及 CPU 就越多,查询效率就会下降。

由于 Lucene 段合并的计算量庞大,会消耗大量的 I/O,所以 ES 默认采用较保守的策略,让后台定期进行段合并,如下所述:

  • 索引写入效率下降:当段合并的速度落后于索引写入的速度时,ES 会把索引的线程数量减少到 1。这样可以避免出现堆积的段数量爆发,同时在日志中打印出"now throttling indexing"INFO 级别的"警告"信息。
  • 提升段合并速度:ES 默认对段合并的速度是 20m/s,如果使用了 SSD,我们可以通过以下的命令将这个合并的速度增加到 100m/s。对大日志数据ELK Stack应用, 建议将其调大到100MB或更高. 可以通过API设置, 也可以写在配置文件中:
bash 复制代码
PUT _cluster/settings
{
    "persistent" : {
        "indices.store.throttle.max_bytes_per_sec" : "100mb"
    }
}

// 响应结果如下:

bash 复制代码
{
    "acknowledged": true,
    "persistent": {
        "indices": {
            "store": {
                "throttle": {
                    "max_bytes_per_sec": "100mb"
                }
            }
        }
    },
    "transient": {}
}

(2) 归并线程的数目:

推荐设置为CPU核心数的一半, 如果磁盘性能较差, 可以适当降低配置, 避免发生磁盘IO堵塞:

bash 复制代码
PUT employee/_settings
{
    "index.merge.scheduler.max_thread_count" : 8
}

也可以在elasticsearch.yml 设置:

yml 复制代码
index.merge.schedule`Ω`r.max_thread_count: 1

说明:

  • index.merge.scheduler.max_thread_count默认设置为Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))但这适用于SSD配置。
  • 对于HDD,应将其设置为1。机械磁盘在并发 I/O 支持方面比较差,所以我们需要降低每个索引并发访问磁盘的线程数。这个设置允许 max_thread_count + 2 个线程同时进行磁盘操作,也就是设置为 1 允许三个线程。

(3) 合并策略:目前可能用不到,高版本不再支持

merge策略index.merge.policy有三种:

tiered(默认策略);log_byete_size; log_doc。

yml 复制代码
# 优先归并小于此值的segment, 默认是2MB:
index.merge.policy.floor_segment
 
# 一次最多归并多少个segment, 默认是10个:
index.merge.policy.max_merge_at_once
 
# 一次直接归并多少个segment, 默认是30个
index.merge.policy.max_merge_at_once_explicit
 
# 大于此值的segment不参与归并, 默认是5GB. optimize操作不受影响
index.merge.policy.max_merged_segment

(4)重并发缓存加大

设置索引缓冲buffer,最大512m,默认值是jvm的10%;

elasticsearch.yml :

yml 复制代码
indices.memory.index_buffer_size : 20%
indices.memory.min_index_buffer_size: 96mb
  • index buffer 的大小是所有的 shard 公用的对于每个 shard 来说,最多给 512mb,因为再大性能就没什么提升了。ES 会将这个设置作为每个 shard 共享的 index buffer,那些特别活跃的 shard 会更多的使用这个 buffer。默认这个参数的值是 10%,也就是 jvm heap 的 10%。
  • 已经索引好的文档会先存放在内存缓存中,等待被写到到段(segment)中。缓存满的时候会触发段刷盘(吃i/o和cpu的操作)。默认最小缓存大小为48m,不太够,最大为堆内存的10%。对于大量写入的场景也显得有点小。

2、查询速度优化

2.1 特定搜索场景,增加搜索线程池配置

默认情况下,Elasticsearch将主要用例是搜索。在需要增加检索并发性的情况下,可以增加用于搜索设置的线程池,与此同时,可以根据节点上的CPU中的核心数量多少斟酌减少用于索引的线程池。

举例:更改配置文件elasticsearch.yml增加如下内容:

yml 复制代码
thread_pool.search.queue_size: 500

#queue_size允许控制没有线程执行它们的挂起请求队列的初始大小。

2.2 降低刷新频率设置

bash 复制代码
10.10.60.15:9200/str/_settings -d
{
       "refresh_interval": "5s"
}
  • 刷新频率由 refresh_interval 参数控制,默认每 1 秒发生一次。也就是说,新插入的文档在刷新到段(内存中)之前,是不能被搜索到的。
  • 关于是否需要实时刷新:
    • 如果新插入的数据需要近乎实时的搜索功能,则需要频繁刷新。
    • 如果对最新数据的检索响应没有实时性要求,则应增加刷新间隔,以提高数据写入的效率,从而应释放资源辅助提高查询性能。
  • 关于刷新频率对查询性能的影响:
    • 由于每刷新一次都会生成一个 Lucene 段,刷新频率越小就意味着同样时间间隔,生成的段越多。每个段都要消耗句柄和内存。
    • 每次查询请求都需要轮询每个段,轮询完毕后再对结果进行合并。
    • 也就意味着:refresh_interval 越小,产生的段越多,搜索反而会越慢;反过来说,加大 refresh_interval,会相对提升搜索性能。
相关推荐
jump_jump7 小时前
流式 HTML:从 htmx 片段装配到浏览器原生增量渲染
javascript·性能优化·前端工程化
大树881 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠1 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
大志哥1231 天前
ES和Logstash日志链路系统上线后遭遇切片爆炸(解决)
大数据·elasticsearch
霸道流氓气质1 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小小工匠1 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
Inhand陈工1 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智1 天前
ARP代理--工作原理
运维·网络·arp·arp代理
shushangyun_1 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
TableRow1 天前
参数化搜索的实现原理:从多维索引到查询优化
elasticsearch·全文检索