Elasticsearch入门笔记

2023-11-03

ES入门笔记

1、概述

elasticsearch简写es,es是一个高扩展、开源的全文检索和分析引擎,它可以准实时地快速存储、搜索、分析海量的数据。

1.1、什么是全文检索

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。全文搜索搜索引擎数据库中的数据。

1.2、es的应用场景

  1. 一个线上商城系统,用户需要搜索商城上的商品。
    可以用es存储所有的商品信息和库存信息,用户只需要输入”空调”就可以搜索到他需要搜索到的商品。
  2. 一个运行的系统需要收集日志,用这些日志来分析、挖掘从而获取系统业务未来的趋势。
    可以用logstash(elk中的一个产品,elasticsearch/logstash/kibana)收集、转换你的日志,并将他们存储到es中。一旦数据到达es中,就你可以在里面搜索、运行聚合函数等操作来挖掘任何你感兴趣的信息。
  3. 基于大量数据(数百万甚至数十亿的数据)快速调查、分析并且要将分析结果可视化的需求。
    可以用es来存储你的数据,用kibana构建自定义的可视化图形、报表,为业务决策提供科学的数据依据。

直白点讲,es是一个企业级海量数据的搜索引擎,可以理解为是一个企业级的百度搜索,除了搜索之外,es还可以快速的实现聚合运算。

1.3、参考资料

  1. 完整版ES笔记:https://blog.csdn.net/u011863024/article/details/115721328
  2. B站视频:https://www.bilibili.com/video/BV1hh411D7sb
  3. 倒排索引:https://blog.csdn.net/qq_43403025/article/details/114779166

2、核心概念

  1. 准实时
    es是一个准实时的搜索平台,这就意味当你存一条数据进去到可以搜索到中间有一定延迟(一般是一秒左右)。

  2. 集群
    集群是存有数据多个节点的集合。集群用名字作为唯一标志,默认为“elasticsearch”。集群的名字非常重要,因为一个节点只能属于一个集群。

  3. 节点

    1. es集群的一部分,节点是用来存储数据、提供搜索请求计算的单位。
    2. es节点和es集群一样都是用名字来作为唯一标识的,es节点默认的名字是uuid。
    3. es节点在一个网段里面是有自动发现功能的,节点启动前配置好节点要加入的集群名称,es节点就会自动加入集群。
    4. 在一个集群里面,你可以加入任意多你想添加的节点。
  4. index——索引

    1. 索引。索引是一组具有相同特点文档的集合。在es里面,索引是参与搜索、更新、删除的基本单位。
    2. 在一个集群里面,你可以创建任意多的索引。
  5. type——类型

    1. 在一个索引中,你可以定义一种或多种类型。

    2. 一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。

      版本 Type
      5.X 支持多种 type
      6.X 只能有一种 type
      7.X 默认不再支持自定义索引类型(默认类型为: _doc)
  6. document——文档
    文档在es里面是数据存储的最基本单元。

  7. shard——分片
    一个index是可以存储超过一台节点硬件极限的数据。为了能够让更多的数据存储在es集群里面,es能够让index打散成多个分片分布在不同的节点上从而增大index存储数据的量。这个打散的碎片就是shard分片。

    每个index创建的时候都能定义分片的数量,定义好后是不能修改的。

    1. 分片可以水平扩展index存储的数据量
    2. 分片可以让计算请求分为多个并行处理。一个分片会有一个线程处理一份数据,多个分片会有多个线程来处理一个请求。
  8. replication——副本
    分布式系统都会有副本的概念,因为数据分散的分布在不同的机器上,难免会遇到网络、死机、硬盘损坏等造成数据损坏、丢失等严重情况,所以一份数据会有多个副本保证数据的完整安全性。

    这里副本实际上是分片的副本,数据在节点上以分片数据的形式存在,es通过算法使每台机器上的碎片副本保存在其它机器上,保证在down掉更多机器的情况下依然保证数据不会丢失。

    1. 当分片或者几点down掉之后,副本依然能够保证数据的完整性,所以分片的副本绝不会和原始分片分布在一台机器上
    2. 因为搜索可以在所有副本上并行运行,所以副本会扩大你集群搜索的吞吐量,从而加快搜索的速度

    创建index的时候可以定义分片和副本的数量,副本的数量可以动态修改,分片的数量一旦定义就不能修改。
    总而言之,言而总之,副本和分片数量都对index的搜索速度有影响。

    分片越多,搜索的线程越多,占用的资源多,自然快,适用于数据量很大的情况。数据量很小分片很多的时候会造成资源的浪费。

    副本越多,可以搜索的文件越多,会提升搜索的速度,但是副本多也会对写入速度造成影响。

    所以要根据数据实际使用的场景合理设置副本和分片的数量。

3、进阶

3.1、集群

3.1.1、单节点

创建 users 索引,分配3个主分片和一份副本(每个主分片拥有一个副本分片)。

#PUT http://127.0.0.1:1001/users
{
    "settings" : {
        "number_of_shards" : 3,
        "number_of_replicas" : 1
    }
}

集群现在是拥有一个索引的单节点集群。所有 3 个主分片都被分配在 node-1 。
在这里插入图片描述
通过 elasticsearch-head 插件(一个Chrome插件)查看集群情况 。
在这里插入图片描述

  1. 集群健康值:yellow( 3 of 6 ):表示当前集群的全部主分片都正常运行,但是副本分片没有全部处在正常状态。
  2. 3 个主分片正常。
  3. 3 个副本分片都是 Unassigned,它们都没有被分配到任何节点。 在同 一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据。
  4. 当前集群是正常运行的,但存在丢失数据的风险。

3.1.2、双节点——故障转移

当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。当你在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的 cluster.name 配置,它就会自动发现集群并加入到其中。但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播主机列表。之所以配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。

如果启动了第二个节点,集群将会拥有两个节点 : 所有主分片和副本分片都已被分配 。
在这里插入图片描述
通过 elasticsearch-head 插件查看集群情况
在这里插入图片描述

  1. 集群健康值:green( 3 of 6 ):表示所有 6 个分片(包括 3 个主分片和 3 个副本分片)都在正常运行。
  2. 3 个主分片正常。(边框加黑加粗的为主分片)
  3. 第二个节点加入到集群后, 3 个副本分片将会分配到这个节点上——每 个主分片对应一个副本分片。这意味着当集群内任何一个节点出现问题时,我们的数据都完好无损。所有新近被索引的文档都将会保存在主分片上,然后被并行的复制到对应的副本分片上。这就保证了我们既可以从主分片又可以从副本分片上获得文档。

3.1.3、三节点——水平扩容

