精通ES,这一篇就够了

原创 精选
开发 前端
本文图片大多来自官网,其余为作者作图。ES 的官网其实介绍的很详细,但是很少人将它整理出来。我这里整理了出来,并加上了自己的看法,希望可以和大家一起深入学习 ES。

作者 | 蔡柱梁

审校 | 重楼

目录

精通ES,这一篇就够了

目标

前言

1 ES 的分布式设计

    1.1 集群添加节点和移除节点

2 ES 的读写

    2.1 文档

    2.2 一个文档写入到一个分片中

    2.3 使文本可被搜索

        2.3.1 动态更新索引

        2.3.2 准实时搜索

        2.3.3 持久化变更

        2.3.4 段合并

    2.4 取回文档(读文档)

        2.4.1 取回单个文档

        2.4.2 分布式查询

        2.4.3 深度分页

3 管理和部署

    3.1 如何合理设置分片数

    3.2 扩容

    3.3 推迟分片分配

    3.4 部署

        3.4.1 上生产前应该要注意的事项

        3.4.2 滚动重启

        3.4.3 备份集群

        3.4.4 从快照恢复

4 总结

作者介绍

目标

1.了解 ES 的分布式设计

2.了解 ES 的读写

3.了解 ES 的管理和部署

前言

本文图片大多来自官网,其余为作者作图。ES 的官网其实介绍的很详细,但是很少人将它整理出来。我这里整理了出来,并加上了自己的看法,希望可以和大家一起深入学习ES。

参考文档:​Elasticsearch 7.17

1 ES 的分布式设计

ES 天生就是分布式的,并且在设计时屏蔽了分布式的复杂性,可以横向扩展至数百(甚至数千)的服务器节点,同时可以处理PB级数据。

我们先来了解可以处理的一些事情:

  • 分配文档到不同的容器或分片中,文档可以储存在一个或多个节点中
  • 按集群节点来均衡分配这些分片,从而对索引和搜索过程进行负载均衡
  • 复制每个分片以支持数据冗余,从而防止硬件故障导致的数据丢失
  • 将集群中任一节点的请求路由到存有相关数据的节点
  • 集群扩容时无缝整合新节点,重新分配分片以便从离群节点恢复接下来,我们来看下集群中一些重要的概念:
  • 分片

分片是 ES 实际存储数据的地方,我们都知道 ES 是使用 Java 语言实现的,而它的搜索的底层是基于 Lucene 实现的,而每一个分片就是一个 Lucene 实例。在 Elasticsearch 6.x 以及之前的版本,每一个索引创建时默认是 5 个分片的,在 Elasticsearc 7.0 开始默认是 1 个分片,即:

PUT /index_name
{
"settings" : {
# 一个主分片
"number_of_shards" : 1,
# 一个副本分片
"number_of_replicas" : 1
}
}

– master

主节点是集群中的控制节点,主要负责管理和维护集群的元数据(如索引、映射、分片分配等),并且负责选举和维护集群中的主分片。每个 ES 集群只有一个 Master 节点,但是如果 Master 节点宕机,集群会通过选举机制重新选举一个新的 Master 节点。

– data

数据节点持有包含你所索引的文档的分片。数据节点处理与数据有关的操作,如CRUD、搜索和聚合。这些操作是I/O、内存和CPU密集型的。监控这些资源是很重要的,如果它们过载了,可以增加更多的数据节点。拥有专用数据节点的主要好处是 master 和 data 角色分离。

– Coordinating node

协调节点是一种特殊的节点类型,它主要负责协调集群中的各个节点之间的通信和任务分配,不参与数据的存储和处理。需要注意的是,协调节点本身不存储数据,因此它们可以在任何节点上运行(即任何节点都可以成为协调节点),并且可以随时添加或删除。通过将协调节点与数据节点分开,可以实现更好的性能和可伸缩性,同时保证集群的稳定性和可靠性,但是不建议过多。因为 master 节点需要维护集群中的节点,如果数量过多,会给 master 造成过多的负担,从而影响性能。具体作用如下:

1.路由请求

协调节点负责处理集群中的路由请求,即将请求路由到正确的节点上进行处理。例如,当一个搜索请求被发送到集群时,协调节点会确定哪些分片需要被搜索,并将请求路由到包含这些分片的节点上进行处理。

2.分配任务

协调节点负责分配各种任务到集群中的节点上进行处理,例如分片的重新分配、节点的加入和移除等。

3.监控集群

