Elasticsearch 7.X 性能优化



Elasticsearch 7.X 性能优化


一、ES性能优化

在前面的文章我们系统的对ES进行了讲解,包括rest方式操作ES、集群、水平扩容、常见几种分词器的使用、以及Java客户端的操做,本篇文章我们一起来学习下ES的性能优化。

二、索引刷新频率 refresh_interval

elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh 。 默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 elasticsearch 是 近 实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。
这些行为可能会对新用户造成困惑: 他们索引了一个文档然后尝试搜索它,但却没有搜到。这个问题的解决办法是用 refresh API 执行一次手动刷新:
向ES服务器发送POST请求:
http://127.0.0.1:9200/user/_refresh
尽管刷新是比提交轻量很多的操作,它还是会有性能开销。当写测试的时候, 手动刷新很有用,但是不要在生产环境下每次索引一个文档都去手动刷新。
并不是所有的情况都需要每秒刷新。可能你正在使用 elasticsearch 索引大量的日志文件,你可能想优化索引速度而不是近实时搜索, 可以通过设置refresh_interval , 降低每个索引的刷新频率。
refresh_interval默认1s刷新一次,我们可以修改为30s一次,这样还可以有效地减少段刷新次数,但这同时意味着需要消耗更多的 Heap 内存。
向es服务器发送PUT请求:
http://127.0.0.1:9200/user/_settings
{
    "settings": {
        "refresh_interval": "30s"
    }
}
refresh_interval 可以在既存索引上进行动态更新。 在生产环境中,当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来。
关闭自动刷新:
{
    "settings": {
        "refresh_interval": "-1"
    }
}

三、translog 持久化变更

如果没有用 fsync 把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在。为了保证 elasticsearch 的可靠性,需要确保数据变化被持久化到磁盘。一次完整的提交会将段刷到磁盘,并写入一个包含所有段列表的提交点。elasticsearch 在启动或重新打开一个索引的过程中使用这个提交点来判断哪些段隶属于当前分片。
即使通过每秒刷新(refresh)实现了近实时搜索,我们仍然需要经常进行完整提交来确保能从失败中恢复。但在两次提交之间发生变化的文档怎么办?我们也不希望丢失掉这些数据。elasticsearch 增加了一个 translog ,或者叫事务日志,在每一次对 elasticsearch 进行操作时均进行了日志记录。
整个流程如下:
一个文档被索引之后,就会被添加到内存缓冲区,并且追加到了 translog
主动刷新refresh 或者 达到refresh_interval时间自动 refresh,这些在内存缓冲区的文档被写入到一个新的段中,即写入了文件系统缓存中,使其可被搜索,内存缓冲区被清空。
直到每隔一段时间,例如 translog 变得越来越大或者索引被刷新(flush),一个新的 translog 被创建,并且一个全量提交被执行。所有在内存缓冲区的文档都被写入一个新的段。缓冲区被清空。一个提交点被写入硬盘。文件系统缓存通过 fsync 被刷新(flush)。老的 translog 被删除。
translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录。当elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。
translog 也被用来提供实时 CRUD 。当你试着通过 ID 查询、更新、删除一个文档,它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更。这意味着它总是能够实时地获取到文档的最新版本。
执行一个提交并且截断 translog 的行为在 elasticsearch 被称作一次 flush
分片每 30 分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新。
translog 的目的是保证操作不会丢失,在文件被 fsync 到磁盘前,被写入的文件在重启之后就会丢失。默认 translog 是每 5 秒被 fsync 刷新到硬盘, 或者在每次写请求完成之后执行(e.g. index, delete, update, bulk)。这个过程在主分片和复制分片都会发生。最终, 基本上,这意味着在整个请求被 fsync 到主分片和复制分片的 translog 之前,你的客户端不会得到一个 200 响应。
translog 的数据量达到512MB 或者 30 分钟时,会触发一次 flush
可以修改index.translog.flush_threshold_size参数优化:
向es服务器发送PUT请求:
http://127.0.0.1:9200/user/_settings
{
    "settings": {
        "index.translog.flush_threshold_size": "800m"
    }
}