当启动了第三个节点,我们的集群将会拥有三个节点的集群 : 为了分散负载而对分片进行重新分配 。
在这里插入图片描述
通过 elasticsearch-head 插件查看集群情况。
在这里插入图片描述

  1. 集群健康值:green( 3 of 6 ):表示所有 6 个分片(包括 3 个主分片和 3 个副本分片)都在正常运行。
  2. Node 1 和 Node 2 上各有一个分片被迁移到了新的 Node 3 节点,现在每个节点上都拥有 2 个分片, 而不是之前的 3 个。 这表示每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片 的性能将会得到提升。
  3. 分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。 我们这个拥有 6 个分 片(3 个主分片和 3 个副本分片)的索引可以最大扩容到 6 个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。

如果想要扩容超过 6 个节点怎么办呢?
主分片的数目在索引创建时就已经确定了下来。实际上,这个数目定义了这个索引能够存储 的最大数据量。(实际大小取决于你的数据、硬件和使用场景。) 但是,读操作——搜索和返回数据——可以同时被主分片 或 副本分片所处理,所以当你拥有越多的副本分片时,也将拥有越高的吞吐量。

在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把副本数从默认的 1 增加到 2。

#PUT http://127.0.0.1:1001/users/_settings

{
    "number_of_replicas" : 2
}

users 索引现在拥有 9 个分片: 3 个主分片和 6 个副本分片。 这意味着我们可以将集群
扩容到 9 个节点,每个节点上一个分片。相比原来 3 个节点时,集群搜索性能可以提升 3 倍。
在这里插入图片描述
通过 elasticsearch-head 插件查看集群情况:

在这里插入图片描述
如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每个分片从节点上获得的资源会变少。 你需要增加更多的硬件资源来提升吞吐量。

但是更多的副本分片数提高了数据冗余量:按照上面的节点配置,我们可以在失去 2 个节点的情况下不丢失任何数据。

3.1.4、应对故障

关闭第一个节点,集群的状态为:关闭了一个节点后的集群。
在这里插入图片描述
关闭的节点是一个主节点。而集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点: Node 2 。在我们关闭 Node 1 的同时也失去了主分片 1 和 2 ,并且在缺失主分片的时候索引也不能正常工作。 如果此时来检查集群的状况,我们看到的状态将会为 red :不是所有主分片都在正常工作。

幸运的是,在其它节点上存在着这两个主分片的完整副本, 所以新的主节点立即将这些分片在 Node 2 和 Node 3 上对应的副本分片提升为主分片, 此时集群的状态将会为yellow。这个提升主分片的过程是瞬间发生的,如同按下一个开关一般。
在这里插入图片描述
为什么我们集群状态是 yellow 而不是 green 呢?

虽然我们拥有所有的三个主分片,但是同时设置了每个主分片需要对应 2 份副本分片,而此时只存在一份副本分片。 所以集群不能为 green 的状态,不过我们不必过于担心:如果我们同样关闭了 Node 2 ,我们的程序 依然 可以保持在不丢任何数据的情况下运行,因为Node 3 为每一个分片都保留着一份副本。

如果想恢复原来的样子,要确保Node-1的配置文件有如下配置:

discovery.seed_hosts: ["localhost:9302", "localhost:9303"]

集群可以将缺失的副本分片再次进行分配,那么集群的状态也将恢复成之前的状态。 如果 Node 1 依然拥有着之前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件。和之前的集群相比,只是 Master 节点切换了。
在这里插入图片描述
1001 挂了,1002自动变为主节点,可以正常提供服务。1001重新连接,1002依旧是主节点

3.2、路由计算 & 分片控制

  1. 路由计算:文档数据在增加和查询时从哪一个分片进行操作的规则,公式如下:

    shard = hash(routing) % number_of_primary_shards
    

    routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值。 routing 通过hash 函数生成一个数字,然后这个数字再除以
    number_of_primary_shards (主分片的数量)后得到余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻
    求的文档所在分片的位置。

  2. 为什么在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量?
    因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。

  3. 所有的文档API ( get . index . delete 、 bulk , update以及 mget )都接受一个叫做routing 的路由参数,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。

  4. 分片控制:用户可以访问任何一个节点获取数据。这个节点称之为协调节点。
    当发送请求的时候, 为了扩展负载,更好的做法是轮询集群中所有的节点

3.3、数据写、读、更新、批量操作

3.3.1、写流程

新建、索引和删除请求都是写操作, 必须在主分片上面完成之后才能被复制到相关的副本分片。

流程如下:

  1. 客户端请求集群节点(任意)-协调节点
  2. 协调节点将请求转换到指定的节点
  3. 主分片需要将数据保存
  4. 主分片需要将数据发送到副本
  5. 副本保存后,进行反馈
  6. 主分片进行反馈
  7. 客户端获取反馈

在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。有一些可选的请求参数允许您影响这个过程,可能以数据安全为代价提升性能。这些选项很少使用,因为 Elasticsearch 已经很快,但是为了完整起见, 请参考下文:

参数 含义
consistency 即一致性。在默认设置下,即使仅仅是在试图执行一个写操作之前,主分片都会要求必须要有规定数量quorum(或者换种说法,也即必须要有大多数)的分片副本处于活跃可用状态,才会去执行写操作(其中分片副本 可以是主分片或者副本分片)。这是为了避免在发生网络分区故障(network partition)的时候进行写操作,进而导致数据不一致。 规定数量即:
int((primary + number_of_replicas) / 2 ) + 1 。

consistency 参数的值可以设为:
one :只要主分片状态 ok 就允许执行写操作。
all:必须要主分片和所有副本分片的状态没问题才允许执行写操作。
quorum:默认值为quorum , 即大多数的分片副本状态没问题就允许执行写操作。

注意,规定数量的计算公式中number_of_replicas指的是在索引设置中的设定副本分片数,而不是指当前处理活动状态的副本分片数。如果你的索引设置中指定了当前索引拥有3个副本分片,那规定数量的计算结果即:int((1 primary + 3 replicas) / 2) + 1 = 3,如果此时你只启动两个节点,那么处于活跃状态的分片副本数量就达不到规定数量,也因此您将无法索引和删除任何文档。
timeout 如果没有足够的副本分片会发生什么?Elasticsearch 会等待,希望更多的分片出现。默认情况下,它最多等待 1 分钟。 如果你需要,你可以使用timeout参数使它更早终止:100是100 毫秒,30s是30秒。

新索引默认有1个副本分片,这意味着为满足规定数量应该需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当number_of_replicas 大于1的时候,规定数量才会执行。