协调节点负责监控集群中各个节点的状态和健康情况,并对集群中的故障进行处理。例如,当一个节点宕机时,协调节点会负责将该节点上的分片重新分配到其他节点上。

  • 管理索引和映射(这里和 master 不同,master 是维护元数据,而这里管理的是行为)

协调节点负责管理集群中的索引和映射,例如创建和删除索引、修改映射等。

  • Transport client(传输客户端)

 轻量级的传输客户端可以将请求发送到远程集群。它本身不加入集群,但是它可以将请求转发到集群中的一个节点上。

  • Node client(节点客户端)

 节点客户端作为一个非数据节点加入到本地集群中。换句话说,它本身不保存任何数据,但是它知道数据在集群中的哪个节点中,并且可以把请求转发到正确的节点。

1.1 集群添加节点和移除节点

当配置了同样的cluster.name: my-cluster的节点被发现后或者被移出集群,集群会对分片进行再平衡。

1)假设当前我集群中只有一个节点,该节点只有一个 index,设置了 3 个分片,具体如下:

PUT /blogs
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}

这时副分片是没有意义的,所以只有主分片,假设目前有三个分片,如下图:

2)假如此时,我添加了一个节点,集群将会为这些主分片创建副本,并分开存放(高可用,需要做到备份容灾,主分片与自己的备份不应该在同一节点),

假设只有一个副分片,如下图:

3)我们搭建最小集群,节点个数应该为 3 个(防止网络分区问题)。因此,再增加一个节点,分片将发生再平衡

4)这时 node 1 失联了,重现选主 node 2 作为 master 节点,分片也会发生再平衡

2 ES 的读写

ES 的读写,其实就是创建文档和读取文档的操作。因此,要了解这些操作,我们就要先了解文档。

2.1 文档

下面了解一些关于文档的概念:

  • 索引文档

所谓的索引文档就是指 通过使用 index API ,文档可以被 索引 —— 存储和使文档可被搜索。使文档可以被搜索,其实就是构建倒排索引。

  • 文档ID

ES 的每一个文档都有一个 ID,这个 ID 要么就是自己创建文档时提供,要么让 ES 自己生成,并且 index_name + id 可以确定唯一的文档(如果是 ES8 之前,则是 index_name + type + id 确定唯一的文档)。

  • 版本号

在 ES 中每个文档都有一个版本号,每当对文档进行修改时(包括删除),版本号的值就会递增,版本号用于处理并发冲突的场景。

上面提到了删除文档时,版本号也会递增,这是为什么呢?

因为在 ES 中,文档是不可变的,不能修改它们。因此,如果我们需要对一个文档进行修改时,那只能 重建索引 或者 进行替换。而删除只是更新文档的一种特殊情况而已。不管对文档进行修改还是删除,版本号递增后,我们只能读取到最新的文档。旧的文档会被标记为已删除,但是不会被马上物理删除。

修改文档的过程:

  1. 从旧文档构建
  2. 更改该
  3. 删除旧文档
  4. 索引一个新文档

2.2 一个文档写入到一个分片中

一个文档写入到一个分片中,又可以称为路由一个文档到一个分片。在说路由之前,我们先梳理下节点,分片,index,文档之间的关系:

  • index 是具有同一特征的文档的集合
  • 我们在创建 index 时需要指定分片数
  • 一个分片就是一个 Lucene 实例,而 Lucene 实例运行在 ES 节点上,一个节点允许有多个分片

下图表示一个拥有 3 个节点的小集群,一共有 2 个 index,每个 index 有 3 个分片(指的是有 3 个主分片,每个主分片有 1 个副本)。

假设有一个集群由三个节点组成。 它包含一个叫blogs的索引,有两个主分片,每个主分片有两个副本分片,具体如下(这是官网的例子):

假设现有要写入一个文档到 blogs,那么这个过程是怎样的呢?

首先要知道只有主分片具有处理写请求的能力,副本只提供读的能力,然后才是路由一个文档到一个主分片中存储起来。

至于是怎么路由的,请看下面的路由公式:

shard = hash(routing) % number_of_primary_shards

  • routing

routing 默认是文档 ID,也可以设置成一个自定义的值

  • number_of_primary_shards

number_of_primary_shards 指的是主分片的数量

这也是为什么我们确定好 index 的分片数后不能修改的原因,如果我们需要扩容,只能通过重建索引等手段来进行水平扩容了。

新建、索引(构建倒排索引)和删除 请求都是 写 操作, 必须在主分片上面完成之后才能被复制到相关的副本分片,如下图所示:

