Elasticsearch——深入原理

在正式介绍Elasticsearch的具体功能以前,将介绍Elasticsearch中比较重要的原理与机制。这有助于理解Elasticsearch的内部机制,以及从表面功能深入了解其背后的逻辑本质。主要内容如下:

  • 搜索引擎的基本原理和组成结构。
  • Elasticsearch集群的形成机制,如节点之间的发现等,以及集群的状态信息在节点之间的同步。
  • 索引的分片在集群中的分配(shard allocation)机制,如何人工干预分配的过程。
  • 索引分片的恢复(shard recovery)触发时间、恢复的过程,以及避免不必要的分片恢复的办法。
  • 写入索引数据的过程。
  • 搜索索引数据的过程。

1、搜索引擎的基本原理

搜索引擎的使用在我们的日常生活中应该已经司空见惯,通常搜索引擎包括数据采集模块、文本分析模块、索引存储模块、搜索模块等,这些模块的协作流程如下图所示:

数据采集模块负责采集需要搜索的数据源,很多网络爬虫可以快速地从各种网站上采集结构化的数据。对于Elasticsearch而言,数据采集模块既可以是官方指定的Beats工具,也可以是第三方提供的ETL工具,还可以是使用Java客户端写入的数据。总之,这个模块的目的就是把用户需要搜索的数据采集起来以备写入搜索引擎。为了提高采集数据的效率,可以使用多线程和分布式等手段,再配合一些比较好的采集策略(例如时间戳增量采集)来满足实际项目的需要。

文本分析模块可以把结构化数据中的长文本切分成有实际意义的词,这样当用户把这些切分出来的词用作查询条件时就可搜索到该文本。如果不做文本分析,就要把原始文本全部写入索引才能搜索到需要的文本,这在某些时候可能是必要的,但是对于长篇幅的文本,直接写入原始的文本数据可能没有太大的意义。在创建索引时,你可以根据实际业务的情况来决定是否在文本数据写入索引以前进行分词。

索引存储模块负责将数据采集模块写入的数据按照定义好的结构写入索引。搜索引擎的索引数据是按照倒排索引的结构进行组织的。倒排索引是一种特殊的数据结构,它保存着每个词在索引中的文档编号以及它们出现在文档的位置。为了直观地表达这个过程,假设现在有3个文档待写入索引,文档内容如下表所示:

简单起见,假如此时分词器以空格进行分词,把上表的3个文档切分成一个个单词,并统计每个单词在文档中出现的位置,就能得到一个简单的倒排索引结构,该结构如下表所示:

Elasticsearch的索引存储模块除了能把写入的数据组织成倒排索引的结构,还管理着数据的存储方式、路由方式、事务日志、索引状态等。

搜索模块的功能是根据用户输入的查询文本找到索引中匹配的文档,相关度越高的文档排名越靠前。有了倒排索引的结构,搜索会变得很方便。假如用户在搜索时输入了"black phone"这样的文本,文本分析模块会把这个搜索文本拆分成分词"black"和"phone",然后去倒排索引中寻找包含这两个词的文档。此时很容易查出文档2和文档3都含有这两个文本。在返回最终的搜索结果时,搜索引擎会使用一套相关度计算公式来计算每个搜索结果与搜索文本的相关度分值,然后把搜索结果列表按照相关度分值由高到低进行返回。在这个例子中,由于文档2同时出现了这两个搜索词,所以在搜索结果的列表中它应当在文档3前面返回。

2、Elasticsearch集群的形成机制

Elasticsearch在生产环境中都是以集群的方式使用的,了解各节点如何互相发现并形成有效的集群是开发者用好Elasticsearch需学习的重要内容,本节就来探讨集群的形成过程。

在了解集群的形成过程以前,需要介绍几个重要的概念,它们在集群的形成过程中发挥着重要的作用:

  • 主节点(master node):每个集群有且只有一个主节点,主节点是整个集群的管理者,它负责维护整个集群的元数据,并在节点数目发生变化时及时更新集群的状态然后将状态发布给集群的其他节点。主节点还负责索引分片的分配。另外,主节点还能处理一些轻量级的请求,例如创建和删除索引。
  • 主候选节点(master-eligible node):主候选节点指的是集群中有权参与主节点选举的那些节点。选举时,超过一半的主候选节点达成一致方能成功。选举出的主节点是主候选节点列表中的一个。为了维持集群的正常运转,任何时候必须确保有一半以上的主候选节点在正常工作。
  • 投票配置(voting configuration):投票配置包含主节点选举或集群状态需要修改时可参与投票的节点列表。在一般情况下,投票配置的列表与主候选节点的列表是一致的。选举主节点时,只有某个节点的得票数超过投票配置中节点数的一半,选举才能成功。你可以人工排除一些主候选节点,即不让它们参与投票。投票配置的信息保存在集群的元数据中,可以调用REST端点进行查看。