3.3.2、读流程

  1. 客户端发送请求到协调节点
  2. 协调节点计算数据所在的分片以及全部的副本位置
  3. 为了能够负载均衡,可以轮询所有节点
  4. 将请求转发给具体的节点
  5. 节点返回查询结果,将结果反馈给客户端

3.3.3、更新流程

部分更新一个文档结合了先前说明的读取和写入流程:
在这里插入图片描述
部分更新一个文档的步骤如下:

  1. 客户端向Node 1发送更新请求。
  2. 它将请求转发到主分片所在的Node 3 。
  3. Node 3从主分片检索文档,修改_source字段中的JSON,并且尝试重新索引主分片的文档。如果文档已经被另一个进程修改,它会重试步骤3 ,超过retry_on_conflict次后放弃。
  4. 如果 Node 3成功地更新文档,它将新版本的文档并行转发到Node 1和 Node 2上的副本分片,重新建立索引。一旦所有副本分片都返回成功,Node 3向协调节点也返回成功,协调节点向客户端返回成功。

当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果 Elasticsearch 仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。

3.3.4、批量操作流程

mget和 bulk API的模式类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中。它将整个多文档请求分解成每个分片的多文档请求,并且将这些请求并行转发到每个参与节点。

协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端。
在这里插入图片描述
用单个 mget 请求取回多个文档所需的步骤顺序:

  1. 客户端向 Node 1 发送 mget 请求。
  2. Node 1为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。一旦收到所有答复,Node 1 构建响应并将其返回给客户端。

可以对docs数组中每个文档设置routing参数。


bulk API, 允许在单个批量请求中执行多个创建、索引、删除和更新请求。
在这里插入图片描述
bulk API 按如下步骤顺序执行:

  1. 客户端向Node 1 发送 bulk请求。
  2. Node 1为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机。
  3. 主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端。

3.4、倒排索引

查看资料:https://blog.csdn.net/qq_43403025/article/details/114779166

  1. 正排索引:以文档ID为key,表中记录文档中每个关键字的位置信息,查找时扫描表中每个文档的字信息直到找出所有包含查询关键字的文档。

    存储演示:
    商品1 -> [(关键词1,出现3次,位置为1,3,5), (关键词2,出现2次,位置为2,6), (关键词4,出现1次,位置为10), …]
    商品2 -> [(关键词1,出现1次,位置为1), (关键词3,出现4次,位置为2,4,7,9), …]

  2. 倒排索引:倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项(倒排列表)记录了出现这个字或词的所有文档。

    存储演示:
    关键词1 -> [商品1,商品2]
    关键词2 -> [商品1,商品3]


3.5、文档搜索

3.5.1、不可变的倒排索引

早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。

倒排索引被写入磁盘后是不可改变的:它永远不会修改。

  • 不需要锁。如果你从来不更新索引,你就不需要担心多进程同时修改数据的问题。
  • 一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。
  • 其它缓存(像filter缓存),在索引的生命周期内始终有效。它们不需要在每次数据改变时被重建,因为数据不会变化。
  • 写入单个大的倒排索引允许数据被压缩,减少磁盘IO和需要被缓存到内存的索引的使用量。

当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制。

3.5.2、动态更新索引

如何在保留不变性的前提下实现倒排索引的更新?

答案是:用更多的索引。通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。

Elasticsearch基于Lucene,这个java库引入了按段搜索的概念。每一段本身都是一个倒排索引,但索引在 Lucene 中除表示所有段的集合外,还增加了提交点的概念——一个列出了所有已知段的文件。
在这里插入图片描述
按段搜索会以如下流程执行:

一、新文档被收集到内存索引缓存。
在这里插入图片描述

二、不时地, 缓存被提交。

  1. 一个新的段,一个追加的倒排索引,被写入磁盘。
  2. 一个新的包含新段名字的提交点被写入磁盘。
  3. 磁盘进行同步,所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件

三、新的段被开启,让它包含的文档可见以被搜索。

四、内存缓存被清空,等待接收新的文档。
在这里插入图片描述
当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。这种方式可以用相对较低的成本将新文档添加到索引。

段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档的更新。取而代之的是,每个提交点会包含一个.del 文件,文件中会列出这些被删除文档的段信息。

当一个文档被删除时,它实际上只是在 .del 文件中被标记删除。一个被标记删除的文档仍然可以被查询匹配到,但它会在最终结果被返回前从结果集中移除。

文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版本被索引到一个新的段中。可能两个版本的文档都会被一个查询匹配到,但被删除的那个旧版本文档在结果集返回前就已经被移除。

3.6、文档刷新 & 文档刷写 & 文档合并

3.6.1、近实时搜索

随着按段(per-segment)搜索的发展,一个新的文档从索引到可被搜索的延迟显著降低了。新文档在几分钟之内即可被检索,但这样还是不够快。磁盘在这里成为了瓶颈。提交(Commiting)一个新的段到磁盘需要一个fsync来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据。但是fsync操作代价很大;如果每次索引一个文档都去执行一次的话会造成很大的性能问题。

我们需要的是一个更轻量的方式来使一个文档可被搜索,这意味着fsync要从整个过程中被移除。在Elasticsearch和磁盘之间是文件系统缓存。像之前描述的一样,在内存索引缓冲区中的文档会被写入到一个新的段中。但是这里新段会被先写入到文件系统缓存—这一步代价会比较低,稍后再被刷新到磁盘—这一步代价比较高。不过只要文件已经在缓存中,就可以像其它文件一样被打开和读取了。

在这里插入图片描述

Lucene允许新段被写入和打开,使其包含的文档在未进行一次完整提交时便对搜索可见。这种方式比进行一次提交代价要小得多,并且在不影响性能的前提下可以被频繁地执行。

在这里插入图片描述

在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做refresh。默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch是近实时搜索:文档的变化并不是立即对搜索可见,但会在一秒之内变为可见。

这些行为可能会对新用户造成困惑:他们索引了一个文档然后尝试搜索它,但却没有搜到。这个问题的解决办法是用refresh API执行一次手动刷新:/usersl_refresh

尽管刷新是比提交轻量很多的操作,它还是会有性能开销。当写测试的时候,手动刷新很有用,但是不要在生产环境下每次索引一个文档都去手动刷新。相反,你的应用需要意识到Elasticsearch 的近实时的性质,并接受它的不足。

并不是所有的情况都需要每秒刷新。可能你正在使用Elasticsearch索引大量的日志文件,你可能想优化索引速度而不是近实时搜索,可以通过设置refresh_interval ,降低每个索引的刷新频率

{
    "settings": {
    	"refresh_interval": "30s"
    }
}

refresh_interval可以在既存索引上进行动态更新。在生产环境中,当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来。

# 关闭自动刷新
PUT /users/_settings
{ "refresh_interval": -1 }