具体步骤如下:

  1. 客户端向任意节点发出写请求(图示例写着请求的是 node 1)
  2. node 1 通过路由算法确定文档应该分配到分片 P0 上,随后将请求转发到 node 3
  3. 主分片 P0 完成写请求后,转发请求给 node 1 和 node 2,让它们的副本分片 R0 完成数据同步
  4. 等到所有副本报告同步成功后,node 3 将向协调节点报告成功(这里的协调节点就是 node 1),协调节点向客户端报告成功

2.3 使文本可被搜索

上面说了一个文档写入的流程,但是文档是一下就写到磁盘吗?是一写入就立马可以被搜索吗?这些问题会在这一节中给出答案。

我们知道最早开始学全文搜索查询到对应的信息。ES 也是如此,那么如何实现全文搜索呢?前面也提到了 ES 采用的是倒排索引,我在《ES入门》有写倒排索引的原理,这里就不累述了。

当讨论倒排索引时,我们会谈到 文档 标引,因为历史原因,倒排索引被用来对整个非结构化文本文档进行标引。 ES 中的 文档 是有字段和值的结构化 JSON 文档。事实上,在 JSON 文档中, 每个被索引的字段都有自己的倒排索引。

早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。倒排索引被写入磁盘后是不可改变的(我们称之为倒排索引具有不变性)。这个特性的好处有:

  • 不需要锁

不变性意味着不怕高并发,所以不需要锁。

  • 极大提高了 ES 的性能

一旦索引被读入内核的文件系统缓存,便会一直在那。由于不变性,只要缓存还有足够的空间,那么大部分请求会直接请求内存。这就很大程度上提升了性能。

  • 其他缓存(如 filter)在索引生命周期内始终有效

因为数据是不会变的(索引都没变,数据当然没变)。

  • 减少 I/O 和需要被缓存到内存的索引的使用量

因为不变性,所以可以在对单个大的倒排索引写入时进行数据压缩。

当然,有好处,就会有坏处:我要新增一个文档,就需要重新构建一个新的索引,因为旧的索引是不可变的。这种设计会对索引包含的数据量和可被更新的频率有极大的限制。

2.3.1 动态更新索引

动态更新索引指的是增量操作文档后,会对索引进行补充索引来反映新的修改。

上面说了倒排索引的不变性的好处与坏处,那么如何在保留不变性的前提下实现倒排索引的更新呢?答案是:用更多的索引。

具体方法如下:

当发生了一些操作导致需要更新倒排索引,ES 不会直接更新索引,而是通过增加新的补充索引来反映最新的修改(也就是所谓的用更多索引)。那么要查询时,关于这个 index 的索引这么多,怎么用呢?也简单,就是按创建时间(升序)来遍历这些索引查询文档,最后对查询结果进行合并。

ES 基于上面的理论,引入了 按段搜索的概念,每一个段都是一个倒排索引,但是索引在段的集合外,还有一个概念提交点。在提交点内的段才是可见的,它包含的文档才是可被搜索的。

到这里,很容易将文档的集合——index(索引) 和 倒排索引(索引,分片中的段)搞混,也不知道它们与分片(Lucene 实例)是一个怎样的关系。因此,这里有必要再梳理下它们的关系:一个 index可以有多个分片,而一个分片中也可以有多个段用于搜索文档。

下面用一个例子说明动态更新索引是如何在保留倒排索引的不变性的同时实现索引更新,从而让新增的文档可被搜索的:

1)假设现在有一个 Lucene 索引当前包含了一个提交点和三个段

2)现在我们新增了一个新的文档。因为之前的索引是不变的,所以 ES 会进行补充索引,具体如下:

2.1)新文档被收集到内存索引缓存

2.2)不时地提交缓存

完整的提交缓存流程如下:

  • 在磁盘上写入一个追加的新段(也就是上面说的补充索引)
  • 在磁盘上写入一个新的提交点,该提交点包含新的段

经过上面两步后得到下图:

  • 所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件(通过 fsync 来 flush,这样在断电的时候就不会丢失数据)

2.3)新的段被开启,让它包含的文档可以被搜索

2.4)内存缓存被清空,等待接收新的文档

上面说了新增文档导致索引更新的情况,那么修改和删除文档呢?

因为段是不变的,所以文档变更导致的索引变化是没法在旧索引体现的。因此,每个提交点都会有一个 .del 文件,这个文件记录着这些过时的文档。不管是修改还是删除都会被标记成删除,但是没有真的从磁盘上删掉。当我们遍历段时,这些文档其实还是会被检索出来的,只是 ES 做结果合并时会过滤这部分被标记为删除的文档。