2.1、集群节点的发现、选举和引导过程

当一组崭新的Elasticsearch节点启动的时候,需要进行集群节点的引导来把这些孤立的节点组织成一个整体对外提供统一的服务。集群节点的引导主要分为4个步骤:

  • 初始化投票配置。确定集群中的主候选节点列表,将配置cluster.initial_master_nodes中的节点列表作为主候选节点列表写入投票配置。
  • 选举主节点。投票配置中的主候选节点发起主节点的选举,只要超过一半的主候选节点达成一致,则主节点选举成功。
  • 发现集群的其他节点。每个节点根据配置的主候选节点列表discovery.seed_hosts逐一尝试连接,如果联系到了主节点就申请加入集群,主节点确定连接成功就把该节点加入集群并修改集群的状态,然后将集群的最新状态发布到其他节点中保存。
  • 当所有的节点都发现完毕,整个集群的状态生成结束时,待集群启动完毕就可以对外提供统一服务。

注意:在步骤(3)中,节点的发现过程是递归的。即使某个节点的discovery.seed_hosts不包含集群的主节点,只要它可以通过列表中的节点间接地找到主节点,就能被主节点纳入集群。

2.2、集群状态的发布过程

上节讲述了一个崭新的集群形成所经历的步骤,然而对于现有的集群,添加和删除节点会改变集群的状态。集群的状态是一种庞大的数据结构,它包含整个集群的元数据信息。当集群状态发生变化时,主节点需要把最新的状态发布给每个节点,这个过程需要经历以下两个阶段:

(1)预提交阶段:如下图所示,主节点把最新的集群状态数据发布到每个节点上,每个节点接收状态数据并将其保存在本地然后向主节点发送确认响应。

(2)正式提交阶段:如下图所示,主节点统计收到的主候选节点的确认响应数量,如果超过一半的主候选节点的确认响应成功,则开始正式提交。主节点发布提交消息到集群的每个节点,通知它们应用最新的集群状态。每个节点应用最新的集群状态后,向主节点发送最终的确认响应。一旦所有的确认响应成功,则本次状态发布成功完成。

集群状态的发布具有时间限制,如果超过时间限制(默认为30秒)主节点状态没有发布成功则主节点失败,主候选节点需要重新选举新的主节点。如果某个节点最终的确认响应超过一定时间限制(默认为90秒)没有发送到主节点,则主节点认为该节点掉线,将其从集群列表中删除。

集群在运行时可能会由于网络环境或硬件问题导致某些节点不能正常工作,为了维持集群的正常运转,主节点和非主节点每隔一段时间就会互相发送心跳检测包。如果某个非主节点连续多次心跳检测失败,则该节点掉线,主节点会把它从集群的状态中删除;如果主节点掉线,则需要使用投票配置重新选举出新的主节点来管理整个集群。

3、 索引分片的分配机制

上节中讲过,完成索引分片的分配是主节点的一项重要任务。本节将探讨Elasticsearch如何使索引的分片在集群中分配,以及如何使用分片分配的感知和分片分配的过滤来人工干预这个过程以减少数据丢失的可能性。

3.1、 分片的分配

一个索引可以有多个分片,那么这些分片到底应该被放在集群中的哪个节点上呢?集群的主节点将索引的各个分片分配到集群的各个节点上的过程就叫分片的分配。当集群的节点数目发生变化、索引的副本分片数目发生变化时,都会触发分片的分配。在这个过程中,需要把节点现有的分片移动到其他的节点上。如果在同一时刻需要移动的分片数太多且分片容量较大,则这个过程会比较耗时。

主节点为每个节点分配索引分片的时候,默认情况下,它会尽可能把同一个索引的分片分配到更多的节点上,这样在读写索引数据的时候就可以利用更多的硬件资源,可提升读写的效率。在分配分片的过程中,永远不可以把同一个索引的某个主分片和它的副本分片分配到同一个节点上,也不可以把某个主分片的多个副本分片分配到同一个节点上。否则,一旦该节点"挂掉"整个集群就可能会丢失数据。因此,如果一个索引的副本分片数较多而集群节点数较少,则可能会导致某些副本分片没有得到分配而不起作用。

3.2、分片分配的过程

为了直观地演示这个过程,本小节将在本地启动多个Elasticsearch实例搭建伪集群,来方便大家观察索引分片在不同节点数目情况下的分配情况。