# 每一秒刷新
PUT /users/_settings
{ "refresh_interval": "1s" }

3.6.2、持久化变更

如果没有用fsync把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在。为了保证Elasticsearch 的可靠性,需要确保数据变化被持久化到磁盘。在动态更新索引,我们说一次完整的提交会将段刷到磁盘,并写入一个包含所有段列表的提交点。Elasticsearch 在启动或重新打开一个索引的过程中使用这个提交点来判断哪些段隶属于当前分片。

即使通过每秒刷新(refresh)实现了近实时搜索,我们仍然需要经常进行完整提交来确保能从失败中恢复。但在两次提交之间发生变化的文档怎么办?我们也不希望丢失掉这些数据。Elasticsearch 增加了一个translog ,或者叫事务日志,在每一次对Elasticsearch进行操作时均进行了日志记录


整个流程如下:

一、一个文档被索引之后,就会被添加到内存缓冲区,并且追加到了 translog

在这里插入图片描述

二、刷新(refresh)使分片每秒被刷新(refresh)一次:

  • 这些在内存缓冲区的文档被写入到一个新的段中,且没有进行fsync操作。
  • 这个段被打开,使其可被搜索。
  • 内存缓冲区被清空。
    在这里插入图片描述

三、这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志。
在这里插入图片描述

四、每隔一段时间—例如translog变得越来越大,索引被刷新(flush);一个新的translog被创建,并且一个全量提交被执行。

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

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

translog 也被用来提供实时CRUD。当你试着通过ID查询、更新、删除一个文档,它会在尝试从相应的段中检索之前,首先检查 translog任何最近的变更。这意味着它总是能够实时地获取到文档的最新版本。

在这里插入图片描述

执行一个提交并且截断translog 的行为在 Elasticsearch被称作一次flush。分片每30分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新。

你很少需要自己手动执行flush操作,通常情况下,自动刷新就足够了。这就是说,在重启节点或关闭索引之前执行 flush有益于你的索引。当Elasticsearch尝试恢复或重新打开一个索引,它需要重放translog中所有的操作,所以如果日志越短,恢复越快。

translog 的目的是保证操作不会丢失,在文件被fsync到磁盘前,被写入的文件在重启之后就会丢失。默认translog是每5秒被fsync刷新到硬盘,或者在每次写请求完成之后执行(e.g. index, delete, update, bulk)。这个过程在主分片和复制分片都会发生。最终,基本上,这意味着在整个请求被fsync到主分片和复制分片的translog之前,你的客户端不会得到一个200 OK响应。

在每次请求后都执行一个fsync会带来一些性能损失,尽管实践表明这种损失相对较小(特别是 bulk 导入,它在一次请求中平摊了大量文档的开销)。

但是对于一些大容量的偶尔丢失几秒数据问题也并不严重的集群,使用异步的 fsync还是比较有益的。比如,写入的数据被缓存到内存中,再每5秒执行一次 fsync 。如果你决定使用异步translog 的话,你需要保证在发生 crash 时,丢失掉 sync_interval时间段的数据也无所谓。请在决定前知晓这个特性。如果你不确定这个行为的后果,最好是使用默认的参数{“index.translog.durability”: “request”}来避免数据丢失。

3.6.3、段合并

由于自动刷新流程每秒会创建一个新的段,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。每一个段都会消耗文件句柄、内存和 cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。

Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。

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

启动段合并不需要你做任何事。进行索引和搜索时会自动进行。

一、当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用。

二、合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。这并不会中断索引和搜索。

在这里插入图片描述

三、一旦合并结束,老的段被删除

  • 新的段被刷新(flush)到了磁盘。
  • 写入一个包含新段且排除旧的和较小的段的新提交点。
  • 新的段被打开用来搜索。老的段被删除。
    在这里插入图片描述
    合并大的段需要消耗大量的 I/O 和 CPU 资源,如果任其发展会影响搜索性能。 Elasticsearch在默认情况下会对合并流程进行资源限制,所以搜索仍然有足够的资源很好地执行。

3.7、文档控制

3.7.1、文档冲突

当我们使用index API更新文档,可以一次性读取原始文档,做我们的修改,然后重新索引整个文档。最近的索引请求将获胜:无论最后哪一个文档被索引,都将被唯一存储在 Elasticsearch 中。如果其他人同时更改这个文档,他们的更改将丢失。

很多时候这是没有问题的。也许我们的主数据存储是一个关系型数据库,我们只是将数据复制到Elasticsearch中并使其可被搜索。也许两个人同时更改相同的文档的几率很小。或者对于我们的业务来说偶尔丢失更改并不是很严重的问题。

但有时丢失了一个变更就是非常严重的。试想我们使用Elasticsearch 存储我们网上商城商品库存的数量,每次我们卖一个商品的时候,我们在 Elasticsearch 中将库存数量减少。有一天,管理层决定做一次促销。突然地,我们一秒要卖好几个商品。假设有两个web程序并行运行,每一个都同时处理所有商品的销售。
在这里插入图片描述
web_1 对stock_count所做的更改已经丢失,因为 web_2不知道它的 stock_count的拷贝已经过期。结果我们会认为有超过商品的实际数量的库存,因为卖给顾客的库存商品并不存在,我们将让他们非常失望。

变更越频繁,读数据和更新数据的间隙越长,也就越可能丢失变更。在数据库领域中,有两种方法通常被用来确保并发更新时变更不会丢失:

  • 悲观并发控制:这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。一个典型的例子是读取一行数据之前先将其锁住,确保只有放置锁的线程能够对这行数据进行修改。
  • 乐观并发控制:Elasticsearch 中使用的这种方法假定冲突是不可能发生的,并且不会阻塞正在尝试的操作。然而,如果源数据在读写当中被修改,更新将会失败。应用程序接下来将决定该如何解决冲突。例如,可以重试更新、使用新的数据、或者将相关情况报告给用户。

3.7.2、乐观并发控制

Elasticsearch是分布式的。当文档创建、更新或删除时,新版本的文档必须复制到集群的其他节点。Elasticsearch也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许顺序是乱的。Elasticsearch需要一种方法确保文档的旧版本不会覆盖新的版本。

当我们之前讨论index , GET和DELETE请求时,我们指出每个文档都有一个_version(版本号),当文档被修改时版本号递增。Elasticsearch使用这个version号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。

我们可以利用version号来确保应用中相互冲突的变更不会导致数据丢失。我们通过指定想要修改文档的 version号来达到这个目的。如果该版本不是当前版本号,我们的请求将会失败。

老的版本es使用version,但是新版本不支持了,会报下面的错误,提示我们用if_seq _no和if _primary_term