2.3.2 准实时搜索

ES 从数据写入到可被搜索不是实时的,而是有一定时延的,所以无法接受这一点的场景可能就不适合使用 ES 了。

2.3.1 说到了动态更新索引 是如何在保留不变性的前提下更新索引,让新的文档可被搜索的,但是这方案中的提交阶段需要 fsync 来确保段被物理性写入磁盘。如果每一次索引一个文档都要执行 fsync 的话,那么会对性能造成很大的影响。在 ES 和磁盘之间是文件系统缓存,为了提升性能, ES 做了一些列优化使得 ES 可以近实时搜索。

下面用一个例子说明 ES 的优化(还是用 “有一个 Lucene 索引当前包含了一个提交点和三个段” 这个前提):

1)新文档被写入了内存索引缓存中,如下图:

2)新文档被写入一个新的段中,该段被写入了文件系统缓存(即只是写入磁盘,但是没有用 fsync 来 flush)。Lucene 允许这个新段被写入和打开,使该段可以被搜索。

上面提到的 写入和打开一个新段的过程 就是 refresh,ES 提供 refresh API,我们可以手动触发,不手动触发的话,默认是每个分片每秒自动刷新一次。因此,我们说 ES 是准实时搜索。将 “使新的段可以被搜索” 拆得更细一点的话,具体步骤如下:

  • 新文档被写入一个新的段中
  • 该段被写入文件系统缓存并打开(refresh)
  • 同步数据到副本分片(如果有的话)
  • 更新集群状态(如果是集群的话)

master更新集群元数据

下面是对上面这些过程,帮助提升性能的一些思路:

  • refresh 的时间

合理调整 refresh 频率或者使用读写性能更强大的 SSD 硬盘。

  • 同步数据的时间

同步数据受网络,物理设备的状态,集群状态,副分片数量等因素影响,可以从这几个角度考虑提升。

  • master更新集群元数据的时间

这个受集群的大小和复杂程度影响,所以要合理搭建集群。

2.3.3 持久化变更

2.3.2说到为了实现准实时搜索完全抛弃了2.3.1也提到了动态更新索引最近一次完整的提交会将段刷到磁盘,但是这个提交点之后的操作呢?无法保证了,这样就可能会出现数据丢失。

为了保证 ES 的可靠性,需要确保数据变化被持久化到磁盘,为此 ES 增加了一个 translog(事务日志,类似 MySQL 的 redo 日志)。整个流程如下:

1)一个新文档被索引之后,就会被添加到内存缓冲区,并且相应的文档操作被写入到 translog(顺序IO)

2)refresh(分片每秒被 refresh 一次),具体操作如下:

  • 内存缓冲区中的文档被写到一个新段中,且没有进行 fsync
  • 新段被打开,使新段可以被搜索

打开新段:

  1. 将新段加入到已有段的文件列表中,并且建立该段的倒排索引、文档存储和其他元数据
  2. 更新索引的内存结构,包括倒排索引、字段信息、文档数和大小等
  • 内存缓存区被清空

因为索引刷新会触发 Lucene 的 SegmentReader 和 SegmentSearcher 的更新操作,使得新段可以被加入到搜索中,所以新段在刷新操作后可以被搜索。

3)这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志

4)不定时对索引 flush(分片每 30 分钟被 flush一次或者 translog 过大的时候也会触发),段被全量提交,并且事务日志被清空,具体操作如下:

  • 内存缓冲区中的所有文档都被写入一个新段
  • 缓冲区被清空
  • 一个提交点被写入磁盘
  • 文件系统缓存通过 fsync 被 flush
  • 老的 translog 被删除

translog 提供所有还没有被刷到磁盘的操作的一个持久化记录。当 ES 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。

这种执行一个提交并且截断 translog 的行为在 ES 中被称为一次 flush。分片每 30 分钟被自动 flush 一次或者 translog 太大的时候也会触发。具体可看 Translog。我们也能手动触发 flush,具体请看 Flush API。

2.3.4 段合并

段合并的时候会将那些旧的已删除文档从文件系统中清除,被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中。

默认每秒一次刷新,会导致段的数量在短时间能激增,但是如果降低 refresh 的频率,对于一些实时性要求较高的场景又不能满足。而且,段数量过多也会造成各种各样的问题:每一个段都会消耗文件句柄、内存和CPU运行周期;更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。ES 通过在后台进行 段合并 来解决这个问题。启动 段合并 不需要我们做任何事,ES 进行索引和搜索时会自动进行。