首先,你需要启动安装好的单个Elasticsearch节点,创建一个带有3个主分片、每个主分片有1个副本分片的索引allocation-test:

bash 复制代码
PUT allocation-test
{
  "settings": {
    "number_of_shards": "3",
    "number_of_replicas": "1"
  }
}

创建成功后,可以使用下面的请求查看索引allocation-test的各个分片的分配状态:

bash 复制代码
GET _cat/shards/allocation-test?v

在返回的结果中,可以看到3个主分片都被成功分配在当前节点node-1上,由于此时已经没有别的节点来分配副本分片,因此3个副本分片都没有得到分配。

bash 复制代码
index           shard prirep   state  docs store    ip        node
allocation-test 1     p      STARTED       0  208b 127.0.0.1 node-1
allocation-test 1     r      UNASSIGNED
allocation-test 2     p      STARTED       0  208b 127.0.0.1 node-1
allocation-test 2     r      UNASSIGNED
allocation-test 0     p      STARTED       0  208b 127.0.0.1 node-1
allocation-test 0     r      UNASSIGNED                      

此时该索引各个分片的分配结果如下图所示:

Elasticsearch提供了一个好用的REST端点用来查看为什么某个分片没有得到分配或者为什么某个分片分配在了某个节点上而没有分配到其他节点上。如果你对分片的分配结果感到疑惑,可以使用这个端点查看原因。例如,想知道R0为何没有得到分配,可以使用以下代码:

bash 复制代码
GET /_cluster/allocation/explain
{
  "index": "allocation-test",
  "shard": 0,
  "primary": false
}

得到的结果如下:

bash 复制代码
{
  "index" : "allocation-test",
  "shard" : 0,
  "primary" : false,
  "current_state" : "unassigned",
  "unassigned_info" : {
    "reason" : "INDEX_CREATED",
    "at" : "2020-12-27T07:45:07.114Z",
    "last_allocation_status" : "no_attempt"
  },
  "can_allocate" : "no",
  "allocate_explanation" : "cannot allocate because allocation is not permitted to any of the nodes",
  "node_allocation_decisions" : [
    {
      "node_id" : "czAR6jr7Tm68tUOxSd7D1A",
      "node_name" : "node-1",
      "transport_address" : "127.0.0.1:9300",
      "node_attributes" : {
        "ml.machine_memory" : "16964157440",
        "xpack.installed" : "true",
        "transform.node" : "true",
        "ml.max_open_jobs" : "20"
      },
      "node_decision" : "no",
      "weight_ranking" : 1,
      "deciders" : [
        {
          "decider" : "same_shard",
          "decision" : "NO",
          "explanation" : "a copy of this shard is already allocated to this node [[allocation-test][0], node[czAR6jr7Tm68tUOxSd7D1A], [P], s[STARTED], a[id=GFyKAxiKRBOCfliYtlqnew]]"
        }
      ]
    }
  ]
}

上述结果表明,R0的一个副本已经在节点node-1上得到了分配,又没有额外的节点用于分片分配,所以R0不能被分配。

下面继续观察新增节点对分片分配的影响。打开cmd,进入Elasticsearch的bin目录,运行如下代码,在本地再启动一个Elasticsearch的实例node-2:

bash 复制代码
.\elasticsearch.bat -Epath.data=data2 -Epath.logs=log2 -Enode.name=node-2

成功启动node-2后,再来看一看索引allocation-test的各个分片的分配状态。

bash 复制代码
index      shard   prirep    state    docs store    ip        node
allocation-test 1     r      STARTED    0  208b  127.0.0.1   node-2
allocation-test 1     p      STARTED    0  208b  127.0.0.1   node-1
allocation-test 2     r      STARTED    0  208b  127.0.0.1   node-2
allocation-test 2     p      STARTED    0  208b  127.0.0.1   node-1
allocation-test 0     r      STARTED    0  208b  127.0.0.1   node-2
allocation-test 0     p      STARTED    0  208b  127.0.0.1   node-1

可以发现,此时3个主分片都在node-1上,3个副本分片已经被分配到了node-2上,此时该索引各个分片的分配结果如下图所示:

下面打开cmd并切换到bin目录,运行如下代码,再启动一个Elasticsearch的实例node-3。

bash 复制代码
.\elasticsearch.bat -Epath.data=data3 -Epath.logs=log3 -Enode.name=node-3

然后查到6个分片的分配状况,具体如下:

