Elasticsearch索引、搜索流程及集群选举细节整理
背景
最近在做搜索推荐相关的优化,在对elasticsearch进行优化时查阅了比较多的资料,现在对其中的一部分进行整理和翻译,做一个记录。主要分为三个部分:
·Elasticsearch 数据索引流程·Elasticsearch 数据搜索流程·集群相关
一、Elasticsearch 数据索引流程
Elasticsearch 是一个非常强大且灵活的分布式数据系统,可以接受和索引数十亿个文档,使它们可以近乎实时地用于搜索、聚合和分析。这篇文章是关于它是如何完成的,重点介绍基本的新数据插入和从数据写入请求一直到写入磁盘的数据流向。
索引是一个相对简单的高级过程,包括:
·数据通过 API 写入·数据路由到正确的索引、分片和节点·映射、归一化和分析·存储在内存和磁盘上·使其可用于搜索
然而,实际过程要复杂得多,特别是考虑到集群及其数据的分布式特性、所涉及的高数据速率以及所有同时进行的并行特性。此外,这一切都必须尽可能可靠和可扩展。这就是 Elasticsearch 的神奇之处。
让我们更详细地看一下这些步骤。
数据到达及分批
当数据通过索引 API 到达时,Elasticsearch 首先了解要索引的传入数据。Logstash、Beats 甚至 cURL 等客户端将数据发送到集群节点进行处理。他们一次可以发送一个文档,但通常使用批量 API 批量发送数据,以减少开销并加快处理速度。批次只是在一个 API 调用中发送的一组文档,文档之间不需要相关性,即它们可以包含用于多个不同索引的数据。
摄取的数据可以发送到任何节点。然而,较大的集群通常使用专用的协调节点(更多用于搜索而不是摄取数据),甚至是专用的摄取(ingest)节点,它们可以运行数据管道来预处理数据。数据到达的任何节点都将成为该批次的协调节点,并将数据路由到正确的位置,即使实际摄取工作是在保存目标索引数据的数据节点上执行的。
管道和数据流
数据通常到达单个标准索引,但也可以路由到数据流或摄取管道。数据流是一个 X-Pack 功能,通常用于处理时间序列数据,例如指标和日志,并且本质上解析为此摄取过程的实际支持索引。管道是一组处理器,用于在索引之前处理文档数据。
如果请求或批处理包含管道并且协调节点不是摄取节点(节点可以是单一角色,也可以同时有多个角色),则它似乎会首先路由到摄取节点,然后继续路由到主节点。由于可能协调节点与摄取节点是分开的,也可能协调节点同时也承担摄取节点的角色,所以不清楚是协调节点还是摄取节点将文档发送到主节点,但可能是摄取节点来进行协调运行处理管道,然后将文档返回到协调节点进行下一步。
路由
一旦数据到达协调节点,必须将每个文档路由到正确的索引、分片和节点以进行摄取。由于批量请求可能包含多个索引的数据,并且单个索引的多个文档可能会进入单独的分片,因此路由步骤是针对每个文档运行的,并且对于将每个文档都放到正确的位置非常重要。这个过程开始了“协调阶段”。
每个文档的第一步是让协调节点使用提供的索引、别名、数据流等来确定文档将要去的实际目标索引。如果索引不存在,则会创建它,然后该过程可以继续。请注意,Elasticsearch 尝试在进行任何索引之前首先创建批量请求所需的所有索引。
在协调节点知道目标索引后,它会运行一个路由过程来为文档选择索引的分片。路由可能会变得复杂,默认情况下由文档 ID 驱动,默认情况下由协调节点自动生成。
默认情况下,索引数据的分片算法如下
shard_num = hash(_routing) % num_primary_shards
routing字段的取值,默认是_id字段或者是_parent字段,这样的取值在hash之后再与有多少个shard的数量取模,最终得到这条数据应该在被分配在那个一个shard上,也就是说默认是基于hash的分片,保证在每个shard上数据量都近似平均,这样就不会出现负载不均衡的情况,然后在检索的时候,es默认会搜索所有shard上的数据,最后在master节点上汇聚在处理后,返回最终数据。
如果您愿意,客户端可以指定自己的 ID,还可以控制用于路由的字段,例如时间戳、用户、源设备等,作为将相关(和可快速查询)数据集中在一个单一位置的集群策略碎片。此外,索引可以具有强制文档到特定分片的自定义路由。但一般来说,每个文档将随机分布在其目标索引的分片中。
路由过程的结果将是目标分片及其分片 ID,但我们必须记住分片可能有副本。如果有副本,协调节点也会将它们包含在路由列表中,因此结果是该文档的所有分片的列表:主分片和副本。然后,协调节点查找这些分片的节点 ID 以了解将文档路由到何处以进行索引。
索引阶段
一旦协调节点知道文档的目标主分片和该分片的节点,文档就会发送到该节点进行主索引,作为“初级阶段”的一部分。主分片会验证请求,然后在本地为它们编制索引,这也会先验证mapping和字段等。下面将更详细地描述该过程。
如果主节点索引成功,主分片节点(不是协调器节点)将文档并行发送给所有处于同步活动状态的副本节点,这就是“副本阶段”。主分片节点等待所有副本节点完成索引,然后将结果返回给等待的协调节点。一旦批处理中的所有文档都被索引(或失败),协调器就会将结果返回给原始 API 调用者,即客户端。
每个文档都由其主分片和副本分片中的每一个分片单独索引。
了解每个文档都被它所在的每个分片单独索引是很重要的,并且所有这些都必须在给定文档被标记为'indexed'之前完成。因此,如果索引的副本数为 3,则意味着每个文档都将转到四个分片(主分片和三个副本)并由它们单独索引,所有分片都位于不同的节点上。
Elasticsearch 中没有真正的预处理或中央索引,集群完成的“工作”随着给定索引的副本数量线性增加。这通常是大多数索引延迟发生的地方,因为它只能与最慢的节点和分片一样慢地完成。
协调器节点尽可能多地并行化批处理中的文档。它并行地将文档发送到它们路由的主分片,但似乎每个主分片只对一个请求进行排队(串行处理)。因此,如果批次有 10 个文档用于单个分片的单个索引,这些将按顺序处理,一次一个,但如果批次有 10 个文档用于两个索引,每个有 5 个分片,路由结果为一个每个分片的文档,所有 10 个都将并行完成(相当于10个文档对应10个分片)。这是增加主分片数量来加速索引处理的一种方式。
分片级索引
一旦一个文档到达一个给定的节点,该节点拥有一个用来写入数据的分片,实际的文档索引就完成了。第一步是将文档写入 translog 以获得持久化副本,以防在此之后节点崩溃。
translog 是 Elasticsearch 的一项功能,可提供超出 Lucene 自身所能做到的持久性,并且是可靠系统的关键。如果节点在实际索引完成之前崩溃,重新启动时 Elasticsearch 会将文档重播到索引过程中以确保它得到处理。
实际的索引过程有几个步骤:
·Elasticsearch 中的映射文档字段·在 Lucene 中解析·添加到Lucene的倒排索引
首先,节点通过索引的模板映射文档的字段,该模板指定如何处理每个字段,例如类型,但还包括分析器和其他选项。由于每个文档可以有不同的字段和数据,这个映射步骤是必不可少的,也是经常发生错误的地方,因为字段类型不匹配、越界等。这项工作是在 Elasticsearch 级别完成的,因为 Lucene 有没有模板或地图的概念。Lucene 文档只是一组字段,每个字段都有名称、类型和值。
其次,该文档被传递给 Lucene 进行analyze(分词等操作)。实际上,这意味着在其上运行配置的分析器,每个分析器可以有多个步骤和组件,包括tokenizing和filtering,它们一起可以做很多强大的事情。
Tokenization 是将每个字段中的数据拆分为Token,例如用空格来分隔单词获取多个token;过滤包括除基本过滤之外的范围更广泛的内容,以将文本转换成小写、删除停用词和通过词干进行归一化(即更改单词)到他们的“正常”版本,例如dogs变成dog,watched变成watch,等等)
最后,Lucene 获取结果并为该文档构建其存储记录。这通常包括文档中的每个字段,以及可用于重新索引等的特殊字段,例如 _source 和 _all,以及非常重要的倒排索引本身。
Lucene 将所有这些写入内存中的segment缓冲区,然后向协调节点返回成功。一旦在所有副本分片上完成此操作,从协调器节点或客户端的角度来看,该文档的索引基本上是完整的。
获取磁盘上的文档数据并可搜索
刚刚索引的文档只在内存中的临时多文档segment中,还没有在磁盘上,也不能用于搜索。两个独立的进程在后台运行以实现这两件事。
索引数据尚未在磁盘上,也尚不可搜索。
第一个过程是“refresh”以使数据可用于搜索。refresh interval按索引设置,默认为 1 秒。许多用户将此设置得更高,例如 30-60 秒,因为这是一项昂贵的操作,每秒执行一次会降低整体索引吞吐量。请注意,不经常搜索的索引在搜索之前不会自动刷新,以提高批量索引速度。
在刷新间隔,内存缓冲区的segment被合并并写入文件系统上的单个新segment,并且数据可用于搜索。但是,虽然该segment现在存在于文件系统上,但它主要位于文件缓存中,实际上可能不在磁盘上,如果此时发生崩溃,这将是一个问题。数据是可用的,但不安全,虽然如果发生崩溃,translog 仍然存在并会被回放,并且文档将被再次索引。
为了确保磁盘上的数据安全,有一个单独的 Elasticsearch 刷新进程执行 Lucene 提交,合并和同步上述segment,确保它们确实在磁盘上。一旦完成,Elasticsearch 将截断 translog,因为数据现在安全地存储在磁盘上并且不会在崩溃中丢失。Elasticsearch 根据 translog 大小(默认最大值为 512MB)安排这些刷新,以帮助保持合理的恢复时间。
本质上,translog 为所有新文档更改以及 Elasticsearch 刷新/Lucene 提交之间保持可靠性。注意 translog 有它自己的可靠性设置,包括每 5 秒 fsync 到磁盘的默认设置。
Elasticsearch 还单独运行后台线程以尽可能地继续合并segments,使用分层合并策略尽量减少段数(因为它们是按顺序搜索的),同时不会降低整体实时索引和搜索性能。这与上述所有过程是分开的。
总体结果是,在任何给定时间,任何特定可用索引都由磁盘上一组不同大小的永久段和文件缓存中的一些新段组成。加上仅在内存中的索引但尚不可用的段,等待刷新间隔。
相关参数:
a) index.translog.sync_interval- translog 被fsync
写入磁盘并提交的频率,无论写操作如何。默认为5s
. 100ms
为最小值。
b) index.translog.durability-是否fsync
在每次索引、删除、更新或批量请求后提交 translog。此设置接受以下参数:(i) 请求 (ii) 异步
c) index.translog_flush_threshold-默认为 512mb。
问题
Elasticsearch 索引是一个很好但复杂的分布式过程,它平衡了高性能、数据可靠性和强大的功能。虽然它运作良好,但事情可能并且确实会出错。一些问题出在文档本身,而另一些问题出在集群方面。
集群级别的问题通常与过程中的碎片丢失或移动有关。正常的流程是从协调器节点到主节点再到副本节点,但是如果在这个过程中主节点发生了变化,或者副本丢失了怎么办?有各种复杂的重试、超时和路由过程尝试保存文档,当然,它们可能会失败,此时客户端必须重试。
其中一些,例如副本超时或失败,将导致该分片被声明为不同步和无效,将索引状态更改为黄色并安排副本重建。其他的,比如网络分区,会导致主节点本身被声明为无效,当它试图与副本通信时会发现这一点。总的来说,这是一个复杂但强大的系统。
在生产中使用 Elasticsearch 时要记住的一些重要点:
1.它提供了乐观并发控制。在更新任何文档时,可以在请求中传递一个版本。它在更新时不会锁定任何分片或文档。2.所有文档都是不可变的,无法更改,更新会删除现有文档(软删除会在稍后的某个时间点在后台删除)。因此,我们必须始终确保最多使用机器中可用容量的一半。3.始终保留比可用实例(或节点)更多的分片,以便索引可以通过添加更多节点在负载增加的情况下扩展。请记住,索引的#shards 一旦创建就无法修改。每个节点的推荐 #shards 为2:1。4.Elasticsearch 在批量操作方面表现更好。如果可能,尝试批量索引或搜索您的文档。5.如果需要精确的字段搜索,请使用过滤器而不是查询,因为过滤器比查询更有效。过滤结果也可以缓存。6.3个主节点集群是首选。7.禁用索引中的_all字段并使用 copy_to 选项复制需要复制到_all字段的字段。默认情况下,每个字段的数据都存储在_all字段中。此过程称为黑名单方法。建议使用白名单方法,以获得有效的索引。它节省了很多空间
参考
有几篇关于这个过程的好文章,但大多数都是多年的,并且最好地涵盖了 Elasticsearch 的早期版本,尽管这些过程仍然大体相同:
·Elasticsearch From the Bottom Up[1]·Elasticsearch From the Top Down[2]·Anatomy of an Elasticsearch Cluster[3]·Current Replication Docs[4]·Elasticsearch Refresh & Flush Operations[5]
出处
译自:https://medium.com/swlh/elasticsearch-indexing-data-flow-d56ea14e1772
二、Elasticsearch 搜索全流程
介绍
Elasticsearch是一个非常强大且灵活的分布式数据系统,主要专注于搜索和分析数十亿个文档。这篇文章是关于它是如何完成的,重点是通过集群的查询和数据流,从磁盘到所有的分片、索引、节点、分析、过滤器等等。
这个博客是关于搜索如何在相当深的层次上工作的,我们的目标是遍历从搜索请求到结果回复的过程,包括将查询路由到碎片、分析器、映射、聚合和协调。其中一些项目来自文档或代码,其他项目来自其他人的著作,还有一些是猜测,因为并非所有内容都清楚或有据可查。
基本搜索数据流
基本的搜索数据流如下:
·到达Coordinator·索引列表和别名·分片路由·实际搜索·组装文档列表·获取文件·排序和聚合·返回结果
然而,实际过程要复杂得多,特别是考虑到集群及其数据的分布式特性、涉及的高搜索率以及所有同时进行的并行特性。此外,这一切都必须尽可能可靠和可扩展。这就是 Elasticsearch 的神奇之处。
我们将在下面更深入地研究每一个。
到达
当搜索查询通过各种搜索 API 到达时,Elasticsearch 首先了解它。Kibana、应用程序甚至 cURL 等客户端将搜索请求发送到集群节点进行处理。有多种 API 和选项,但几乎所有的 API 和选项都以本质上的搜索结束,尽管或多或少具有复杂性和资源需求。
搜索请求可以发送到任何节点,但较大的集群通常使用具有足够 CPU 和 RAM 的专用协调节点来管理高搜索量并限制协调对数据或其他节点的影响。查询到达的任何节点都将成为此查询的协调节点,并将数据路由到正确的位置,即使大部分实际搜索工作是在保存源索引数据的数据节点上执行的。
路由
查询到达协调节点后,必须将其路由到正确的索引、分片和节点以进行搜索。由于查询请求可能涵盖许多索引和分片,因此路由步骤对于将每个索引和分片都放到正确的位置非常重要。
首先,协调器根据查询索引模式或别名构建目标索引列表。这通常是单个索引,但也可以是“logsash-*”之类的模式或指向索引或模式的别名。结果是查询需要搜索的实际索引列表。
然后协调器构建所有目标索引的不同分片的列表。这可能会令人困惑,因为在 Elasticsearch 中,一个不同的分片(带有分片 ID)实际上是一组单一的主副本及其可选的副本副本。
因此,具有 5 个分片和 2 个副本的索引将总共有 15 个分片,但只有 5 个不同的分片,每个分片的 ID 都以 0 开头,因此在这种情况下为 0-4。每个都将有 3 个分片:一个主分片和两个副本。给定的主节点和它的副本共享相同的分片 ID,只是在分片列表中将 primaryOrReplica 设置为“p”或“r”,因此您将看到分片:0/p、0/r 和第二个 0/r(其中每一个也有一个唯一的分配 ID,这是 Elasticsearch 在内部区分它们的方式)。
对于每个索引并基于索引路由选项,协调器决定查询是转到单个不同的分片还是所有分片。大多数查询会转到所有不同的分片,但特定的路由可以确保所有查询的文档都在单个不同的分片中;如果是这样,查询只会转到那个不同的分片。
负载
无论查询是针对一个不同的分片还是所有分片,对于涉及的每个分片,协调器都会选择要查询每个分片的实际分片,主分片或副本之一。
因此,如果我们查询具有 5 个分片和 2 个副本的索引,则有 5 个不同的分片,总共有 15 个分片。假设没有配置路由,实际查询将发送到 5 个分片,每个分片从每个不同分片的 3 个副本(1 个主分片,2 个副本)中选择。
默认情况下,这种选择或“平衡”算法大多是随机的,尽管有一些优化,包括支持在最近的查询中表现最好的分片。它也可以通过“首选项”选项在查询的基础上进行控制。
路由过程的结果是要查询的实际分片列表,以及这些分片所在的节点,因为这是协调器需要发送要运行的查询的地方。
搜索分片——查询阶段
分片执行实际的搜索(和评分)工作。查询阶段搜索就是这样,搜索与查询匹配的文档。
此搜索的每个分片都会发生几件事:
·Elasticsearch 级别的映射·Lucene 中的Analysis·在 Lucene 中搜索·在 Lucene 中评分
该映射类似于索引时的映射,Elasticsearch 将查询字段映射到底层 Lucene 数据字段和结构,以创建每个段(实际上是一个 Lucene 索引)都可以执行的 Lucene 兼容查询。看起来映射和转换到 Lucene 查询是由每个分片完成的,类似于索引由每个分片完成。
分析与索引时完全相同,查询的文本部分通过相同的分析器运行,例如标记文本、转换为小写和词干等。这样查询文本将最好地匹配这些文件已编入索引。
段搜索
分片级搜索实际上是一系列合并在一起的段级搜索(这就是为什么段越少通常性能越好)。由于段正在执行真正的搜索工作,因此大多数缓存也在段级别,这就是您在集群和节点统计信息中看到它们的方式。
段级别的实际搜索过程详细信息取决于查询类型和所需内容。这可以有很大的不同,从简单的术语搜索像 name = “bob” 到复杂的多字段全文搜索在各种语言中。
任何这些搜索的结果通常是一个文档 ID 列表,可以选择对其进行评分和排序以获得相关性。这个列表被称为优先级队列,它的大小取决于查询,默认为 10,但如果查询使用普通分页,它将是 'from+size',它可以在深度分页时使用大量 RAM。
评分本身是一个复杂的领域,比非评分查询需要更多的资源,特别是如果使用 DTS 模式来提高全局评分结果。我们将把 Lucene 评分留给其他博客。
按任何文档字段(即不是分数)排序是通过 doc 值完成的,因为倒排索引不太适合于此。Doc 值是 Lucene 的序列化列数据存储,它将一个字段的所有数据打包在一起,因此可以快速读取大量值,这非常适合聚合,也适用于排序。默认情况下,除分析字符串外的所有字段都启用它们。
聚合更复杂,因为它们需要一种方法来访问所有匹配的文档,即它们不能使用短列表。它们也适用于“文档值”,而不是倒排索引。该过程因聚合类型而异,在某些情况下,例如术语计数,分片返回为其文档设置的整个聚合大小,协调器会将它们合并在一起。
例如,对于大小为 100 的术语计数,每个分片返回 160 个术语,协调器会将它们合并并排序为最终的 100 个给客户端。每个分片的计数可以通过 shard_size 参数进行调整,默认为 (size * 1.5 + 10),或者 160 表示大小为 100。
对于指标聚合,例如平均值,它需要所有匹配的文档及其字段数据。目前尚不清楚这是如何完成的,但大概每个分片都提供了自己的平均值和计数,然后协调节点可以将其合并。Min/Max 和其他可能类似的处理。
搜索进程段的注意事项有自己的缓存,用于以下几件事:
·Filter Cache - 给定过滤器的文档 ID 段缓存。这极大地加快了搜索速度,这也是过滤器流行的原因。·Field Cache — 字段数据值的段缓存。主要在获取阶段稍后使用。·Page Cache——当然,在 Elasticsearch 之外,用于分段数据。
分片还维护一个query cache,因此它可以在将来返回相同查询的结果。但是,如果索引实际发生更改,则每次索引刷新(默认为 1 秒,更常见的是 30-60 秒)时,此缓存都会失效,因此虽然对繁重的索引不太有用,但它仍然可以帮助搜索大量索引。请注意,此缓存由给定节点上的所有分片共享,最多为堆大小的 1%。
虽然过滤器有缓存,但查询(评分搜索)不是,因此对于查询和任何未缓存的过滤器或字段,搜索必须命中倒排索引以构建文档 ID 列表。可以缓存生成的过滤器结果和字段数据。
请注意,所有搜索都是从刷新或提交的索引段完成的,因此只有在刷新后才会搜索或找到数据。唯一的例外是当客户端通过 ID 执行 GET 获取文档时,在这种情况下,可以在刷新索引之前从 translog 中提取它。有关刷新和 translog 的更多详细信息,请参阅 Elasticsearch Indexing Dataflow 上的博客。
Coordinator归集数据
每个分片将返回其最高命中作为文档 ID,而不是整个文档。因此,如果我们有 5 个分片且默认大小为 10,我们将得到 50 个结果。如果涉及多个索引,它们的分片也会返回它们的结果。协调器节点合并这些列表以获得实际的排序列表,并在收集阶段继续为它们获取实际数据。
获取阶段——收集
一旦协调器节点有了它需要的最终文档 ID 列表,它将返回到分片以获取实际数据,直到现在它都不需要这些数据。这是第 2 阶段或“收集”过程,它使用对各种分片的多文档 GET 请求来获取文档数据,通常作为 _source 字段。请注意,如果客户端仅要求聚合(大小 = 0),则会跳过此步骤。
请注意,这是协调节点 RAM 可能失控的地方,也是首先拥有协调节点的主要原因之一。这些节点将处理、合并和排序结果所需的 CPU 和 RAM 资源保存在几个易于监控的节点中,重要的是让这些资源密集型进程远离主节点、数据节点和 ML 节点,以执行其他重要工作。
例如,在深度分页中,返回的文档数量将是“from + size”页面,因此来自多个索引和分片的深度页面将收集“number_of_shards * (from + size)”文档,这会变得非常大,吃光了所有的堆。在这种情况下,用户通常使用滚动查询。大文档大小和列表同样会导致 RAM 使用量增加。
聚合通常是根据分片返回的聚合结果构建的,聚合似乎没有获取阶段,但如果查询大小>0,协调器仍会为客户端获取底层文档数据。
一旦协调节点拥有所有文档及其数据和/或聚合,它就会构建最终结果,并在需要时使用元数据和其他元素对其进行增强,然后将它们返回给调用者,过程完成。
问题
Elasticsearch 搜索非常快速和强大,尽管它是一个复杂的分布式过程,可以平衡高性能、准确性和功能。更大的实时性和稳定性,即不让大查询炸毁集群。虽然它工作得很好,但事情也确实可能会出问题。
当然,任何数据系统都可能耗尽关键资源,尤其是 CPU 和磁盘 IO 带宽。Elasticsearch 非常依赖这两者,但由于是分布式的,通常很容易根据需要添加更多。
另一个关键资源是 RAM,这是可能发生更多问题的地方。在最近的版本中,在保护系统方面做了很多工作,尤其是断路器的概念,它限制了单个查询和聚合操作可以消耗的 RAM。
查询级别的断路器也用于查询的各个部分,例如字段数据,以防止查询使系统的该部分过载(并提供关于您的查询如何潜在地损害集群的准确报告)。
查询驱动的内存相关问题通常来自字段组合、大聚合、大文档、深分页等。与此相关的是,拥有不适合页面缓存的大索引会导致 I/O 压力,这不会使系统崩溃,但会减慢系统速度。
其他问题包括在搜索过程中超时和分片或节点丢失。通常,Elasticsearch 会使用其他分片重试这些操作,以尝试尽可能完整地回答客户端的查询。注意默认情况下,如果存在内部超时或分片故障,Elasticsearch 将返回部分结果。
概括
Elasticsearch 是一个非常漂亮和强大的系统,能够通过简单的界面快速灵活地搜索数十亿文档。从这个博客中,您可以看到请求和数据如何在集群中移动以从磁盘到达客户端。
参考
有几篇关于这个过程的好文章,但大多数都是多年的,并且最好地涵盖了 Elasticsearch 的早期版本,尽管这些过程仍然大体相同:
·Elasticsearch 及其内部工作[6]·自下而上的 Elasticsearch[7]·自上而下的 Elasticsearch[8]·Elasticsearch 集群剖析[9]
出处
译自:https://steve-mushero.medium.com/elasticsearch-search-data-flow-2a5f799aac6a
三、集群相关
共识[10]是分布式系统的基本挑战之一。它要求系统中的所有进程/节点就给定的数据值/状态达成一致。有很多共识算法,如Raft[11]、Paxos[12]等,它们在数学上被证明是有效的,但是,由于Shay Banon(Elasticsearch 的创建者)在这里[13]描述的原因,Elasticsearch 已经实现了自己的共识系统(zen discovery)。zen discovery模块有两个部分:
·Ping: 进程节点用来发现彼此·Unicast:包含主机名列表的模块,用于控制要 ping 的节点
Elasticsearch 是一个点对点系统,其中所有节点相互通信,并且有一个活动主节点更新和控制集群范围内的状态和操作。作为 ping 过程的一部分,新的 Elasticsearch 集群会进行选举,其中从所有符合主节点的节点中选出一个节点作为主节点,其他节点加入主节点。默认ping_interval为1 sec,ping_timeout为3 sec。节点加入,他们发送加入请求到主节点,join_timeout的默认值是ping_timeout的20倍. 如果 master 失败,集群中的节点会再次开始 ping 以开始另一次选举。如果节点意外地认为主节点发生故障并通过其他节点发现主节点,则此 ping 过程也有帮助。
注意:默认情况下,客户端和数据节点不参与选举过程。这可以通过在elasticsearch.yml配置文件中将discovery.zen.master_election.filter_client和discovery.zen.master_election.filter_data属性设置为False来更改。
对于故障检测,主节点 ping 所有其他节点以检查它们是否处于活动状态,所有节点都 ping 主节点以报告它们处于活动状态。
如果使用默认设置,Elasticsearch 会遇到脑裂[14]问题 ,在网络分区的情况下,节点会认为 master 已死并选择自己作为 master,从而导致集群具有多个 master。这可能会导致数据丢失,并且可能无法正确合并数据。这可以通过将以下属性设置为符合主节点的法定人数来避免。
discovery.zen.minimum_master_nodes = int(# of master eligible nodes/2)+1
此属性需要活动主节点的法定人数,以加入新当选的主节点,以便选举过程完成,并让新主节点接受其主节点。这是确保集群稳定性的一个极其重要的属性,并且可以在集群大小发生变化时进行动态更新。图a和b显示了分别设置和不设置minimum_master_nodes属性时,对于网络分区会发生什么情况。注意:对于一个生产集群,建议有3个专用的主节点,这些节点不服务于任何客户端请求,其中1个节点在任何时候都是活动的。正如我们在Elasticsearch中了解的共识,现在让我们看看它是如何处理并发性的。
并发
Elasticsearch 是一个分布式系统,支持并发请求。当创建/更新/删除请求到达主分片时,它也会并行发送到副本分片,但是,这些请求可能会乱序到达。在这种情况下,Elasticsearch 使用乐观并发控制[15]来确保较新版本的文档不会被旧版本覆盖。
每个索引的文档都有一个版本号,该版本号随着应用于该文档的每次更改而递增。这些版本号用于确保按顺序应用更改。为确保我们的应用程序中的更新不会导致数据丢失,Elasticsearch 的 API 允许您指定应应用更改的文档的当前版本号。如果请求中指定的版本比分片中存在的版本旧,则请求失败,这意味着文档已被另一个进程更新。可以在应用程序级别控制如何处理失败的请求。还有其他锁定选项可用,您可以在此处[16]阅读有关它们的信息[17]。
当我们向 Elasticsearch 发送并发请求时,下一个问题是——我们如何使这些请求保持一致?现在,尚不清楚回答CAP[18]三角形 Elasticsearch 落在哪一边,这是一个我们不会在这篇文章中解决的争论。
但是,我们将回顾如何使用 Elasticsearch 实现一致的写入和读取。
一致性——确保一致的写入和读取
对于写入,Elasticsearch 支持与大多数其他数据库不同的一致性级别,以允许进行初步检查以查看允许写入的可用分片数量。可用选项有quorum、one和all。默认情况下,它设置为quorum ,这意味着只有在大多数分片可用时才允许写入操作。在大多数分片可用的情况下,仍然可能发生对副本的写入由于某种原因失败,在这种情况下,副本被称为有故障,分片将在不同的节点上重建。
对于读取,新文档在刷新间隔之后才可用于搜索。为了确保搜索请求从最新版本的文档返回结果,复制可以设置为同步(默认),它在主分片和副本分片上完成操作后返回写请求。在这种情况下,来自任何分片的搜索请求将返回文档最新版本的结果。即使您的应用程序需要replication=async以获得更高的索引率,也可以将_preference参数设置为主要用于搜索请求。这样,主分片会被查询以获取搜索请求,并确保结果将来自文档的最新版本。
随着我们了解 Elasticsearch 如何处理共识、并发和一致性,让我们回顾一下分片内部的一些重要概念,这些概念导致 Elasticsearch 作为分布式搜索引擎的某些特征。
Translog
自关系数据库的发展以来,预写日志 (WAL) 或事务日志 (translog) 的概念一直存在于数据库世界中。Translog 确保在发生故障时的数据完整性,其基本原则是必须在将数据的实际更改提交到磁盘之前记录并提交预期的更改。
当新文档被索引或旧文档被更新时,Lucene 索引会发生变化,这些变化将提交到磁盘以进行持久化。在每次写入请求之后执行它是一项非常昂贵的操作,因此,它以一次将多个更改持久化到磁盘的方式执行。正如我们在之前的博客中[19]所描述的, 默认情况下每 30 分钟执行一次刷新操作(Lucene 提交)或当 translog 变得太大时(默认为 512MB)。在这种情况下,有可能会丢失两次 Lucene 提交之间的所有更改。为了避免这个问题,Elasticsearch 使用了一个 translog。所有索引/删除/更新操作都写入 translog,并且在每次索引/删除/更新操作后(或默认情况下每 5 秒)对 translog 进行 fsync,以确保更改是持久的。在主分片和副本分片上对 translog 进行 fsync 后,客户端会收到写入确认。
如果在两次 Lucene 提交或重新启动之间发生硬件故障,则会重播 translog 以从最后一次 Lucene 提交之前丢失的任何更改中恢复,并将所有更改应用于索引。
注意:建议在重启 Elasticsearch 实例之前显式刷新 translog,因为启动会更快,因为要重放的 translog 将为空。POST /_all/_flush命令可用于刷新集群中的所有索引。
通过 translog 刷新操作,文件系统缓存中的段被提交到磁盘以使索引中的更改持久化。现在让我们看看什么是 Lucene 段。
Lucene 段
一个 Lucene 索引由多个段组成,一个段本身就是一个功能齐全的倒排索引。段是不可变的,这允许 Lucene 以增量方式向索引添加新文档,而无需从头开始重建索引。对于每个搜索请求,搜索索引中的所有段,每个段消耗 CPU 周期、文件句柄和内存。这意味着段数越多,搜索性能就越低。
为了解决这个问题,Elasticsearch 将小段合并成一个更大的段(如下图所示),将新合并的段提交到磁盘并删除旧的小段。
这会在后台自动发生,不会中断索引或搜索。由于段合并可能会耗尽资源并影响搜索性能,因此 Elasticsearch 会限制合并过程以获得足够的资源可用于搜索。
对于搜索请求,会搜索 Elasticsearch 索引的给定分片中的所有 Lucene 段,但是,获取所有匹配文档或位于排名结果深处的文档对您的 Elasticsearch 集群来说是危险的。
参考
·https://www.alibabacloud.com/blog/elasticsearch-distributed-consistency-principles-analysis-1---node_594358?spm=a2c41.12499374.0.0·https://www.alibabacloud.com/blog/elasticsearch-distributed-consistency-principles-analysis-2---meta_594359?spm=a2c65.11461447.0.0.460e1107c5kGpY·https://www.alibabacloud.com/blog/elasticsearch-distributed-consistency-principles-analysis-3---data_594360?spm=a2c65.11461447.0.0.460e1107c5kGpY
出处
译自:https://blog.insightdatascience.com/anatomy-of-an-elasticsearch-cluster-part-ii-6db4e821b571
References
[1]
Elasticsearch From the Bottom Up: https://www.elastic.co/blog/found-elasticsearch-from-the-bottom-up
[2]
Elasticsearch From the Top Down: https://www.elastic.co/blog/found-elasticsearch-top-down
[3]
Anatomy of an Elasticsearch Cluster: https://blog.insightdatascience.com/anatomy-of-an-elasticsearch-cluster-part-ii-6db4e821b571
[4]
Current Replication Docs: https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-replication.html
[5]
Elasticsearch Refresh & Flush Operations: https://qbox.io/blog/refresh-flush-operations-elasticsearch-guide
[6]
Elasticsearch 及其内部工作: https://medium.com/@shivanshugoyal0111/elasticsearch-internals-4c4c9ec077fa
[7]
自下而上的 Elasticsearch: https://www.elastic.co/blog/found-elasticsearch-from-the-bottom-up
[8]
自上而下的 Elasticsearch: https://www.elastic.co/blog/found-elasticsearch-top-down
[9]
Elasticsearch 集群剖析: https://blog.insightdatascience.com/anatomy-of-an-elasticsearch-cluster-part-ii-6db4e821b571
[10]
共识: https://en.wikipedia.org/wiki/Consensus_(computer_science)
[11]
Raft: https://raft.github.io/
[12]
Paxos: https://en.wikipedia.org/wiki/Paxos_(computer_science)
[13]
在这里: https://www.elastic.co/blog/resiliency-elasticsearch
[14]
脑裂: https://en.wikipedia.org/wiki/Split-brain_(computing)
[15]
乐观并发控制: https://en.wikipedia.org/wiki/Optimistic_concurrency_control
[16]
在此处: https://www.elastic.co/guide/en/elasticsearch/guide/2.x/concurrency-solutions.html
[17]
信息: https://www.elastic.co/guide/en/elasticsearch/guide/2.x/concurrency-solutions.html
[18]
CAP: https://en.wikipedia.org/wiki/CAP_theorem
[19]
之前的博客中: http://insightdataengineering.com/blog/elasticsearch-crud/