假设现在要进行段合并了:

1)合并进程选择大小相似的小段并且将他们合并到更大的段中(这样就不会中断索引和搜索)。如下图所示:

2)一旦合并结束,老的段被删除

合并大的段需要消耗大量的 I/O 和 CPU 资源,如果任其发展会影响搜索性能。ES 在默认情况下会对合并流程进行资源限制,所以搜索仍然有足够的资源很好地执行。

​2.3.4.1 段合并建议

2.3.4 末尾说到了 “合并大的段需要消耗大量的 I/O 和 CPU 资源”,但是合并是在后台定期操作的。因为这些操作可能要很长时间才能完成,尤其是比较大的段。这个通常来说都没问题,因为大规模段合并的概率是很小的。不过有时候合并会拖累写入速率,如果这个真的发生了,ES 会自动限制索引请求到单个线程里。这样可以防止出现 段爆炸 问题(即数以百计的段在被合并之前就生成出来)。

ES 默认设置在这块比较保守:不希望搜索性能被后台合并影响。不过有时候(尤其是 SSD,或者日志场景)限流阈值太低了。默认值是 20 MB/s,对机械磁盘应该是个不错的设置。

  • 如果服务器用的是 SSD,可以考虑提高到 100–200 MB/s。测试验证对系统哪个值比较合适:
PUT /_cluster/settings
{
"persistent" : {
"indices.store.throttle.max_bytes_per_sec" : "100mb"
}
}
  • 如果在做批量导入,完全不在意搜索,那么可以彻底关掉合并限流。这样让索引速度跑到你磁盘允许的极限:
PUT /_cluster/settings
{
"transient" : {
# 设置限流类型为 none 彻底关闭合并限流。等完成了导入,记得改回 merge 重新打开限流
"indices.store.throttle.type" : "none"
}
}

最后,可以增加 index.translog.flush_threshold_size 设置,从默认的 512 MB 到更大一些的值,比如 1 GB。这可以在一次清空触发的时候,在事务日志里积累出更大的段。而通过构建更大的段,清空的频率变低,大段合并的频率也变低。这一切合起来导致更少的磁盘 I/O 开销和更好的索引速率。当然,这会需要对应量级的 heap 内存用以积累更大的缓冲空间,调整这个设置的时候请记住这点。

不过一般对于中小公司的业务或者试探性的业务建议用默认设置就好了,否则自己不是特别熟悉 ES 的情况下,容易让 ES 性能下降又找不出原因。

2.3.4.2 科学的测试性能

2.3.4.1 有些设置给出的值是经验值,但不适用所有公司。因此,真的要改默认设置的话,性能测试很重要。下面给出一些方法论,如下:

  • 在单个节点上,对单个分片,无副本的场景测试性能。
  • 在 100% 默认配置的情况下记录性能结果,这样就有了一个对比基线。
  • 确保性能测试运行足够长的时间(30 分钟以上),这样可以评估长期性能,而不是短期的峰值或延迟。

一些事件(比如段合并,GC)不会立刻发生,所以性能概况会随着时间继续而改变的。

  • 开始在基线上逐一修改默认值。

严格测试需要修改的配置,如果性能提升可以接受,保留这个配置项,开始下一项。

2.4 取回文档(读文档)

关于读请求需要知道:

  • 在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
  • 在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。

2.4.1 取回单个文档

通过文档 ID 查询文档。

以下是从主分片或者副本分片检索文档的步骤顺序(官网例子):

  • 客户端向某个节点(假设现在是 Node 1) 发送获取请求。
  • 节点使用文档的 _id 来确定文档属于哪个分片。

假设是属于分片0,并应该请求 Node 2 的 R0,这时会将请求转发到 Node 2。

  • Node 2 将文档返回给 Node 1,然后将文档返回给客户端。

下面是上面步骤的流程图:

2.4.2 分布式查询

我们大多数时候都不是根据文档 ID 查询文档,而是根据某些条件筛选做聚合查询。这时,我们就需要了解下分布式查询是怎样处理请求的了。整体上分为两个阶段:查询阶段 和 取回阶段。

2.4.2.1 查询阶段

协调节点向各个分片发出查询请求,每个分片(可能是主分片,也可能是副分片,但是不会重复分片)查询出自己的结果并返回给协调节点,协调节点合并所有结果并得到最终查询结果。

下图是客户端的一次分布式查询请求图(以下图为例子说明查询阶段需要做的事情)