bash 复制代码
index           shard prirep state    docs store   ip       node
allocation-test 1     p      STARTED    0  208b 127.0.0.1  node-3
allocation-test 1     r      STARTED    0  208b 127.0.0.1  node-2
allocation-test 2     r      STARTED    0  208b 127.0.0.1  node-3
allocation-test 2     p      STARTED    0  208b 127.0.0.1  node-1
allocation-test 0     p      STARTED    0  208b 127.0.0.1  node-1
allocation-test 0     r      STARTED    0  208b 127.0.0.1  node-2

可以发现,此时每个节点上都分配到了两个分片,这样既能保证在读写索引数据时每个节点承担的负载比较均匀,又能把集群中的每个节点的硬件资源都利用起来以提升整体的性能。此时索引分片的分配结果如下图所示:

此时Elasticsearch已经把6个分片均匀地分配到了3个节点上,如果你继续向集群添加新的节点,那么Elasticsearch依然会分配一些分片到新的节点上,直到你添加到第7个节点时,再也没有多余的分片可以分配到新的节点上。由此可以得出,对于一个总分片数为N的索引而言,当集群节点的数目达到N以后,继续添加新节点无法再提升该索引的读写性能,这一点在实际应用时需要注意。

3.3、分片分配的感知

我们已经了解了Elasticsearch在默认情况下是如何在集群中分配分片的,这种方式也存在一些不足。例如在上图中,假如node-1和node-2是同一个服务器的两个虚拟机,node-3是另一个服务器的虚拟机。如果某个时刻node-1和node-2所在的那台物理机宕机或停电,而分片P0和它的副本分片R0都在这台物理机上,则会直接导致索引丢失分片不能使用。为了解决这一问题,可以使用分片分配的感知,对副本分片分配的位置进行人为干预。分片分配的感知允许把Elasticsearch的节点划分为属于不同的区域,当分配一个副本分片时,不允许将它分配到它的主分片所在的区域。这样做的好处是,即使某个区域的节点全部"挂掉",其他区域依然有相应的副本分片,不影响集群的使用。

为了演示分片分配的感知,先把上节中介绍的3个节点关掉,然后重新传入分片分配感知的参数来启动这3个节点:

bash 复制代码
.\elasticsearch.bat -Epath.data=data -Epath.logs=logs -Enode.name=node-1
-Enode.attr.zone=zone1
.\elasticsearch.bat -Epath.data=data2 -Epath.logs=log2 -Enode.name=node-2
-Enode.attr.zone=zone1
.\elasticsearch.bat -Epath.data=data3 -Epath.logs=log3 -Enode.name=node-3
-Enode.attr.zone=zone2

在启动这3个节点时,传入了node.attr.zone这个参数,它代表对应节点所属的区域。以上3条命令把node-1和node-2划分到了zone1,把node-3划分到了zone2。集群启动后,需要为集群添加分片分配感知的配置:

bash 复制代码
PUT /_cluster/settings
{
  "transient": {
    "cluster.routing.allocation.awareness.attributes": "zone",
    "cluster.routing.allocation.awareness.force.zone.values": "zone1,zone2"
  }
}

因为在启动命令中使用了node.attr.zone,所以集群的感知属性awareness.attributes要配置为zone,而awareness.force.zone.values则代表每个分区的具体名称。

配置好上述内容后,就可以体验分片分配感知的效果了。新建一个索引,它也包含3个主分片,每个主分片有1个副本分片。

bash 复制代码
PUT allocation-awareness-test
{
  "settings": {
    "number_of_shards": "3",
    "number_of_replicas": "1"
  }
}

通过下述代码查看分片的分配结果:

bash 复制代码
GET _cat/shards/allocation-awareness-test?v

可以得到如下结果:

bash 复制代码
index                shard prirep  state docs store   ip      node
allocation-awareness-test 1   p    STARTED   0  208b 127.0.0.1 node-3
allocation-awareness-test 1   r    STARTED   0  208b 127.0.0.1 node-1
allocation-awareness-test 2   r    STARTED   0  208b 127.0.0.1 node-3
allocation-awareness-test 2   p    STARTED   0  208b 127.0.0.1 node-2
allocation-awareness-test 0   r    STARTED   0  208b 127.0.0.1 node-3
allocation-awareness-test 0   p    STARTED   0  208b 127.0.0.1 node-1

此时,使用分片分配感知的结果如下图所示。在每个区域中,同一分片只能有一个主分片或副本分片,即使zone1的两个节点全部"挂掉",也不影响集群的使用,从下图可以明显看出其与上图分配结果的差异:

注意:由于在本地搭建伪集群改变了集群的状态,如果你想回到单节点模式,请把安装目录data文件夹中产生的数据都清空,data2、data3、logs2、logs3这几个目录都可以删除,否则单个节点可能无法正常启动。