创建索引

#put http://localhost:9200/shopping/_create/1001

返回结果

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 10,
    "_primary_term": 15
}

更新数据

#POST http://127.0.0.1:9200/shopping/_update/1001
{
    "doc":{
        "title":"华为手机"
    }
}

返回结果

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "_version": 2,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 11,
    "_primary_term": 15
}

旧版本使用的防止冲突更新方法:

#POST http://127.0.0.1:9200/shopping/_update/1001?version=1
{
    "doc":{
        "title":"华为手机2"
    }
}

返回结果:

{
    "error": {
        "root_cause": [
            {
                "type": "action_request_validation_exception",
                "reason": "Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"
            }
        ],
        "type": "action_request_validation_exception",
        "reason": "Validation Failed: 1: internal versioning can not be used for optimistic concurrency control. Please use `if_seq_no` and `if_primary_term` instead;"
    },
    "status": 400
}

新版本使用的防止冲突更新方法:

#POST http://127.0.0.1:9200/shopping/_update/1001?if_seq_no=11&if_primary_term=15
{
    "doc":{
        "title":"华为手机2"
    }
}

返回结果:

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "_version": 3,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 12,
    "_primary_term": 16
}

如果报错信息是

"type": "version_conflict_engine_exception",
"reason": "[1001]: version conflict, required seqNo [3], primary term [2]. current document has seqNo [3] and primary term [1]",

只需要将seqNo 和 primary term 改为 reason 的后半段即可 current document has seqNo [3] and primary term [1]

3.7.3、外部系统版本控制

一个常见的设置是使用其它数据库作为主要的数据存储,使用Elasticsearch做数据检索,这意味着主数据库的所有更改发生时都需要被复制到Elasticsearch,如果多个进程负责这一数据同步,你可能遇到类似于之前描述的并发问题。

如果你的主数据库已经有了版本号,或一个能作为版本号的字段值比如timestamp,就可以在 Elasticsearch 中通过增加 version_type=extermal到查询字符串的方式重用这些相同的版本号,版本号必须是大于零的整数,且小于9.2E+18,一个Java中 long类型的正值。

外部版本号的处理方式和我们之前讨论的内部版本号的处理方式有些不同,Elasticsearch不是检查当前_version和请求中指定的版本号是否相同,而是检查当前_version是否小于指定的版本号。如果请求成功,外部的版本号作为文档的新_version进行存储。

#POST http://127.0.0.1:9200/shopping/_doc/1001?version=300&version_type=external
{
	"title":"华为手机2"
}

返回结果

{
    "_index": "shopping",
    "_type": "_doc",
    "_id": "1001",
    "_version": 300,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 13,
    "_primary_term": 16
}

4、Elasticsearch优化

4.1、优化-硬件选择

Elasticsearch 的基础是 Lucene,所有的索引和文档数据是存储在本地的磁盘中,具体的路径可在 ES 的配置文件…/config/elasticsearch.yml中配置,如下:

#
# Path to directory where to store the data (separate multiple locations by comma):
#
path.data: /path/to/data
#
# Path to log files:
#
path.logs: /path/to/logs

磁盘在现代服务器上通常都是瓶颈。Elasticsearch重度使用磁盘,你的磁盘能处理的吞吐量越大,你的节点就越稳定。这里有一些优化磁盘I/O的技巧:

  • 使用SSD(固态硬盘),比机械磁盘好
  • 使用RAID0。条带化RAID会提高磁盘IO,代价显然就是当一块硬盘故障时整个就故障了。不要使用镜像或者奇偶校验RAID,因为副本已经提供了这个功能。
  • 另外,使用多块硬盘,并允许Elasticsearch 通过多个path data目录配置把数据条带化分配到它们上面。
  • 不要使用远程挂载的存储,比如NFS或者SMB/CIFS。这个引入的延迟对性能来说完全是背道而驰的。

4.2、优化-分片策略

  1. 合理设置分片数
    分片和副本的设计为 ES 提供了支持分布式和故障转移的特性,但并不意味着分片和副本是可以无限分配的。而且索引的分片完成分配后由于索引的路由机制,我们是不能重新修改分片数的。

    可能有人会说,我不知道这个索引将来会变得多大,并且过后我也不能更改索引的大小,所以为了保险起见,还是给它设为 1000 个分片吧。但是需要知道的是,一个分片并不是没有代价的。需要了解:

    • 一个分片的底层即为一个 Lucene 索引,会消耗一定文件句柄、内存、以及 CPU运转。
    • 每一个搜索请求都需要命中索引中的每一个分片,如果每一个分片都处于不同的节点还好, 但如果多个分片都需要在同一个节点上竞争使用相同的资源就有些糟糕了。
    • 用于计算相关度的词项统计信息是基于分片的。如果有许多分片,每一个都只有很少的数据会导致很低的相关度。

    一个业务索引具体需要分配多少分片可能需要架构师和技术人员对业务的增长有个预先的判断,横向扩展应当分阶段进行。为下一阶段准备好足够的资源。 只有当你进入到下一个阶段,你才有时间思考需要作出哪些改变来达到这个阶段。一般来说,我们遵循一些原则:

    • 控制每个分片占用的硬盘容量不超过 ES 的最大 JVM 的堆空间设置(一般设置不超过 32G,参考下文的 JVM 设置原则),因此,如果索引的总容量在 500G 左右,那分片大小在 16 个左右即可;当然,最好同时考虑原则 2。
    • 考虑一下 node 数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,即使保持了 1 个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以, 一般都设置分片数不超过节点数的 3 倍。
    • 主分片,副本和节点最大数之间数量,我们分配的时候可以参考以下关系:
      节点数<=主分片数 *(副本数+1)
  2. 推迟分片分配
    对于节点瞬时中断的问题,默认情况,集群会等待一分钟来查看节点是否会重新加入,如果这个节点在此期间重新加入,重新加入的节点会保持其现有的分片数据,不会触发新的分片分配。这样就可以减少 ES 在自动再平衡可用分片时所带来的极大开销。

    通过修改参数 delayed_timeout ,可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改:

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

4.3、优化-路由选择

当我们查询文档的时候, Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?它其实是通过下面这个公式来计算出来:

shard = hash(routing) % number_of_primary_shards

routing 默认值是文档的 id,也可以采用自定义值,比如用户 id。

  1. 不带routing查询
    在查询的时候因为不知道要查询的数据具体在哪个分片上,所以整个过程分为2个步骤
    • 分发:请求到达协调节点后,协调节点将查询请求分发到每个分片上。
    • 聚合:协调节点搜集到每个分片上查询结果,在将查询的结果进行排序,之后给用户返回结果。
  2. 带routing查询
    查询的时候,可以直接根据routing 信息定位到某个分片查询,不需要查询所有的分片,经过协调节点排序。向上面自定义的用户查询,如果routing 设置为userid 的话,就可以直接查询出数据来,效率提升很多。