查询阶段包含以下三个步骤:

  • 客户端发送一个 search 请求到 Node 3 (协调节点可以是任意节点,这时 Node 3 成为了协调节点), Node 3 会创建一个大小为 from + size 的空优先队列。

协调节点将在之后的请求中轮询所有的分片来分摊负载。

  • Node 3 将查询请求转发到索引的每个 主分片/副分片 中。每个分片在本地执行查询并添加结果到大小为 from + size 的本地有序优先队列中。
  • 每个分片返回各自优先队列中所有文档的 ID 和 排序值 给协调节点,也就是 Node 3,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。

2.4.2.2 取回阶段

查询阶段得到的结果只是最终结果的文档 ID,但是要返回给客户端的需要是文档,所以需要取回文档这么一个动作。

2.4.2.1 的例子的分布式搜索的取回阶段的流程,大概如下图:

下面是步骤解释:

  1. 协调节点辨别出哪些文档需要被取回并向相关的分片提交多个
  2. 每个分片加载对应文档返回给协调节点
  3. 全部文档取回之后,协调节点返回结果给客户端

2.4.3 深度分页

了解了分布式查询后,相信大家都会有一个问题:

 分片要返回给协调节点from + size个结果,那么协调节点将会收到number_of_shards * (from + size)个结果,然后再排序。如果我要查第如果数据量更大,那么会占用多少

因此,我们要避免深度分页,如果真的无法避免,那么请使用​scroll​(Elasticsearch 7.x 开始不再推荐使用 scroll)或者 search after

3 管理和部署

3.1 如何合理设置分片数

 我们知道每个原因。可当公司大到一定规模或者老板觉得自己发展很好要求我们要预设一段时间内可以承受的访问量时,就不可能用默认的分片数或者随便设置了。

要注意的点如下:

  • 硬件

索引的分片数量应该与集群的可用硬件资源相匹配,特别是磁盘和内存资源。如果分片数量太大,可能会导致磁盘空间不足或内存不足,影响搜索性能。如果分片数量太少,可能会导致硬件资源的浪费。

  • 数据量

索引的分片数量应该与数据量相匹配,特别是对于大型数据集。通常,建议每个分片至少包含几十 GB 的数据。如果分片数量太多,可能会导致管理和查询索引变得困难,如果分片数量太少,可能会导致搜索性能下降。

  • 并发查询

索引的分片数量应该与集群中同时进行的查询数量相匹配。每个分片都需要处理查询请求,并返回结果;如果分片数量太少,可能会导致查询请求在队列中排队等待,影响查询性能。

  • 索引更新频率

索引的分片数量应该考虑索引的更新频率,如果索引更新频率很高,可能会导致更新操作在集群中的竞争,影响写入性能。

分片数建议不要超过节点数,当我们计算出分片数大于节点数时,我们应该增加节点。

因为分片数超过了节点数,同一个文档的主分片有多个在同一节点会让该节点负载过重,而且一旦该节点挂掉,会影响到多个分片的可用性。当然,资金短缺的情况就没办法了,那这时建议每个节点分配到的分片数尽量一致。

具体数值应该怎么定呢?

  1. 要尽量和生产中要使用的硬件资源,网络资源保持一致,然后单节点单分片,然后进行压测,逐渐增大数据量,看我们能接受的平均响应值对应的数据量是多少,假设 50 G。
  2. 再去看看我们实际业务场景预计需要支撑的数据量是多少(包括预留未来 3 年的预估值,如果业务给不出,就按照今年的值的 3 倍),然后除以 50,就是我们一个理论值的分片数了。
  3. 但是要考虑到 索引更新频率 问题,如果是文档里面某个字段高频更新导致的,考虑是否可以拆出来;如果不行,那就得对这个理论值进行实际压测,看看是否要减少分片数了。

3.2 扩容

大多数的扩容问题可以通过添加节点来解决。但有一种资源是有限制的,因此值得我们认真对待:集群状态。

集群状态是一种数据结构,贮存下列集群级别的信息:

  • 集群级别的设置
  • 集群中的节点
  • 索引以及它们的设置、映射、分析器、预热器(Warmers)和别名
  • 与每个索引关联的分片以及它们分配到的节点

集群状态存在于集群中的每个节点,包括客户端节点。这就是为什么任何一个节点都可以将请求直接转发至被请求数据的节点——每个节点都知道每个文档应该在哪里。