3.4、分片分配的过滤

当你想从集群中删除一个节点,在关闭该节点前,你想把它拥有的分片全部迁移到其他的节点上,这时候就可以使用分片分配的过滤来完成。分片分配的过滤允许配置分片只能或不能被分配到某些节点上,这些配置对整个集群有效。

在配置分片分配的过滤之前,需要给每个节点定义一些属性,或者使用节点自带的属性,以便在设置过滤条件的时候区分它们。例如,你在某个节点的elasticsearch.yml中指定了以下内容:

bash 复制代码
node.attr.state: abandon

上述配置表示该节点有一个自定义属性state,内容为abandon。为了不让任何分片分配到该节点上,你需要调用接口配置分配分片时过滤掉这个节点:

bash 复制代码
PUT _cluster/settings
{
  "transient" : {
    "cluster.routing.allocation.exclude.state" : "abandon"
  }
}

还可以使用节点自带的属性,常用的节点自带属性如下表所示:

exclude可以用于分配分片时过滤掉某些节点,include和require可以用于配置分配分片时需要包含的节点:

bash 复制代码
PUT _cluster/settings
{
  "transient" : {
    "cluster.routing.allocation.exclude.state" : "abandon",
    "cluster.routing.allocation.include._name" : "node-1,node-2",
    "cluster.routing.allocation.require.state" : "good,fine"
  }
}

上述配置的意思是,在集群中分配分片时,要过滤掉state属性为abandon的节点,允许将分片分配到节点名称为node-1或node-2的节点,而且节点的state属性必须同时包含good和fine。

4、索引分片的恢复机制

上节探讨了分片的分配过程,这个过程往往伴随有分片的恢复,Elasticsearch正是使用分片的恢复实现在集群节点数发生变化时也能保证索引分片的容错性和完整性的,本节就来探讨索引分片的恢复机制。

4.1、分片的恢复

索引分片的恢复指的是在某些条件下,由于索引的分片缺失,Elasticsearch把某个索引的分片数据复制一份来得到该分片副本的过程。这个过程会在以下3种场景下触发:

  • 分片的分配:当集群节点数增加,需要把一个主分片的副本分配到新节点上,或集群节点数减少,或某个节点配置了分片的过滤,不得不将该节点的分片复制到其他节点上时,就需要进行分片的分配。总之,在分片分配过程中,一旦发现索引的某个分片缺失,就会使用分片的恢复机制来创建相应的分片。
  • 增加了索引的副本数:在节点充足的情况下,增加了索引的副本,必然会导致在某些节点上分配这些新的副本分片,这个过程就会使用分片的恢复。
  • 从索引备份的快照恢复数据:使用索引备份的快照进行数据恢复时,也会使用分片的恢复。

分片恢复可以分为3种不同的类型:

  • 如果分片是从本地的分片文件复制过来的,则这种分片恢复称为本地存储恢复(local store recovery)。
  • 如果分片是从集群中的其他节点的分片复制过来的,则这种分片恢复称为对等恢复(peer recovery)。
  • 如果分片是从索引备份的快照文件恢复的,则这种分片恢复称为快照恢复(snapshot recovery)。

4.2、分片恢复的过程

为了直观地演示分片的恢复过程,本小节使用一个具有3个节点的Elasticsearch集群,每个节点的IP地址和名称如下表所示:

先在集群上创建一个索引recovery-test,它包含3个主分片,每个主分片有1个副本分片:

bash 复制代码
PUT recovery-test
{
  "settings": {
    "number_of_shards": "3",
    "number_of_replicas": "1"
  }
}

根据分片的分配机制,集群会在每个节点上放置2个分片,分配结果如下图所示:

实际上,在分配分片的过程中已经发生了分片的恢复,你可以调用REST服务来查看分片恢复的情况:

bash 复制代码
GET /_cat/recovery/recovery-test?v&h=i,s,t,ty,st,snode,tnode,f,fp,b,bp

为了完成分片的分配,一共发生了6次分片的恢复,得到的内容如下:

bash 复制代码
i             s t     ty          st   snode  tnode  f fp     b   bp
recovery-test 0 92ms  empty_store done n/a    node-3 0 0.0%   0   0.0%
recovery-test 0 176ms peer        done node-3 node-1 1 100.0% 208 100.0%
recovery-test 1 49ms  empty_store done n/a    node-1 0 0.0%   0   0.0%
recovery-test 1 553ms peer        done node-1 node-2 1 100.0% 208 100.0%
recovery-test 2 138ms peer        done node-2 node-3 1 100.0% 208 100.0%
recovery-test 2 35ms  empty_store done n/a    node-2 0 0.0%   0   0.0%

