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,会相对提升搜索性能。
相关推荐
Htht1111 分钟前
【Linux】之【Bug】VMware 虚拟机开机 一直卡在黑屏左上角下划线闪烁界面
linux·运维·bug
溟洵10 分钟前
Linux下学【MySQL】表的连接(inner join、left join、right join)(简单试题理解版)
linux·运维·mysql
健康平安的活着20 分钟前
性能调优-cpu的性能指标【经典篇】
linux·运维·服务器
努力努力再努力wz34 分钟前
【Linux实践系列】:用c语言实现一个shell外壳程序
linux·运维·服务器·c语言·c++·redis
Watink Cpper1 小时前
[MySQL初阶]MySQL(1)MySQL的理解、库的操作、表的操作
linux·运维·服务器·数据库·c++·后端·mysql
m0_748256141 小时前
springboot之集成Elasticsearch
spring boot·后端·elasticsearch
青木沐1 小时前
基于 openEuler 22.09 的 OpenStack Yoga 部署
运维·服务器·云计算·openeuler·私有云
鱼忆梦1 小时前
Debian基于Hexo搭建个人博客
运维·debian
赵民勇1 小时前
debian/control 文件中的${misc:Depends}
linux·运维·debian
shenzhenNBA2 小时前
Docker相关知识
linux·运维·docker·容器