三、合理设置分片数

分片和副本的设计为 ES 提供了支持分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的。而且索引的分片完成分配后由于索引的路由机制,我们是不能重新修改分片数的。
如果分片数一开始就设置很大呢,这里我们需要知道
一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU 运转。
每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好, 但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。
用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度。
一个业务索引具体需要分配多少分片可能需要架构师和技术人员对业务的增长有个预先的判断,横向扩展应当分阶段进行。为下一阶段准备好足够的资源。 只有当你进入到下一个阶段,你才有时间思考需要作出哪些改变来达到这个阶段。一般来说,我们遵循一些原则:
控制每个分片占用的硬盘容量不超过 ES 的最大 JVM 的堆空间设置(一般设置不超过 32G,参考下文的 JVM 设置原则),因此,如果索引的总容量在 500G 左右,那分片大小在 16 个左右即可;当然,最好同时考虑原则 2。
考虑一下 node 数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了 1 个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以, 一般都设置分片数不超过节点数的 3 倍。
主分片,副本和节点最大数之间数量,我们分配的时候可以参考以下关系:节点数<=主分片数*(副本数+1)

四、 推迟分片分配

对于节点瞬时中断的问题,默认情况,集群会等待一分钟来查看节点是否会重新加入,如果这个节点在此期间重新加入,重新加入的节点会保持其现有的分片数据,不会触发新的分片分配。这样就可以减少 ES 在自动再平衡可用分片时所带来的极大开销。
通过修改参数 delayed_timeout ,可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改:
向es服务器发送PUT请求:
http://127.0.0.1:9200/user/_settings
修改为5分钟。
{
    "settings": {
        "index.unassigned.node_left.delayed_timeout": "5m"
    }
}

五、 使用自动生成id

在索引具有显式 id 的文档时,Elasticsearch 需要检查具有相同 id 的文档是否已存在于同一个分片中,这是一项代价高昂的操作,并且随着索引的增长而变得更加昂贵。通过使用自动生成的 id,Elasticsearch 可以跳过此检查,从而加快索引速度。

六、适量减少副本的数量

ES 为了保证集群的可用性,提供了 Replicas(副本)支持,然而每个副本也会执行分析、索引及可能的合并过程,所以 Replicas 的数量会严重影响写索引的效率。
当写索引时,需要把写入的数据都同步到副本节点,副本节点越多,写索引的效率就越慢。如果我们需要大批量进行写入操作, 可以先禁止Replica复 制。
通过设置index.number_of_replicas: 0 关闭副本同步。写入完成后再将Replica 修改回正常的状态。
向es服务器发送PUT请求:
http://127.0.0.1:9200/user/_settings
关闭副本:
{
    "settings": {
        "index.number_of_replicas": "0"
    }
}
在添加完成数据后,在修改为原先的分片数:
{
    "settings": {
        "index.number_of_replicas": "1"
    }
}

七、内存设置