可以看到,创建新索引时,需要在3个节点上各创建1个主分片,上述代码的第1、3、6条记录即恢复记录,由于这3个分片是从本地创建的空分片,所以ty列显示恢复类型为empty_store。然后需要把这3个主分片通过网络复制到其他节点上产生副本分片,所以又产生了3次对等恢复,类型为peer。这3次对等恢复的过程说明如下:

第2条记录,把node-3的分片0复制到node-1上形成R0,类型为peer。

第4条记录,把node-1的分片1复制到node-2上形成R1,类型为peer。

第5条记录,把node-2的分片2复制到node-3上形成R2,类型为peer。

现在来观察节点"下线"以后分片的恢复过程。关闭node-3,此时索引分片的恢复会分为两个阶段来完成。

(1)恢复主分片,保证索引中的每个主分片都可用。

bash 复制代码
GET /_cat/recovery/recovery-test?v&h=i,s,t,ty,st,snode,tnode,f,fp,b,bp
i             s t     ty          st   snode  tnode  f fp     b   bp
recovery-test 0 176ms peer        done node-3 node-1 1 100.0% 208 100.0%
recovery-test 1 49ms  empty_store done n/a    node-1 0 0.0%   0   0.0%
recovery-test 1 553ms peer        done node-1 node-2 1 100.0% 208 100.0%
recovery-test 2 35ms  empty_store done n/a    node-2 0 0.0%   0   0.0%

上述记录中的第1条记录表示把node-3的P0分片复制到node-1上,保证P0不丢失,类型为peer。后面的3条记录是创建索引的时候已经有的分片恢复记录,不是新增的分片恢复,此时索引的分片状态如下图所示:

(2)恢复缺失的副本分片。此时缺少2个副本分片R0和R2,需要通过2次分片恢复来得到它们。等待几分钟再次查看分片的恢复记录,显示如下:

bash 复制代码
GET /_cat/recovery/recovery-test?v&h=i,s,t,ty,st,snode,tnode,f,fp,b,bp
i             s t     ty          st   snode  tnode  f fp     b   bp
recovery-test 0 176ms peer        done node-3 node-1 1 100.0% 208 100.0%
recovery-test 0 649ms peer        done node-1 node-2 1 100.0% 208 100.0%
recovery-test 1 49ms  empty_store done n/a    node-1 0 0.0%   0   0.0%
recovery-test 1 553ms peer        done node-1 node-2 1 100.0% 208 100.0%
recovery-test 2 241ms peer        done node-2 node-1 1 100.0% 208 100.0%
recovery-test 2 35ms  empty_store done n/a    node-2 0 0.0%   0   0.0%

可以发现,此时确实新增了2条恢复记录,也就是第2条记录和第5条记录。

第2条记录,把node-1上的分片0复制到node-2上形成R0,类型为peer。

第5条记录,把node-2上的分片2复制到node-1上形成R2,类型为peer。

此时索引分片的分配结果如下图所示:

可以看出,node-3下线使得集群中一共经历了2个阶段,通过一次主分片的恢复和两次副本分片恢复,先后恢复了主分片P0和副本分片R0、R2,才使得索引的分配状态重新恢复正常。

现在来看看node-3回到集群时分片的恢复过程,再次启动node-3。

查看当前分片恢复的结果:

bash 复制代码
GET /_cat/recovery/recovery-test?v&h=i,s,t,ty,st,snode,tnode,f,fp,b,bp
i             s t     ty   st   snode  tnode  f fp     b   bp
recovery-test 0 176ms peer done node-3 node-1 1 100.0% 208 100.0%
recovery-test 0 649ms peer done node-1 node-2 1 100.0% 208 100.0%
recovery-test 1 618ms peer done node-1 node-3 1 100.0% 208 100.0%
recovery-test 1 553ms peer done node-1 node-2 1 100.0% 208 100.0%
recovery-test 2 172ms peer done node-2 node-3 1 100.0% 208 100.0%
recovery-test 2 241ms peer done node-2 node-1 1 100.0% 208 100.0%

通过对比可以发现,此时又新增了2次分片恢复:

第3条记录,将分片1从node-1转移到node-3,形成P1。

第5条记录,将分片2从node-2转移到node-3,形成P2。

此时,你可以调用_cat/shards端点来查看每个分片所在的节点,索引分片的分配结果如下图所示:

可见,在node-3回到集群后,又使用了2次分片恢复把P1和P2两个主分片转移到node-3上,保证了分片在集群中均匀分配。从node-3下线到上线,一共新增了5次分片恢复。