4.4、优化-写入速度优化

ES 的默认配置,是综合了数据可靠性、写入速度、搜索实时性等因素。实际使用时,我们需要根据公司要求,进行偏向性的优化。

针对于搜索性能要求不高,但写入要求较高的场景,需要尽可能的选择恰当写优化策略。综合来说,考虑以下几个方面来提升写索引的性能:

  • 加大Translog Flush,目的是降低Iops、Writeblock。
  • 增加Index Refesh间隔,目的是减少Segment Merge的次数。
  • 调整Bulk 线程池和队列。
  • 优化节点间的任务分布。
  • 优化Lucene层的索引建立,目的是降低CPU及IO。
    优化存储设备
    ES 是一种密集使用磁盘的应用,在段合并的时候会频繁操作磁盘,所以对磁盘要求较高,当磁盘速度提升之后,集群的整体性能会大幅度提高。
  1. 合理使用合并
    Lucene 以段的形式存储数据。当有新的数据写入索引时, Lucene 就会自动创建一个新的段。

    随着数据量的变化,段的数量会越来越多,消耗的多文件句柄数及 CPU 就越多,查询效率就会下降。

    由于 Lucene 段合并的计算量庞大,会消耗大量的 I/O,所以 ES 默认采用较保守的策略,让后台定期进行段合并。

  2. 减少 Refresh 的次数
    Lucene 在新增数据时,采用了延迟写入的策略,默认情况下索引的refresh_interval 为1 秒。

    Lucene 将待写入的数据先写到内存中,超过 1 秒(默认)时就会触发一次 Refresh,然后 Refresh 会把内存中的的数据刷新到操作系统的文件缓存系统中。

    如果我们对搜索的实效性要求不高,可以将 Refresh 周期延长,例如 30 秒。
    这样还可以有效地减少段刷新次数,但这同时意味着需要消耗更多的 Heap 内存。

  3. 加大 Flush 设置
    Flush 的主要目的是把文件缓存系统中的段持久化到硬盘,当 Translog 的数据量达到 512MB 或者 30 分钟时,会触发一次 Flush。

    index.translog.flush_threshold_size 参数的默认值是 512MB,我们进行修改。

    增加参数值意味着文件缓存系统中可能需要存储更多的数据,所以我们需要为操作系统的文件缓存系统留下足够的空间。

  4. 减少副本的数量
    ES 为了保证集群的可用性,提供了 Replicas(副本)支持,然而每个副本也会执行分析、索引及可能的合并过程,所以 Replicas 的数量会严重影响写索引的效率。

    当写索引时,需要把写入的数据都同步到副本节点,副本节点越多,写索引的效率就越慢。

    如果我们需要大批量进行写入操作,可以先禁止Replica复制,设置
    index.number_of_replicas: 0 关闭副本。在写入完成后, Replica 修改回正常的状态。

4.5、优化-内存设置

ES 默认安装后设置的内存是 1GB,对于任何一个现实业务来说,这个设置都太小了。如果是通过解压安装的 ES,则在 ES 安装文件中包含一个 jvm.option 文件,添加如下命令来设置 ES 的堆大小, Xms 表示堆的初始大小, Xmx 表示可分配的最大内存,都是 1GB。

确保 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 等)之间移动数据的时候,会占用更多的带宽。

最终我们都会采用 31 G 设置

- -Xms 31g
- -Xmx 31g

假设你有个机器有 128 GB 的内存,你可以创建两个节点,每个节点内存分配不超过 32 GB。也就是说不超过 64 GB 内存给 ES 的堆内存,剩下的超过 64 GB 的内存给 Lucene。

4.6、优化-重要配置

参数名 参数值 说明
cluster.name elasticsearch 配置 ES 的集群名称,默认值是 ES,建议改成与所存数据相关的名称, ES 会自动发现在同一网段下的 集群名称相同的节点。
node.name node-1 集群中的节点名,在同一个集群中不能重复。节点 的名称一旦设置,就不能再改变了。当然,也可以 设 置 成 服 务 器 的 主 机 名 称 , 例 如 node.name:${HOSTNAME}。
node.master true 指定该节点是否有资格被选举成为 Master 节点,默 认是 True,如果被设置为 True,则只是有资格成为 Master 节点,具体能否成为 Master 节点,需要通 过选举产生。
node.data true 指定该节点是否存储索引数据,默认为 True。数据 的增、删、改、查都是在 Data 节点完成的。
index.number_of_shards 1 设置都索引分片个数,默认是 1 片。也可以在创建 索引时设置该值,具体设置为多大都值要根据数据 量的大小来定。如果数据量不大,则设置成 1 时效 率最高
index.number_of_replicas 1 设置默认的索引副本个数,默认为 1 个。副本数越 多,集群的可用性越好,但是写索引时需要同步的 数据越多。
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 秒。 在较差的网络环境下需要设置得大一点,防止因误 判该节点的存活状态而导致分片的转移