ES 默认安装后设置的内存是 1GB,对于任何一个现实业务来说,这个设置都太小了。如果是通过解压安装的 ES,则在 ES 安装文件中包含一个 jvm.option 文件,添加如下命令来设置 ES 的堆大小:
-Xms16g
-Xmx16g
Xms 表示堆的初始大小,也就是最小堆内存,Xmx 表示可分配的最大内存。
确保 Xmx 和 Xms 的大小是相同的,其目的是为了能够在 Java 垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小而浪费资源,可以减轻伸缩堆大小带来的压力。
假设有一个 64G 内存的机器,按照正常思维思考,你可能会认为把 64G 内存都给ES 比较好,但现实是这样吗, 越大越好?虽然内存对 ES 来说是非常重要的,但是答案是否定的!因为 ES 堆内存的分配需要满足以下两个原则:
不要超过物理内存的 50%
Lucene 的设计目的是把底层 OS 里的数据缓存到内存中,Lucene 的段是分别存储到单个文件中的,这些文件都是不会变化的,所以很利于缓存,同时操作系统也会把这些段文件缓存起来,以便更快的访问。如果我们设置的堆内存过大,Lucene 可用的内存将会减少,就会严重影响降低 Lucene 的全文本查询性能。
堆内存的大小最好不要超过 32GB
在 Java 中,所有对象都分配在堆上,然后有一个 Klass Pointer 指针指向它的类元数据。这个指针在 64 位的操作系统上为 64 位,64 位的操作系统可以使用更多的内存(2^64)。在 32 位的系统上为 32 位,32 位的操作系统的最大寻址空间为 4GB(2^32)。但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。浪费内存不算,更糟糕的是,更大的指针在主内存和缓存器(例如 LLC, L1 等)之间移动数据的时候,会占用更多的带宽。
综上,我们生产环境如果有足够的内容,可以设置为31G的堆大小。
-Xms31g
-Xmx31g
假设你有个机器有 128 GB 的内存,你可以创建两个节点,每个节点内存分配不超过 32 GB。 也就是说不超过 64 GB 内存给 ES 的堆内存,剩下的超过 64 GB 的内存给 Lucene。
一般生产环境64 GB 内存的机器是非常理想的,但是 32 GB 和 16 GB 机器也是很常见的。少于 8 GB 会适得其反。

八、防止集群脑裂

脑裂问题可能的成因:
网络问题:集群间的网络延迟导致一些节点访问不到 master,认为 master 挂掉了从而选举出新的master,并对 master 上的分片和副本标红,分配新的主分片
节点负载:主节点的角色既为 master 又为 data,访问量较大时可能会导致 ES 停止响应造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
内存回收:data 节点上的 ES 进程占用的内存较大,引发 JVM 的大规模内存回收,造成 ES 进程失去
响应。
脑裂问题解决方案:
减少误判discovery.zen.ping_timeout 节点状态的响应时间,默认为 3s,可以适当调大,如果 master在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如 6s,discovery.zen.ping_timeout:6),可适当减少误判。
选举触发: discovery.zen.minimum_master_nodes:1
该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值,且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n/2)+1,n 为主节点个数,即有资格成为主节点的节点个数。
角色分离:即 master 节点与 data 节点分离,限制角色
主节点配置为:node.master: true node.data: false
从节点配置为:node.master: false node.data: true

九、根据磁盘使用情况来决定是否分配shard

es可以根据磁盘使用情况来决定是否继续分配shard。默认设置是开启的,也可以通过cluster.routing.allocation.disk.threshold_enabled: false 关闭:
向ES服务器发送PUT请求:
http://10.32.145.121:9200/_cluster/settings
请求体内容:
{
    "persistent": {
        "cluster.routing.allocation.disk.threshold_enabled": false
    }
}

persistent修改为transient,就是临时修改,ES重启即恢复原来的配置。
在上面参数为true开启的情况下,会有两个重要的设置:
cluster.routing.allocation.disk.watermark.low:控制磁盘最小使用率。默认85%.说明es在磁盘使用率达到85%的时候将会停止分配新的shard。也可以设置为一个绝对数值,比如500M.
{
    "persistent": {
        "cluster.routing.allocation.disk.watermark.low": "94%"
    }
}

cluster.routing.allocation.disk.watermark.high:控制磁盘的最大使用率。默认90%.说明在磁盘使用率达到90%的时候es将会relocate shard去其他的节点。同样也可以设置为一个绝对值。
{
    "persistent": {
        "cluster.routing.allocation.disk.watermark.high": "94%"
    }
}
ES 默认每隔30s会收集各个节点磁盘的使用情况,然后进行上面参数的对比,这个时间可以通过cluster.info.update.interval进行修改:
{
    "persistent": {
        "cluster.info.update.interval": "1m"
    }
}



yg9538 2022年7月22日 22:48 249 收藏文档