一旦某个索引要增加一个字段(数据结构需要变化的情况),那么接收到这个请求的主分片所在的节点必须向 master 汇报(因为只有 master 可以修改集群状态)。然后,master 要将更改合并到集群状态中,并向集群中所有节点发布一个新版本。请注意:集群状态包括了映射!因此,我们字段越多,集群状态也会越大,网络开销也会越大,所以最好想办法拆一些字段出来(垂直拆分)。而拆字段出来导致文档更多了也没关系,ES 本来就是为了解决大数据的,保持集群状态小而敏捷更重要。

3.3 推迟分片分配

我们都知道 ES 将自动在可用节点间进行分片均衡,包括新节点的加入和现有节点的离线。理论上来说,这个是理想的行为,我们想要提拔副本分片来尽快恢复丢失的主分片。我们同时也希望保证资源在整个集群的均衡,用以避免热点。

然而,在实践中,立即的再均衡所造成的问题会比其解决的更多。举例来说,考虑到以下情形:

  • 集群中的某个节点突然失联了(只是短暂的失联)。
  • master 立即发现了有节点离线了,然后在集群内提拔了拥有该节点主分片副本的副分片为主分片。
  • 在副本被提拔为主分片以后,master 节点开始执行恢复操作来重建缺失的副本。集群中的节点之间互相拷贝分片数据,网卡压力剧增,集群状态尝试变绿。
  • 由于目前集群处于非平衡状态,这个过程还有可能会触发小规模的分片移动。其他不相关的分片将在节点间迁移来达到一个最佳的平衡状态。

与此同时,失联的节点又回归集群了。不幸的是,这个节点被告知当前的数据已经没有用了,数据已经在其他节点上重新分配了。因此,它只能删除本地数据,然后重新开始恢复集群的其他分片(然后这又导致了一个新的再平衡)。这种开销无疑是极大而且是没有必要的。为了避免这种情况,ES 可以推迟分片的分配。这可以让集群在重新分配之前有时间去检测这个节点是否会再次重新加入。

下面将延时修改成 5 分钟:

PUT /_all/_settings 
{
"settings": {
"index.unassigned.node_left.delayed_timeout": "5m"
}
}

3.4 部署

经过 1 和 2 章节,相信大家对 ES 的核心原理已经比较清楚了,但是我们更多是作为使用者,所以如何正确部署、管理和使用其实更为重要。

3.4.1 上生产前应该要注意的事项

• 硬件

– 内存

 如果有一种资源是最先被耗尽的,它很可能是内存。排序和聚合都很耗内存,所以有足够的堆空间来应付它们是很重要的。即使堆空间比较小的时候, 也能为操作系统文件缓存提供额外的内存。因为

 内存一般选 8 ~ 64 GB,64 GB 内存的机器是非常理想的。少于8 GB 会适得其反(你最终需要很多很多的小机器),大于64 GB 的机器也会有问题。

– CPU

 大多数他资源,具体配置多少个(CPU)不是那么关键。常见的集群使用两到八个核的机器。

– 硬盘

 硬盘对所有的集群都很重要,对大量写入的集群更是加倍重要(例如那些存储日志数据的)。硬盘是服务器上最慢的子系统,这意味着那些写入量很大的集群很容易让硬盘饱和,使得它成为集群的瓶颈。纯看速度的话,推荐

– 网络

 快速可靠的网络显然对分布式系统的性能是很重要的。低延时能帮助确保节点间能顺畅通讯,大带宽能帮助分片移动和恢复。现代数据中心网络(1 GbE, 10 GbE)对绝大多数集群都是足够的。

  • JVM

每个 ES 版本都会有和它相匹配的推荐的 JDK 版本,自己注意就好了。这里说下关于堆的设置。首先 xms 和 xmx 应该设置成一样的。其次,堆占用的内存不应该超过服务器内存的一半。更详细请看:set-jvm-heap-size 。

  • 文件描述符和 MMap

Lucene 使用了大量的文件。同时, ES 在节点和 HTTP 客户端之间进行通信也使用了大量的套接字。这一切都需要足够的文件描述符。因此,安装好 ES 后,要请求 GET /_nodes/process 确认下 max_file_descriptors 是否足够大(比如 64000)。另外,ES 对各种文件混合使用了 NioFs(非阻塞文件系统)和 MMapFs (内存映射文件系统)。请确认系统配置的最大映射数量,以便有足够的虚拟内存可用于 mmapped 文件。可以在 /etc/sysctl.conf 通过修改 vm.max_map_count 永久设置它。

vm.max_map_count=262144

3.4.2 滚动重启

