前言
本方案主要从运维层面分析es是实际生产使用过程中的参数优化,深入理解es各个名词及含义,深入分析es的使用过程中应注意的点,详细解释参数设置的原因以及目的,主要包括系统层面,参数层面。除此之外,优化方面还有查询优化和写入优化等。
第一章 基本概念
Elasticsearch 是一个分布式的免费开源搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;Elastic Stack 是一套适用于数据采集、扩充、存储、分析和可视化的免费开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据。
Cluster :包含client、master、 data、 ingest各个节点的集群(其中master data是集群中必须存在的)
Node :集群服务单元
Index :ES中的索引,类似于msyql中的库名,包含一个或多个物理分片,它只是这些分片的逻辑命名空间
Type :一个 index 的不同分类,6.x 后只能配置一个 type,以后将移除
Document :最基础的可被索引的数据单元,如一个 JSON 串
Shards :一个分片是一个底层的工作单元,它仅保存全部数据中的一部分,它是一个 Lucence 实例 (一个 lucene 索引最大包含 2,147,483,519 (= Integer.MAX_VALUE - 128)个文档数量)
Replicas:分片副本,主要用于分片的实时备份,用于保障数据安全,高可用与分担检索压力。
分片与副本
分片与副本关系如图:
分片 (shard):首先我们需要知道ES 是一个分布式的搜索引擎,我们存放的数据 通常都会分解成不同部分, 分布在不同节点的部分数据就是分片。一个索引可以有多个分片,同一个索引的所有分片的数据集合才是完整的数据。ES 自动管理和组织分片, 并在必要的时候对分片数据进行再平衡分配, 所以用户基本上不用担心分片的处理细节。创建索引时默认的分片数为 5 个,需注意一旦创建不能更改。
副本 (replica):ES 默认创建一份副本,就是说在 5 个主分片的基础上,每个主(Paimary)分片都相应的有一个副(replica)本分片。ES不允许Primary和它的Replica放在同一个节点中,并且同一个节点不接受完全相同的两个Replica。副本并不是越多越好,额外的副本有利有弊,有副本可以有更强的故障恢复能力,但也占了相应副本倍数的磁盘空间。
第二章 硬件优化
1.使用固态SSD
ES 是一种密集使用磁盘的应用,在段合并的时候会频繁操作磁盘,所以对磁盘要求较高,当磁盘速度提升之后,集群的整体性能会大幅度提高。
磁盘的选择,提供以下几点建议:
-
使用固态硬盘(Solid State Disk)替代机械硬盘。SSD 与机械磁盘相比,具有高效的读写速度和稳定性。
-
使用 RAID 0 。RAID 0 条带化存储,可以提升磁盘读写效率。
在 ES 的服务器上挂载多块硬盘。使用多块硬盘同时进行读写操作提升效率,在配置文件 ES 中elasticsearch.yml
-
设置多个存储路径 ,如下所示:
path.data:/path/to/data1,/path/to/data2
注意:避免使用 NFS(Network File System)等远程存储设备,网络的延迟对性能的影响是很大的。
2.设置合理的副本数和分片数
那我们在创建索引的时候,应该创建多少个分片与副本数呢?
对于副本来说比较好确定,可以根据集群规模和存储空间来定,如果存储空间足够大,可以设置多副本,一般建议设置为1-3个即可,但注意,生产环境不可将副本设置为0,若由于异常原因,主数据丢失则数据无法正常使用,设置副本合理值可保证容灾,并且副本数量是可以动态调整的。
对于分片数,是比较难确定的。并不是分片越多越好,查询效率越高,因为在查询过程中,有一个分片合并的过程,如果分片数不断的增加,合并的时间则会增加,而且随着更多的任务需要按顺序排队和处理,更多的小分片不一定要比查询较小数量的更大的分片更快。如果有多个并发查询,则有很多小碎片也会降低查询吞吐量。
一个索引分片数一旦确定,就不能更改,所以我们在创建索引前,要充分的考虑到,以后我们创建的索引所存储的数据量,否则创建了不合适的分片数,会对我们的性能造成很大的影响。一个分片的大小不要超过 50G,一般建议分片为最大为30G,这样的配置有助于集群的健康。实际生产环境还需要对数据进行压测,以确定最合适的分片数,确保更加充分利用ES集群性能。
如果当前es集群已做优化配置,但某个索引分片数并不合理,查询经常超时,那么可以按照时间去创建索引比如 Application_log_202209, Application_log_202210, Application_log_202211,然后进行通配符查询,这种是按照月份进行索引的创建,还可以按照天或者小时,当然,这个取决于数据量是否足够大。
3.系统优化
系统层面的调优主要是内存的设定与避免交换内存。关闭交换分区,我们知道如果ES使用了交换分区,性能会急剧下降。
大多数操作系统尝试将尽可能多的内存用于文件系统缓存,并急切地换掉未使用的应用程序内存。这可能导致部分 JVM 堆甚至其可执行页面被换出到磁盘。
交换对性能和节点稳定性非常不利,应该不惜一切代价避免。它可能导致垃圾收集持续几分钟而不是几毫秒,并且可能导致节点响应缓慢甚至与集群断开连接。在Elastic分布式系统中,让操作系统杀死节点更有效。
系统设置关闭交换分区:
swapoff -a
ES 安装后默认设置的堆内存是 1GB,显然是不够的,我们在生产环境都要重新配置内存。
要设置多少内存给 ES 呢?
其实这是要看我们集群节点的内存大小,还取决于我们是否在服务器节点上还是否要部署其他服务。
如果内存相对很大,如 64G 及以上,并且我们不在 ES 集群上部署其他服务,那么建议 ES 内存可以设置为 31G,我们知道es也是基于jvm的,jvm的最大设置内存一般为31G,所以这里有一个31G性能瓶颈问题,即使你给了 ES 集群大于 32G 的内存,其性能也不一定会更加高效,甚至会低于设置为 32G 时候的性能。
这里为什么是31G呢?
在 Java 中,所有对象都分配在堆上,然后有一个 Klass Pointer 指针指向它的类元数据。 这个指针在 64 位的操作系统上为 64 位,64 位的操作系统可以使用更多的内存(2^64)。在 32 位的系统上为 32 位,32 位的操作系统的最大寻址空间为 4GB(2^32)。 但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。浪费内存不算,更糟糕的是,更大的指针在主内存和缓存器(例如 LLC, L1等)之间移动数据的时候,会占用更多的带宽。
Java 使用内存指针压缩(Compressed Oops)技术来解决这个问题。它的指针不再表示对象在内存中的精确位置,而是表示偏移量。
这意味着 32 位的指针可以引用 4GB 个 Byte,而不是 4GB 个 bit。也就是说,当堆内存为 32GB 的物理内存时,也可以用 32 位的指针表示。不过,在越过那个神奇的边界 32GB 时,指针就会变为普通对象的指针,每个对象的指针都变长了,就会浪费更多的内存,降低了 CPU 的性能,还要让 GC 应对更大的内存。
事实上,当内存到达 40~40GB 时,有效的内存才相当于内存对象指针压缩技术时的 32GB 内存,所以即便你有足够的内存,也尽量不要超过 32G,因此我们建议可以设置为 31GB。
以我们以前生产环境为例,单台服务器内存为64G,设置es内存为31G,留一半内存给系统,注意系统只运行了es服务,并未运行其他服务器抢占资源,以充分发挥ES集群的性能。还有一点就是确保堆内存最小值(Xms)与最大值(Xmx)的大小是相同的,防止程序在运行时改变堆内存大小,这是一个很耗系统资源的过程。
这里有个问题如果我的机器内存大于64G呢?
比如我有个机器有 128G 内存,我可以创建两个 Node,使用 32G 内存。也就是说 64G 内存给 ES 的堆内存,剩下的 64G 给 Lucene。其他类型依次类推。但并不建议一台机器运行多个同一集群的es节点。
避免交换内存,可以在配置文件中对内存进行锁定,以避免交换内存(也可以在操作系统层面进行关闭内存交换)。
对应的参数: bootstrap.mlockall: true
第三章 参数优化
1.Elasticsearch.yml参数解析
- node.name: "node1":集群中的节点名,在同一个集群中不能重复。节点的名称一旦设置,就不能再改变了。当然,也可以设置成服务器的主机名称,例如 node.name:${HOSTNAME}。
- noed.master:true:指定该节点是否有资格被选举成为 Master 节点,默认是 True,如果被设置为 True,则只是有资格成为 Master 节点,具体能否成为 Master 节点,需要通过选举产生。
- node.data:true:指定该节点是否存储索引数据,默认为 True。数据的增、删、改、查都是在 Data 节点完成的。
- index.number_of_shards:5:设置都索引分片个数,默认是 5 片。也可以在创建索引时设置该值,具体设置为多大都值要根据数据量的大小来定。如果数据量不大,则设置成 1 时效率最高。
- index.number_of_replicas:1:设置默认的索引副本个数,默认为 1 个。副本数越多,集群的可用性越好,但是写索引时需要同步的数据越多。
- path.conf:/path/to/conf:设置配置文件的存储路径,默认是 ES 目录下的 Conf 文件夹。建议使用默认值。
- path.data:/path/to/data1,/path/to/data2:设置索引数据多存储路径,默认是 ES 根目录下的 Data 文件夹。切记不要使用默认值,因为若 ES 进行了升级,则有可能数据全部丢失。 可以用半角逗号隔开设置的多个存储路径,在多硬盘的服务器上设置多个存储路径是很有必要的。
- path.logs:/path/to/logs:设置日志文件的存储路径,默认是 ES 根目录下的 Logs,建议修改到其他地方。
- path.plugins:/path/to/plugins:设置第三方插件的存放路径,默认是 ES 根目录下的 Plugins 文件夹。
- bootstrap.mlockall:true:设置为 True 时可锁住内存。因为当 JVM 开始 Swap 时,ES 的效率会降低,所以要保证它不 Swap。
- network.bind_host:192.168.0.1:设置本节点绑定的 IP 地址,IP 地址类型是 IPv4 或 IPv6,默认为 0.0.0.0。
- network.publish_host:192.168.0.1:设置其他节点和该节点交互的 IP 地址,如果不设置,则会进行自我判断。
- network.host:192.168.0.1:用于同时设置 bind_host 和 publish_host 这两个参数。
- http.port:9200:设置对外服务的 HTTP 端口,默认为 9200。ES 的节点需要配置两个端口号,一个对外提供服务的端口号,一个是集群内部使用的端口号。 http.port 设置的是对外提供服务的端口号。注意,如果在一个服务器上配置多个节点,则切记对端口号进行区分。
- transport.tcp.port:9300:设置集群内部的节点间交互的 TCP 端口,默认是 9300。注意,如果在一个服务器配置多个节点,则切记对端口号进行区分。
- transport.tcp.compress:true:设置在节点间传输数据时是否压缩,默认为 False,不压缩。
- discovery.zen.minimum_master_nodes:1:设置在选举 Master 节点时需要参与的最少的候选主节点数,默认为 1。如果使用默认值,则当网络不稳定时有可能会出现脑裂。 合理的数值为(master_eligible_nodes/2)+1,其中 master_eligible_nodes 表示集群中的候选主节点数。
- discovery.zen.ping.timeout:3s:设置在集群中自动发现其他节点时 Ping 连接的超时时间,默认为 3 秒。 在较差的网络环境下需要设置得大一点,防止因误判该节点的存活状态而导致分片的转移。
2.其他重要参数
2.1 refresh参数
Lucene 将待写入的数据先写到内存中,超过 1 秒(默认)时就会触发一次 Refresh,然后 Refresh 会把内存中的的数据刷新到操作系统的文件缓存系统中。
如果我们对搜索的实效性要求不高,可以将 Refresh 周期延长,例如 30 秒,若对实时性要求较高,切勿将此参数设置的较高(默认1s即可)。
设置示例:
index.refresh_interval:30s
2.2 Flush参数
Flush 的主要目的是把文件缓存系统中的段持久化到硬盘,当 Translog 的数据量达到 512MB 或者 30 分钟时,会触发一次 Flush。
index.translog.flush_threshold_size 参数的默认值是 512MB,我们进行修改。
2.3 合并线程参数
index.merge.scheduler.max_thread_count: 1 索引 merge 最大线程数设置为 1 个,该参数可以有效调节写入的性能。因为在存储介质上并发写,由于寻址的原因,写入性能不会提升,只会降低。
如果是机械盘可以设置为1
index.merge.scheduler.max_thread_count: 1,如果是SSD,则可以设置多个线程,比如6个合并线程:
index.merge.scheduler.max_thread_count: 6
如何确定设置多少呢?
首先我们的服务器是多线程的,不可超过服务器的最大线程数,其次我们还需要通过一定的性能压测,来查看设置的参数是否有利于提升es集群性能。
段合并,合并在后台定期执行,比较大的 segment 需要很长时间才能完成,为了减少对其他操作的影响(如检索),elasticsearch 进行阈值限制,默认是 20MB/s,可配置的参数:"indices.store.throttle.max_bytes_per_sec" : "200mb"
2.4 index缓存参数
如果我们要进行非常重的高并发写入操作可以将indices.memory.index_buffer_size 调大一些,index buffer 的大小是所有的 shard 公用的,一般建议,对于每个 shard 来说,最多给 512mb,因为再大性能就没什么提升了。ES 会将这个设置作为每个 shard 共享的 index buffer,那些特别活跃的 shard 会更多的使用这个 buffer。默认这个参数的值是 10%,也就是 jvm heap 的 10%。
indices.memory.index_buffer_size:512m
2.5 超时参数
- discovery.zen.ping_timeout 判断 master 选举过程中,发现其他 node 存活的超时设置
- discovery.zen.fd.ping_interval 节点被 ping 的频率,检测节点是否存活
- discovery.zen.fd.ping_timeout 节点存活响应的时间,默认为 30s,如果网络可能存在隐患,可以适当调大
- discovery.zen.fd.ping_retries ping 失败/超时多少导致节点被视为失败,默认为 3
2.6 优化完成参数示例
index.merge.scheduler.max_thread_count:1 | # 索引merge最大线程数 |
indices.memory.index_buffer_size:30% | # 内存 |
index.translog.durability:async | #异步写硬盘,增大写的速度 |
index.translog.sync_interval:120s | #translog 间隔时间 |
discovery.zen.ping_timeout:120s | # 心跳超时时间 |
discovery.zen.fd.ping_interval:120s | # 节点检测时间 |
discovery.zen.fd.ping_timeout:120s | #ping 超时时间 |
discovery.zen.fd.ping_retries:6 | # 心跳重试次数 |
thread_pool.bulk.size:20 | # 写入线程个数 |
thread_pool.bulk.queue_size:1000 | # 写入线程队列大小 |
index.refresh_interval:300s | #index 刷新间隔 |