4.3、减少不必要的分片恢复

前面介绍了有3个节点时,节点node-3下线又上线引起的分片恢复的过程。在生产环境中,节点多、索引多、分片多,而且一个分片的容量通常能达到十几到二十几字节,如果某个节点临时断网或者重启集群时未及时启动所有节点导致某些节点下线又上线,带来的结果是集群新增许多不必要的分片恢复。这个过程既浪费时间也占用网络,所以应该尽量避免这种不必要的分片恢复,本小节就来探讨其解决方法。

1.延迟分片的恢复

Elasticsearch提供了一个索引级别的动态配置,它可以控制节点下线后多久开始分片的恢复,默认是1min,你可以把它改为5min:

bash 复制代码
PUT _all/_settings
{
  "settings": {
    "index.unassigned.node_left.delayed_timeout": "5m"
  }
}

一旦上述配置生效,node-3下线后,第1个阶段主分片的恢复不受影响,第2个阶段的副本分片恢复在5min之后才会开始,如果5min之内node-3返回集群,则第2个阶段的恢复直接在node-3上进行。如果超过5min node-3还未返回集群,则继续进行第2个阶段的分片恢复。上述配置的优点是,只要node-3及时返回集群,就能够减少第2个阶段的副本分片恢复。

2.改变网关中触发分片恢复的条件

你可以在Elasticsearch的elasticsearch.yml文件中添加与网关相关的配置来改变分片恢复的触发条件,例如:

bash 复制代码
gateway.expected_data_nodes: 5
gateway.recover_after_time: 5m
gateway.recover_after_data_nodes: 3

上述配置的意思是,集群启动时,先等待出现5个数据节点才开始分片的恢复,如果过了5min还没等到集群达到5个数据节点,只要数据节点达到3个就触发分片的恢复。将expected_data_nodes设置为集群数据节点的总数,可以有效减少集群重启时不必要的分片恢复。

5、索引数据的写入过程

本节来探讨索引数据写入Elasticsearch集群时到底会经历哪些过程。假如包含3个节点的集群中有一个索引,拥有3个主分片,每个主分片有1个副本分片,正如前面所介绍的那样,当一个文档写入请求到来时,Elasticsearch处理的过程如下图所示:

这个过程的实现分为4个步骤:

  • node-1接到一个文档写入的请求。
  • 根据索引数据的路由规则计算当前文档应当被写入哪一主分片,假如此时决定路由到P1,则使用传输模块把当前的写入请求转发到node-3上进行写入。如果是P0或者P2则直接写入node-1的本地分片即可。
  • 主分片P1写入完成后,把请求转发到node-2上,将数据写入副本分片R1,使得P1和它的副本分片数据保持一致;如果主分片写入失败,直接返回False。
  • 向客户端报告写入的结果。

Elasticsearch还支持在一个请求中批量写入多个文档,过程如下图所示:

这个过程的实现也可以分为4个步骤:

  • node-2接到一个批量写入文档的请求。
  • 对写入的文档按照路由规则进行分组,在这个例子中有3个主分片会因此分为3组,每组包含对应的主分片上需要写入的文档列表,并要把请求转发到每个主分片所在的节点上。
  • 在每个主分片上写入对应的文档,每个文档写入时,先写入主分片再写入副本分片,直到3个组的文档全部写入完毕。
  • 汇总每个节点上各个文档写入的结果并进行返回。

从这个过程可以看出,向索引写入数据时,总是先写入主分片再写入副本分片。只有主分片和副本分片都写入成功,整个请求才会成功。由于批量写入文档相比单独写入文档减少了请求次数,所以批量写入能够大幅提高文档写入的效率,推荐在生产环境中使用批量写入。

6、索引数据的搜索过程

索引写入数据时总是先寻找主分片,搜索时则不然,一个搜索请求既可以使用主分片,也可以使用副本分片。当请求选择分片时,会采取轮询的方式进行分片的选择。如果搜索数据时不带路由值,则需要选择包含整个索引数据的分片(每个分片的主分片和副本分片二选一),此时Elasticsearch处理的过程如下图所示:

这个过程的实现分为4个步骤:

  • node-1接到一个搜索请求,该请求不包含路由值,接到请求的节点成为协调节点。
  • 选择搜索要用的分片,假如本次选择P0、P1和P2,由于P1在node-3节点上,需要使用传输模块把搜索请求转发到node-3上。
  • 在选择的每个分片上进行搜索,默认情况下,每个分片最多会搜索出匹配的前10条记录作为局部结果,局部结果会交给协调节点node-1。
  • 协调节点node-1汇总这30条记录,按照搜索请求的参数进行排序,默认返回的是全局结果的前10条数据。