5、面试题

  1. 为什么要使用 Elasticsearch?

    系统中的数据, 随着业务的发展,时间的推移, 将会非常多, 而业务中往往采用模糊查询进行数据的搜索, 而模糊查询会导致查询引擎放弃索引,导致系统查询数据时都是全表扫描,在百万级别的数据库中,查询效率是非常低下的,而我们使用 ES 做一个全文索引,将经常查询的系统功能的某些字段,比如说电商系统的商品表中商品名,描述、价格还有 id 这些字段我们放入 ES 索引库里,可以提高查询速度。

  2. Elasticsearch 的 master 选举流程?

    • Elasticsearch的选主是ZenDiscovery模块负责的,主要包含Ping(节点之间通过这个RPC来发现彼此)和Unicast(单播模块包含-一个主机列表以控制哪些节点需要ping通)这两部分。
    • 对所有可以成为master的节点(node master: true)根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。
    • 如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。
    • master节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data节点可以关闭http功能。
  3. Elasticsearch 集群脑裂问题?

    假死:由于心跳超时(网络原因导致的)认为master死了,但其实master还存活着。
    脑裂:由于假死会发起新的master选举,选举出一个新的master,但旧的master网络又通了,导致出现了两个master ,有的客户端连接到老的master 有的客户端链接到新的master。


    脑裂问题可能的成因

    • 网络问题:集群间的网络延迟导致一些节点访问不到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

  4. Elasticsearch 索引文档的流程?
    在这里插入图片描述

    • 协调节点默认使用文档 ID 参与计算(也支持通过 routing),以便为路由提供合适的分片:shard = hash(document_id) % (num_of_primary_shards)
    • 当分片所在的节点接收到来自协调节点的请求后,会将请求写入到 Memory Buffer,然后定时(默认是每隔 1 秒)写入到 Filesystem Cache,这个从 Memory Buffer 到 Filesystem Cache 的过程就叫做 refresh;
    • 当然在某些情况下,存在 Momery Buffer 和 Filesystem Cache 的数据可能会丢失, ES 是通过 translog的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到 translog 中,当 Filesystemcache 中的数据写入到磁盘中时,才会清除掉,这个过程叫做 flush;
    • 在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync 将创建一个新的提交点,并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog。
      flush 触发的时机是定时触发(默认 30 分钟)或者 translog 变得太大(默认为 512M)时;
  5. Elasticsearch 更新和删除文档的流程?

    • 删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;
    • 磁盘上的每个段都有一个相应的.del 文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段。
    • 在新的文档被创建时, Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
  6. Elasticsearch 搜索的流程?
    在这里插入图片描述

    • 搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;
    • 在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。 每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。 PS:在搜索的时候是会查询Filesystem Cache 的,但是有部分数据还在 Memory Buffer,所以搜索是近实时的。
    • 每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
    • 接下来就是取回阶段, 协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并丰富文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。
    • Query Then Fetch 的搜索类型在文档相关性打分的时候参考的是本分片的数据,这样在文档数量较少的时候可能不够准确, DFS Query Then Fetch 增加了一个预查询的处理,询问 Term 和 Document frequency,这个评分更准确,但是性能会变差。
  7. Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法?

    • 64 GB 内存的机器是非常理想的, 但是 32 GB 和 16 GB 机器也是很常见的。少于 8 GB 会适得其反。

    • 如果你要在更快的 CPUs 和更多的核心之间选择,选择更多的核心更好。多个内核提供的额外并发远胜过稍微快一点点的时钟频率。

    • 如果你负担得起 SSD,它将远远超出任何旋转介质。 基于 SSD 的节点,查询和索引性能都有提升。如果你负担得起, SSD 是一个好的选择。

    • 即使数据中心们近在咫尺,也要避免集群跨越多个数据中心。绝对要避免集群跨越大的地理距离。

    • 请确保运行你应用程序的 JVM 和服务器的 JVM 是完全一样的。 在 Elasticsearch 的几个地方,使用 Java 的本地序列化。

    • 通过设置 gateway.recover_after_nodes、 gateway.expected_nodes、 gateway.recover_after_time 可以在集群重启的时候避免过多的分片交换,这可能会让数据恢复从数个小时缩短为几秒钟。

    • Elasticsearch 默认被配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。最好使用单播代替组播。

    • 不要随意修改垃圾回收器(CMS)和各个线程池的大小。

    • 把你的内存的(少于)一半给 Lucene(但不要超过 32 GB!),通过 ES_HEAP_SIZE 环境变量设置。

    • 内存交换到磁盘对服务器性能来说是致命的。如果内存交换到磁盘上,一个 100 微秒的操作可能变成 10 毫秒。 再想想那么多 10 微秒的操作时延累加起来。 不难看出 swapping 对于性能是多么可怕。

    • Lucene 使用了大量的文件。同时, Elasticsearch 在节点和 HTTP 客户端之间进行通信也使用了大量的套接字。 所有这一切都需要足够的文件描述符。你应该增加你的文件描述符,设置一个很大的值,如 64,000。

  8. GC 方面,在使用 Elasticsearch 时要注意什么?

    倒排词典的索引需要常驻内存,无法 GC,需要监控 data node 上 segment memory 增长趋势。

    各类缓存, field cache, filter cache, indexing cache, bulk queue 等等,要设置合理的大小,并且要应该根据最坏的情况来看 heap 是否够用,也就是各类缓存全部占满的时候,还有 heap 空间可以分配给其他任务吗?避免采用 clear cache 等“自欺欺人”的方式来释放内存。

    避免返回大量结果集的搜索与聚合。确实需要大量拉取数据的场景,可以采用 scan & scroll api 来实现。

    cluster stats 驻留内存并无法水平扩展,超大规模集群可以考虑分拆成多个集群通过 tribe node 连接。

    想知道 heap 够不够,必须结合实际应用场景,并对集群的 heap 使用情况做持续的监控。

  9. Elasticsearch 对于大数据量(上亿量级)的聚合如何实现?

    Elasticsearch 提供的首个近似聚合是 cardinality 度量。它提供一个字段的基数,即该字段的 distinct或者 unique 值的数目。它是基于 HLL 算法的。 HLL 会先对我们的输入作哈希运算,然后根据哈希运算的结果中的 bits 做概率估算从而得到基数。其特点是:可配置的精度,用来控制内存的使用(更精确 = 更多内存);小的数据集精度是非常高的;我们可以通过配置参数,来设置去重需要的固定内存使用量。无论数千还是数十亿的唯一值,内存使用量只与你配置的精确度相关。

  10. 在并发情况下, Elasticsearch 如果保证读写一致?

    • 可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;

    • 另外对于写操作,一致性级别支持 quorum/one/all,默认为 quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。

    • 对于读操作,可以设置 replication 为 sync(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置 replication 为 async 时,也可以通过设置搜索请求参数_preference 为 primary 来查询主分片,确保文档是最新版本。

  11. 如何监控 Elasticsearch 集群状态?

    • elasticsearch-head 插件。
    • 通过 Kibana 监控 Elasticsearch。你可以实时查看你的集群健康状态和性能,也可以分析过去的集群、索引和节点指标
  12. 是否了解字典树?
    字典树又称单词查找树, Trie 树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。

    Trie 的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。它有 3 个基本性质:

    • 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
    • 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
    • 每个节点的所有子节点包含的字符都不相同。

    对于中文的字典树,每个节点的子节点用一个哈希表存储,这样就不用浪费太大的空间,而且查询速度上可以保留哈希的复杂度 O(1)。

  13. Elasticsearch 中的集群、节点、索引、文档、类型是什么?

    • 集群是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索引和搜索功能。群集由唯一名 称标识,默认情况下为"elasticsearch"。此名称很重要,因为如果节点设置为按名称加入群集,则该节点只能是群集的一部分。
    • 节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能。
    • 索引就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一个或多个主分片,并且可以有零个或多个副本分片。MySQL =>数据库,Elasticsearch=>索引。
    • 文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但是对于通用字段应该具有相同的数据类型。MySQL => Databases => Tables => Columns / Rows,Elasticsearch=> Indices => Types =>具有属性的文档Doc。
    • 类型是索引的逻辑类别/分区,其语义完全取决于用户。
  14. Elasticsearch 中的倒排索引是什么?
    ES中的倒排索引其实就是 lucene 的倒排索引,区别于传统的正向索引, 倒排索引会再存储数据时将关键词和数据进行关联,保存到倒排表中,然后查询时,将查询内容进行分词后在倒排表中进行查询,最后匹配数据即可。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Elasticsearch入门笔记 的相关文章

随机推荐

  • 开关电源原理、电路组成部分

    开关电源电路图及原理12v分析 详细版 KIA半导体的博客 CSDN博客 开关电源适配器各部分电路原理分析介绍
  • 【区块链实战】什么是 P2P 网络,区块链和 P2P 网络有什么关系

    目录 一 简介 二 知识点 P2P 网络 区块链节点与 P2P 的关系 区块链节点功能分类 P2P 网络特征 三 什么是 P2P 网络 区块链式使用 P2P 网络做什么 1 P2P 网络概念 2 P2P 网络节点特征 3 P2P 与区块链
  • 数据结构之图:无向图的介绍与功能实现,Python——22

    无向图 Undigraph 的介绍 引入 生活中的图 有地图 集成电路板的图 可以看类似的看做是数据结构中的图 数据有 一对一 一对多 和 多对多 的关系 前两种分别表示线性表和树的存储结构性质 而多对多则可表示图的存储结构性质 定义 图是
  • 基于Jupyter(python)使用蒙特·卡罗方法计算圆周率近似值

    使用蒙特 卡罗方法计算圆周率近似值 from random import random times int input 请输入掷飞镖次数 hits 0 for i in range times x random y random if x
  • 操作系统日志收集与分析

    一 Windows日志收集与分析 在运维工作中 如若windows服务器被入侵 往往需要检索和分析相应的安全日志 除了安全设备 系统自带的日志就是取证的关键材料 但是此类日志数量庞大 需要高效分析windows安全日志 提取出我们想要的有用
  • 电源纹波测试,居然还能这么玩

    开关稳压器因其具有非常高的效率优势 正在各个领域逐渐替代线性稳压器 但由于开关稳压器通常被认为具有很大的输出纹波 Ripple 所以很多工程师在高性能和噪声敏感型系统中只考虑使用低压差 LDO 稳压器 而事实上 现今很多高性能开关稳压器都已
  • 迁移学习概述

    1 迁移学习的背景 在有监督的机器学习和尤其是深度学习的场景应用中 需要大量的标注数据 标注数据是一项枯燥无味且花费巨大的任务 关键是现实场景中 往往无法标注足够的数据 而且模型的训练是极其耗时的 因此迁移学习营运而生 传统机器学习 主要指
  • markdown语法介绍

    目录 动态目录 toc 文章目录 目录 一 标题和文本 1 使用 和 标记一级和二级标题 2 使用 号标记 3 换行 二 文字标记和插入图片 链接 1 插入链接 2 插入图片 3 文字标记 设置文字 删除线 下划线 上标 下标 注释 4 e
  • Flip card 卡片翻转效果

    鼠标滑过 卡片翻转 如果想要点击卡片翻转的话就把 hover 改成 hover 然后自己添加点击事件 添加 class hover
  • Java序列化

    Java序列化 原理图 0bjectoutputstream java io 0bjectoutputstream extends outputstreamobjectoutputstream 对象的序列化流 作用 把对象以流的方式写入到文
  • 以太坊合并后,Layer2 何去何从?

    转载原文链接 http www btcwbo com 5671 html 在2022年以太坊开发者峰会上 联合创始人威尔基说 以太坊预计将信号标准链与主网络合并 通过8月份的TheMerge 合并 升级 这样以太坊就可以证明 POS 共识算
  • 英雄联盟-经验砖块

    作为一个LOL老玩家 如果说对游戏细节把握不到位 这是说不过去的 我们时常说道发育为重 那么发育包括等级和装备 我们是不是应该更精细的把我等级呢 今天我们来探究一下 赖线从小兵身上我们能获得多少经验等级 问题1 每个小兵有多少经验值呢 近战
  • 勒索软件攻击防护中的6个常见错误

    勒索软件攻击已经成为影响所有行业和组织的大问题 考虑到这些攻击可能对企业造成的影响 安全专业人员正在尝试以各种方式保护企业的网络 应用和数据 然而 但随着勒索攻击威胁形势的不断变化 很多错误的做法可能会阻碍企业勒索防护计划的有效执行 并使组
  • 最详细的Transformer讲解,Attention Is All You Need

    前言 Attention Is All You Need Google Brain 引用量 30255 1 3 ResNet 贡献 Transformer 是第一个完全依赖自注意力来计算其输入和输出表示而不是使用序列对齐的RNN和CNN 一
  • C++标准库头文件(工具库->csetjmp)

    参考网址 https zh cppreference com w cpp header https www runoob com cplusplus cpp standard library html 工具库 csetjmp 保存执行语境的
  • Android-Fragment详解

    Fragment是Android最常使用的控件之一 一般情况下 我们会在首页使用到 有的小伙伴也会单Activity和多Fragment的App 我总结了一下我了解的Fragment知识 希望对看这篇文章的小伙伴有所帮助 Fragment的
  • USB:Type-A、Type-B、Type-C、miniUSB、microUSB接口类型区分

    通用串行总线 universal serial bus USB 自推出以来 成功替代串口和并口 已经成为计算机和各种设备不可或缺的接口 USB的优点不用多说 网上有很多 今天主要小结一下USB的各种接口类型 避免被各种各样的USB名字搞晕
  • SQL调优案例1

    SQL语句的执行顺序 1 LIMIT 语句 分页查询是最常用的场景之一 但也通常也是最容易出问题的地方 比如对于下面简单的语句 一般 DBA 想到的办法是在 type name create time 字段上加组合索引 这样条件排序都能有效
  • 遥感影像识别-利用较大数据集训练

    回顾 经过前两次的思考 最终还是回到最初的想法上来 利用大量比较合理的电子地图进行模型的训练 看能否产生较好的效果 前两次的博文链接如下 遥感影像识别 制作数据集 遥感影像识别 训练策略 本次训练所使用的数据集 地域覆盖了全国几个主要的城市
  • Elasticsearch入门笔记

    ES入门笔记 1 概述 1 1 什么是全文检索 1 2 es的应用场景 1 3 参考资料 2 核心概念 3 进阶 3 1 集群 3 1 1 单节点 3 1 2 双节点 故障转移 3 1 3 三节点 水平扩容 3 1 4 应对故障 3 2 路