我们平常给应用上线,是一个个节点部署,做的好一点的公司甚至是逐渐放量到新节点,稳定后再上另一个节点的。ES 如果要重启,也应该是如此。操作流程如下:

  • 可能的话,停止索引新的数据。虽然不是每次都能真的做到,但是这一步可以帮助提高恢复速度。
  • 禁止分片分配。这一步阻止 ES 再平衡缺失的分片,直到我们处理好。禁止分配如下:
PUT /_cluster/settings
{
"transient" : {
"cluster.routing.allocation.enable" : "none"
}
}
  • 关闭单个节点
  • 执行维护/升级
  • 重启节点,然后确认它加入到集群了
  • 用如下命令重启分片分配:
PUT /_cluster/settings
{
"transient" : {
"cluster.routing.allocation.enable" : "all"
}
}

分片再平衡会花一些时间。一直等到集群变成绿色状态后再继续

  • 重重复第 2 到 6 步操作剩余节点
  • 到这步可以安全的恢复索引了(如果之前停止了的话),不过等待集群完全均衡后再恢复索引,也会有助于提高处理速度。

3.4.3 备份集群

不管是备份容灾,还是迁移集群,都是需要备份的。

要备份集群,可以使用​snapshot API​。这个会拿到集群里当前的状态和数据,然后保存到一个共享仓库里。这个备份过程是"智能"的。第一个快照会是一个数据的完整拷贝,但是所有后续的快照会保留的是已存快照和新数据之间的差异。随着不时的对数据进行快照,备份也在增量的添加和删除。这意味着后续备份会相当快速,因为它们只传输很小的数据量。

1.要使用这个功能,必须首先创建一个保存数据的仓库。有多个仓库类型可以选择:

– 共享文件系统,比如 NAS

– Amazon S3

– HDFS (Hadoop 分布式文件系统)

– Azure Cloud

2.选好仓库后,就要部署一个共享文件系统仓库,如下:

PUT _snapshot/my_backup 
{
"type": "fs",
"settings": {
"location": "/mount/backups/my_backup"
}
}

注意:共享文件系统路径必须确保集群所有节点都可以访问到。

3.快照所有打开的索引 PUT _snapshot/my_backup/snapshot_1

这个调用会立刻返回,然后快照会在后台运行。

– 如果希望等待快照完成才有返回:PUT _snapshot/my_backup/snapshot_1?wait_for_completion=true

– 快照指定索引,如下:

PUT _snapshot/my_backup/snapshot_2
{
"indices": "index_1,index_2"
}

– 删除快照:DELETE _snapshot/my_backup/snapshot_2这个要慎用。

– 监控快照进度:GET _snapshot/my_backup/snapshot_2/_status

– 取消一个快照,用于删除一个进行中的快照:DELETE _snapshot/my_backup/snapshot_2

这个会中断快照进程,然后删除仓库里进行到一半的快照。

3.4.4 从快照恢复

官网介绍:restore snapshot api

备份好数据后,就可以通过备份的快照快速恢复数据了。如下:

POST _snapshot/my_backup/snapshot_1/_restore

4 总结

纸上得来终觉浅,绝知此事要躬行。希望你能在工作中,根据公司实际情况按照学到的案例实际应用就最好了。

作者介绍

蔡柱梁,51CTO社区编辑,从事Java后端开发8年,做过传统项目广电BOSS系统,后投身互联网电商,负责过订单,TMS,中间件等。

责任编辑:华轩 来源: 51CTO
相关推荐

2020-08-03 10:00:11

前端登录服务器

2019-08-13 15:36:57

限流算法令牌桶

2022-08-01 11:33:09

用户分析标签策略

2021-04-08 07:37:39

队列数据结构算法

2023-09-11 08:13:03

分布式跟踪工具

2020-02-18 16:20:03

Redis ANSI C语言日志型

2023-02-10 09:04:27

2020-05-14 16:35:21

Kubernetes网络策略DNS

2022-06-20 09:01:23

Git插件项目

2017-03-11 22:19:09

深度学习

2022-04-07 10:39:21

反射Java安全

2024-04-10 08:22:44

2020-03-09 17:28:51

NoSQLMongoDB数据库

2023-09-04 08:00:00

开发Java线程

2021-03-03 14:55:10

开发MySQL代码

2023-11-18 09:30:42

模型AI

2021-05-14 23:31:50

大数据计算机开发

2020-07-03 08:21:57

Java集合框架

2018-05-22 08:24:50

PythonPyMongoMongoDB

2019-05-14 09:31:16

架构整洁软件编程范式
点赞
收藏

51CTO技术栈公众号