当搜索请求带有路由值时,搜索的过程会变得更简化,Elasticsearch处理的过程如下图所示:

这个过程的实现可以分为3个步骤:

  • node-1接到一个搜索请求,该请求包含路由值,该节点成为协调节点。
  • 根据请求的路由值计算搜索的分片,假如为搜索分片1,则按照轮询的策略从P1和R1中选择一个,假定本次搜索选择P1分片,协调节点node-1将请求转发到node-3。
  • 在P1分片上按照搜索条件完成搜索,取出搜索结果的列表并排序返回,默认会得到符合条件的前10条数据。

从上面的过程可以看出,副本分片也可以分担搜索请求,所以增加索引的副本分片数可以加大搜索的并发量。但是增加副本分片并不能提升搜索的性能,因为搜索用到的分片数并未减少。而使用路由条件搜索减少了搜索请求要用到的分片数,可以明显提升搜索性能。

7、总结

深入地探讨了Elasticsearch中比较重要的几个原理,它们能帮助大家更好地理解Elasticsearch。本章的主要内容总结如下:

  • 搜索引擎一般由数据采集模块、文本分析模块、索引存储模块和搜索模块组成。数据采集模块负责采集数据,文本分析模块负责将原始的文本数据切分成有意义的分词以便于搜索,索引存储模块负责将数据组建成倒排索引以实现高速搜索,搜索模块负责根据用户的查询条件返回最相关的搜索结果。
  • Elasticsearch的节点发现模块可以用于形成集群,每个集群包含唯一的主节点,主节点维护着整个集群的状态,当集群状态改变时需要把最新的状态发布到整个集群中进行同步。
  • 分片的分配指的是Elasticsearch会把索引分片均匀地分配到尽可能多的节点上的过程,它由主节点来完成。集群数改变、索引副本数改变都会触发分片的分配。通过分片的分配可以让索引在读写时利用更多集群节点的资源,提升整体的性能。
  • 分片分配时需要满足的基本条件是,同一分片的主分片和副本分片不能分到同一节点上,同一分片的多个副本分片也不能在同一节点上。
  • 可以使用分片分配的感知和分片分配的过滤干预分片分配的结果,分片分配的感知可以把集群分成不同的区域,一个分片和它的副本分片不能分配到同一区域的节点上。分片分配的过滤允许配置索引分片可以分配到哪些节点及不能分配到哪些节点。
  • 分片的恢复指的是把一个分片复制一份产生新的分片的过程。通常在分片的分配、索引分片的副本数改变、快照恢复时都会伴随有分片的恢复。
  • 虽然分片的恢复能够保持索引在节点数发生变化时依然维持分片的容错性并让分片均匀分布,但是网络临时断开或节点重启时触发的大量分片恢复是应当通过恰当的配置来尽量避免的。
  • 索引数据写入时可以分为单个文档写入和批量文档写入,写入时总是先写入主分片再写入副本分片,副本分片写入完成才进行返回。
  • 索引数据搜索时可以分为带路由值的搜索和不带路由值的搜索。带路由值的搜索可以直接定位到要搜索的分片,跳过无关的分片并获取搜索结果,它可以提升搜索性能。不带路由值的搜索需要搜索每个分片(主分片和副本分片二选一)。副本分片可以用于搜索,因此,增加副本分片可以提升搜索的并发量。
相关推荐
java1234_小锋2 分钟前
对于GC方面,在使用Elasticsearch时要注意什么?
大数据·elasticsearch·jenkins
Elastic 中国社区官方博客2 分钟前
Elasticsearch:Retrievers 介绍
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
xnuscd1 小时前
milvus es
大数据·elasticsearch·milvus
字节跳动数据平台1 小时前
火山引擎VeDI在AI+BI领域的演进与实践
大数据
soso19681 小时前
构建与优化数据仓库-实践指南
大数据·数据仓库·人工智能
九河云3 小时前
华为云国内版与国际版的差异
大数据·服务器·华为云
Yz987610 小时前
Hive的基础函数-日期函数
大数据·数据仓库·hive·hadoop·sql·数据库架构·big data
Mephisto.java12 小时前
【大数据学习 | Spark-Core】详解Spark的Shuffle阶段
大数据·学习·spark
FreeIPCC12 小时前
电话机器人是什么?
大数据·人工智能·语言模型·机器人·开源·信息与通信
Tianyanxiao12 小时前
【探商宝】大数据获客平台在销售型企业中的应用
大数据·经验分享·科技·数据分析