打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Elasticsearch集群剖析架构分析

  第一部分

  这篇文章是一系列文章的一部分,该文章涵盖了流行的分布式搜索引擎Elasticsearch的基础架构和原型示例。在本文中,我们将讨论基础存储模型以及CRUD(创建,读取,更新和删除)操作在Elasticsearch中的工作方式。

  Elasticsearch是一种非常流行的分布式搜索引擎,已在GitHub,SalesforceIQ,Netflix等许多公司中用于全文搜索和分析应用程序。在Insight数据工程研究员计划中,研究员已将Elasticsearch用于其各种不同功能,例如:

  全文搜索

  例如,查找与搜索词最相关的Wikipedia文章。

  聚合分析

  例如,在广告网络上可视化搜索词的出价直方图。

  地理空间API

  例如,一个可以与最接近的驾驶员和骑手相匹配的乘车平台。

  由于Elasticsearch在业界和我们的研究员中如此受欢迎,因此我决定仔细研究一下。在这篇文章中,我想分享我对它的存储模型以及CRUD操作如何工作的了解。

  现在,当我想到分布式系统如何工作时,我想到下图:

  

  表面上方的部分是API,下方部分是发生所有魔力的实际引擎。在这篇文章中,我们将专注于表面以下的部分。主要地,我们将看:

  它是主/从架构还是无主架构?什么是存储模型?写操作是如何进行的?读操作如何进行?搜索结果如何确定相关性的?

  在深入探讨这些概念之前,让我们熟悉一些术语。

  Elasticsearch索引是用于组织数据(如数据库)的逻辑命名空间。Elasticsearch索引具有一个或多个分片(默认为5)。分片是实际上存储数据的Lucene索引,它本身就是搜索引擎。每个分片可以具有零个或多个副本(默认值为1)。Elasticsearch索引还具有“类型”(例如数据库中的表),使您可以在索引中对数据进行逻辑分区。Elasticsearch索引中给定“类型”中的所有文档都具有相同的属性(例如表的架构)。

  

  图a显示了一个Elasticsearch集群,该集群由三个主要分组成,每个分都有一个副本。所有这些分片共同构成一个Elasticsearch索引,每个分片本身就是一个Lucene索引。图b展示了Elasticsearch索引,分片,Lucene索引和文档之间的逻辑关系。

  类比关系数据库术语

  Elasticsearch索引?数据库类型?表映射?模式

  注意:上面的类比仅用于等同目的,并不用于等同。我建议阅读此博客,以帮助决定何时选择索引或类型来存储数据。

  现在,我们熟悉了Elasticsearch世界中的术语,让我们看看节点可以具有的各种角色。

  节点类型

  Elasticsearch的一个实例是一个节点,一组节点组成一个集群。可以通过三种不同的方式配置Elasticsearch集群中的节点:

  主节点

  它控制Elasticsearch集群,并负责所有集群范围的操作,例如创建/删除索引,跟踪哪些节点是集群的一部分以及为节点分配分片。主节点一次处理一个群集状态,并将该状态广播到所有其他节点,这些节点响应主节点的确认。通过在elasticsearch.yml中将node.master属性设置为true(默认值),可以将节点配置为有资格成为主节点。对于大型生产集群,建议使用专用的主节点来仅控制集群而不满足任何用户请求。

  数据节点

  它保存数据和倒排索引。默认情况下,每个节点都配置为一个数据节点,并且属性node.data在elasticsearch.yml中设置为true。如果您想要一个专用的主节点,则将node.data属性更改为false。

  客户端节点:

  如果将node.master和node.data都设置为false,则该节点将配置为客户端节点,并充当负载平衡器,将传入的请求路由到群集中的其他节点。

  您作为客户端连接的Elasticsearch集群中的节点称为协调节点。协调节点将客户端请求路由到集群中的适当分片。对于读取请求,协调节点每次都会选择一个不同的分片来处理请求,以平衡负载。

  在我们开始查看发送到协调节点的CRUD请求如何在集群中传播并由引擎执行之前,让我们了解一下Elasticsearch如何在内部存储数据以在低延迟下为全文搜索提供结果。

  储存模式

  Elasticsearch使用Apache Lucene,这是一种用Java编写的全文搜索库,由Doug Cutting(Apache Hadoop的创建者)开发,在内部使用了称为倒排索引的数据结构,旨在提供低延迟搜索结果。文档是Elasticsearch中数据的单位,并且通过对文档中的单词(Term)进行标记,创建所有唯一单词的排序列表并将文档列表与可以找到该单词的列表相关联来创建反向索引。

  它与书后部的索引非常相似,该索引包含书中所有唯一的单词以及可在其中找到该单词的页面列表。当我们说一个文档被索引时,我们指的是倒排索引。让我们看看以下两个文档的倒排索引如何:

  Doc 1:Insight Data Engineering Fellows Program

  Doc 2:Insight Data Science Fellows Program

  

  如果我们要查找包含“ insight”一词的文档,我们可以扫描倒排索引(对单词进行排序),找到“ insight”一词,然后返回包含该单词的文档ID(在这种情况下为Doc)1和文件2。

  为了提高可搜索性(例如,对小写和大写单词提供相同的结果),首先对文档进行分析,然后将其编入索引。分析包括两个部分:

  将句子标记为单个单词将单词标准化为标准格式

  默认情况下,Elasticsearch使用标准分析器,而标准分析器使用

  标准标记器可在单词边界上拆分单词小写标记过滤器将单词转换为小写

  还有许多其他分析器可用。

  为了提供相关的搜索结果,还使用与索引相同的分析器来分析在文档上进行的每个查询。

  注意:标准分析器还使用停止令牌过滤器,但默认情况下处于禁用状态。

  倒排索引的概念现在很清楚,让我们回顾一下CRUD操作。我们将从写开始。

  写数据的分析

  创建(C)

  当您向协调节点发送请求以索引新文档时,将发生以下一组操作:

  Elasticsearch集群中的所有节点都包含有关哪个分片位于哪个节点上的元数据。协调节点使用文档ID(默认)将文档路由到适当的分片。Elasticsearch使用murmur3作为哈希函数哈希哈希文档ID,并根据索引中主要碎片的数量对mods进行哈希处理,以确定应在文档中建立哪个碎片索引。

  分片=哈希(document_id)%(num_of_primary_shards)

  当节点从协调节点接收到请求时,该请求将被写入事务日志,然后将文档添加到内存缓冲区中。如果请求在主分片上成功,则将请求并行发送到副本分片。只有在所有主分片和副本分片上同步传输日志后,客户端才会收到请求成功的确认。内存缓冲区会定期刷新(默认为1秒),并将内容写入文件系统缓存中的新段。该细分受众群尚未同步,但是该细分受众群处于打开状态,其内容可供搜索。每隔30分钟或当Translog太大时,清空Translog,并同步文件系统缓存。在Elasticsearch中,此过程称为冲洗。在刷新过程中,将清除内存缓冲区,并将内容写入新的段。创建了一个新的提交点,其中的段已同步并刷新到磁盘。旧的日志被删除,新的日志开始。

  下图显示了写入请求和数据的流向。

  

  更新(U)和删除(D)

  删除和更新操作也是写操作。但是,Elasticsearch中的文档是不可变的,因此不能删除或修改以表示任何更改。那么,如何删除/更新文档?

  磁盘上的每个段都有一个与其关联的.del文件。发送删除请求后,该文档并没有真正删除,而是在.del文件中标记为已删除。该文档可能仍与搜索查询匹配,但已从结果中过滤掉。合并细分后(我们将在后续帖子中介绍细分合并),. del文件中标记为已删除的文档不包括在新的合并细分中。

  现在,让我们看看更新是如何工作的。创建新文档后,Elasticsearch会为该文档分配一个版本号。对文档的每次更改都会产生一个新的版本号。执行更新时,旧版本在.del文件中标记为已删除,而新版本在新段中建立索引。较旧的版本可能仍与搜索查询匹配,但是,它已从结果中滤除。

  在对文档建立索引/更新之后,我们想执行搜索请求。让我们看看如何在Elasticsearch中执行搜索请求。

  读数据的分析

  读取操作包括两部分:

  查询阶段提取阶段

  让我们看看每个阶段如何运作。

  查询阶段

  在此阶段,协调节点将搜索请求路由到索引中的所有分片(主分片或副本分片)。分片会独立执行搜索,并创建一个按相关性得分排序的结果优先级队列。所有分片将匹配文档的文档ID和相关分数返回给协调节点。协调节点创建一个新的优先级队列,并对结果进行全局排序。可能有很多与结果匹配的文档,但是,默认情况下,每个分片都会将前10个结果发送到协调节点,并且协调会从所有分片创建优先级队列排序结果,并返回前10个匹配项。

  提取阶段

  在协调节点对所有结果进行排序以生成文档的全局排序列表之后,它随后从所有分片中请求原始文档。所有分片都会丰富文档,然后将其返回到协调节点。

  下图显示了软件读取请求和数据的流向。

  

  如上所述,搜索结果按相关性排序。让我们回顾一下相关性的定义。

  搜索相关性

  相关性由Elasticsearch给搜索结果中返回的每个文档的分数确定。评分的默认算法是tf / idf(术语频率/文档反向频率)。术语频率衡量一个术语在文档中出现的次数(较高的频率==更高的相关性),反向文档频率衡量该术语在整个索引中出现的频率占索引中文档总数的百分比(较高的频率)==减少相关性)。最终分数是tf-idf分数与其他因素的组合,例如术语接近度(针对短语查询),术语相似度(针对模糊查询)等。

  接下来是什么?

  这些CRUD操作得到一些内部数据结构和技术的支持,这对于理解Elasticsearch的工作方式非常重要。在后续文章中,我将在使用Elasticsearch时介绍一些概念和一些陷阱。

  Elasticsearch中的裂脑问题以及如何避免交易记录Lucene段为什么在搜索过程中进行深度分页很危险?计算搜索相关性时的困难和折衷并发控制为什么Elasticsearch接近实时?如何确保一致的读写?第二部分

  在本文中,我们将讨论Elasticsearch如何处理三个C(共识,并发和一致性)以及分片(如跨记录(Write Ahead Log,WAL))和Lucene段的内部概念。

  在上部分中,我们讨论了Elasticsearch中的基础存储模型和CRUD操作。在本章中,我想分享一下Elasticsearch如何解决分布式系统的一些基本挑战以及分片的一些内部概念。这些涉及Insight数据工程研究员在使用Elasticsearch构建数据平台时成功解决和理解的一些操作方面。我主要讲的是:

  共识-脑裂问题和法定人数的重要性并发一致性:确保一致的读写Translog(提前写入日志-WAL)Lucene段共识-脑裂问题和法定人数的重要性

  共识是分布式系统的基本挑战之一。它要求系统中的所有进程/节点都同意给定的数据值/状态。有很多共识算法,例如Raft,Paxos等,都在数学上得到了证明,但是,由于Shay Banon(Elasticsearch的创建者)在此描述的原因,Elasticsearch已实现了自己的共识系统(zen discovery)。zen discovery模块分为两个部分:

  Ping:流程节点用于发现彼此单播:包含主机名列表的模块,用于控制要ping的节点

  Elasticsearch是一个对等系统,其中所有节点都相互通信,并且有一个活动的主节点来更新和控制集群范围的状态和操作。作为ping进程的一部分,将对新的Elasticsearch集群进行选举,在该过程中,从所有符合主条件的节点中选出一个节点作为主节点,其他节点加入该主节点。缺省ping_interval为1秒,而ping_timeout为3秒。当节点加入时,它们会使用默认的join_timeout(它是ping_timeout的20倍)向主服务器发送加入请求。如果主服务器发生故障,则群集中的节点将再次开始ping通,以开始另一个选举。如果节点意外地认为主节点发生故障并通过其他节点发现主节点,则此ping过程也将有所帮助。

  注意:默认情况下,客户端和数据节点不会参与选举过程。可以通过在elasticsearch.yml配置文件中将

  Discovery.zen.master_election.filter_client和

  Discovery.zen.master_election.filter_data属性设置为False来更改此设置。

  为了进行故障检测,主节点对所有其他节点执行ping操作,以检查它们是否还处于活动状态,而所有节点对主节点执行ping操作,以报告它们仍处于活动状态。

  如果使用默认设置,Elasticsearch会遇到裂脑的问题,在网络分区的情况下,节点可以认为主节点已死,并将自己选举为主节点,从而导致具有多个主节点的集群。这可能会导致数据丢失,并且可能无法正确合并数据。可以通过将以下属性设置为合格的主节点法定数量来避免这种情况。

  discovery.zen.minimum_master_nodes=int(# of master eligible nodes/2)+1

  

  此属性需要有效的主节点资格的法定人数参加选举进程完成了新当选的主节点,并有新的主接受了主控权。这是确保群集稳定性的极其重要的属性,并且如果群集大小发生更改,可以动态更新。图a和b显示了分别设置minimum_master_nodes属性和未设置时在发生网络分区时会发生的情况。

  注意:对于生产集群,建议有3个专用主节点,这些主节点不满足任何客户端请求,在任何给定时间中有1个处于活动状态。

  当我们了解了Elasticsearch中的共识后,现在让我们看看它如何处理并发。

  并发

  Elasticsearch是一个分布式系统,支持并发请求。当创建/更新/删除请求命中主分片时,它也会并行发送到副本分片,但是,这些请求可能会乱序到达。在这种情况下,Elasticsearch使用乐观并发控制来确保文档的新版本不会被旧版本覆盖。

  索引的每个文档都有一个版本号,该版本号随对该文档进行的每次更改而增加。这些版本号用于确保按顺序应用更改。为确保应用程序中的更新不会导致数据丢失,Elasticsearch的API允许您指定应将更改应用到的文档的当前版本号。如果请求中指定的版本比分片中存在的版本旧,则请求将失败,这意味着文档已由另一个进程更新。失败请求的处理方式可以在应用程序级别进行控制。还有其他可用的锁定选项,您可以在此处阅读有关它们的信息。

  当我们将并发请求发送到Elasticsearch时,下一个需要关注的问题是-如何使这些请求保持一致?现在,尚不清楚如何回答CAP三角形Elasticsearch落在哪一侧,这是一个辩论,我们将不在本文中解决。

  

  但是,我们将介绍如何使用Elasticsearch实现一致的读写。

  一致性-确保一致的读写

  对于写入,Elasticsearch支持与大多数其他数据库不同的一致性级别,以允许进行初步检查,以查看允许写入的可用分片数量。可用的选项是定额,全部。默认情况下,它设置为仲裁,这意味着仅当大多数分片可用时才允许写操作。在大多数分片可用的情况下,仍然可能会由于某种原因而导致对副本的写入失败,并且在这种情况下,据说该副本有故障,并且分片将在其他节点上重建。

  对于读取,直到刷新间隔之后才能搜索新文档。为确保搜索请求返回文档最新版本的结果,可以将复制设置为同步(默认),该操作将在完成对主分片和副本分片的操作后返回写请求。在这种情况下,来自任何分片的搜索请求都将返回文档最新版本的结果。即使您的应用程序要求Replication=async以获得更高的索引编制率,也可以将_preference参数设置为primary以用于搜索请求。这样,就可以查询搜索请求的主分片,并确保结果将来自文档的最新版本。

  当我们了解Elasticsearch如何处理共识,并发性和一致性时,让我们回顾一下分片内部的一些重要概念,这些概念会导致Elasticsearch作为分布式搜索引擎具有某些特征。

  跨日志

  自从关系数据库开发以来,预写日志(WAL)或事务日志(translog)的概念已出现在数据库领域。事务日志可确保在发生故障时确保数据完整性,其基本原则是必须先记录并提交预期的更改,然后再将对数据的实际更改提交到磁盘。

  当对新文档建立索引或更新旧文档时,Lucene索引将更改,并且这些更改将提交到磁盘以实现持久性。在每个写请求之后执行该操作非常昂贵,因此,以一次将多个更改持久保存到磁盘的方式执行该操作。正如我们在之前的博客中所述,刷新操作(Lucene提交)默认情况下每30分钟执行一次,或者当translog太大时(默认值为512MB)执行一次。在这种情况下,可能会丢失两次Lucene提交之间的所有更改。为了避免出现此问题,Elasticsearch使用了一个Translog。所有索引/删除/更新操作都会写入转日志,并且在每次索引/删除/更新操作(默认情况下每5秒钟)之后对转日志进行同步,以确保更改是永久的。在主分片和副本分片上同步传输日志后,客户端会收到写入确认。

  如果两次Lucene提交之间发生硬件故障或重新启动,则重播事务日志以从上次Lucene提交之前丢失的所有更改中恢复,并将所有更改应用于索引。

  注意:建议重新启动Elasticsearch实例之前显式刷新事务日志,因为启动的速度更快,因为要重播的事务日志将为空。POST / _all / _flush命令可用于刷新集群中的所有索引。

  通过translog刷新操作,文件系统缓存中的段将提交到磁盘,以使索引中的更改持久化。现在,我们来看看什么是Lucene细分市场。

  Lucene段

  Lucene索引由多个段组成,一个段本身就是一个功能齐全的反向索引。段是不可变的,这使得Lucene可以将新文档递增地添加到索引中,而无需从头开始重建索引。对于每个搜索请求,将搜索索引中的所有段,并且每个段都消耗CPU周期,文件句柄和内存。这意味着段数越多,搜索性能将越低。

  为了解决此问题,Elasticsearch将较小的段合并到一个较大的段中(如下图所示),将新合并的段提交到磁盘,并删除旧的较小的段。

  

  这会在后台自动发生,而不会中断索引或搜索。由于段合并会消耗资源并影响搜索性能,因此Elasticsearch会限制合并过程,以使有足够的资源可用于搜索。

  接下来是什么?

  对于搜索请求,将搜索Elasticsearch索引的给定分片中的所有Lucene段,但是,获取所有匹配的文档或排名结果深的文档对您的Elasticsearch集群来说是危险的。在后续文章中,我们将了解原因,并复习以下主题,包括一些在Elasticsearch中进行的取舍,以在低延迟下提供相关的搜索结果。

  Elasticsearch的近实时方面为什么深度分页搜索可能很危险?计算搜索相关性时需要权衡第三部分

  这篇文章是一系列文章的一部分,该文章涵盖了流行的分布式搜索引擎Elasticsearch的基础架构和原型示例。在这篇文章中,我们将讨论Elasticsearch如何提供近乎实时的搜索,并考虑取舍取舍来计算搜索的相关性。

  在上部分中,我们讨论了Elasticsearch如何解决分布式系统的一些基本挑战。在本文中,我们将回顾Elasticsearch的各个方面,例如近乎实时的搜索和权衡取舍,它考虑了Insight数据工程研究员在构建数据平台时所利用的搜索相关性。主要地,我们将看:

  近实时搜索为什么在分布式搜索中进行深度分页可能很危险?计算搜索相关性时需要权衡近实时搜索

  尽管Elasticsearch中的更改无法立即看到,但它确实提供了接近实时的搜索引擎。如前一篇文章所述,将Lucene更改提交到磁盘是一项昂贵的操作。为了避免在继续使文档可供搜索的同时提交对磁盘的更改,在内存缓冲区和磁盘之间有一个文件系统缓存。内存缓冲区每秒刷新一次(默认情况下),并在文件系统缓存中创建一个具有反向索引的新段。此段已打开并可供搜索。

  文件系统缓存可以具有文件句柄,并且可以打开,读取和关闭文件,但是它驻留在内存中。由于默认情况下刷新间隔为1秒,因此更改不会立即可见,因此几乎是实时的。由于事务日志是未持久存储在磁盘上的更改的持久记录,因此它也有助于CRUD操作的近实时方面。对于每个请求,在查看相关段之前都会在事务日志中搜索任何最近的更改,因此,客户端几乎可以实时访问所有更改。

  可以在每次“创建/更新/删除”操作后显式刷新索引,以使更改立即可见,但不建议这样做,因为由于创建了太多小段,它会影响搜索性能。对于搜索请求,将搜索Elasticsearch索引的给定分片中的所有Lucene段,但是,获取所有匹配的文档或结果页面中较深的文档对于您的Elasticsearch集群是危险的。让我们看看为什么。

  为什么在分布式搜索中进行深度分页可能很危险?

  当您在具有大量匹配文档的Elasticsearch中发出搜索请求时,默认情况下,返回的第一页包含前10个结果。搜索API具有from和size参数,用于指定与搜索匹配的所有文档的结果深度。例如,如果要查看与搜索匹配的等级50至60的文档,则from=50和size=10。当每个分片接收搜索请求时,它会创建一个优先级队列,其大小为+ size,以满足搜索结果本身的要求,然后将结果返回给协调节点。

  

  如果要查看结果的范围从50,000到500,010,则每个分片将创建一个优先级队列,每个队列具有500,010个结果,并且协调节点将必须对分片数进行排序* 50,010个结果在内存中。根据您拥有的硬件资源,可能无法达到这种级别的分页,但是足以说明您应该对深层分页非常小心,因为它很容易使您的集群崩溃。

  可以使用滚动API获取所有与结果匹配的文档,滚动API的作用更像是关系数据库中的游标。滚动API禁用了排序功能,并且每个分片只要具有与搜索匹配的文档,便会继续发送结果。

  如果要获取大量文档,则对计分结果进行排序非常昂贵。而且由于Elasticsearch是分布式系统,因此计算文档的搜索相关性得分非常昂贵。现在让我们看一下在计算搜索相关性时要进行的许多权衡之一。

  计算搜索相关性时需要权衡

  Elasticsearch将tf-idf用于搜索相关性,并且由于其分布式性质,因此计算全局idf(反向文档频率)非常昂贵。相反,每个分片都会计算一个本地idf,以便为生成的文档分配相关性得分,并仅返回该分片上文档的结果。类似地,所有分片都返回使用本地idf计算的相关分数的结果文档,并且协调节点对所有结果进行排序以返回最上面的结果。在大多数情况下,这很好,除非您的索引在关键字方面有所偏斜,或者单个分片上没有足够的数据来表示全局分布。

  例如,如果您要搜索单词“ insight”,并且大多数包含“ insight”一词的文档都位于一个分片上,则与查询匹配的文档将不会在每个分片上公平地排名为本地idf值将有很大的不同,搜索结果可能不太相关。同样,如果没有足够的数据,则某些搜索的本地idf值可能会发生很大变化,并且结果可能不如预期的那样相关。在具有足够数据的现实情况下,本地idf值趋于均匀,并且搜索结果具有相关性,因为文档的评分较高。

  有两种方法可以解决本地idf分数,但实际上不建议在生产系统中使用它们。

  一种方法是,您只能为索引分配一个分片,然后本地idf是全局IDf,但这不会为并行性/扩展留有余地,并且对于庞大的索引也不实用。另一种方法是在搜索请求中使用参数dfs_query_then_search(dfs=分布式频率搜索),该参数首先计算所有分片的本地idf,然后将这些本地idf值组合起来以计算整个索引的全局IDf,然后将结果返回给使用全局idf计算的相关性得分。在生产中不建议这样做,因为有足够的数据可以确保频率术语分布合理。

  在最近的几篇文章中,我们回顾了Elasticsearch的一些基本原理,这些基本原理对于入门非常重要。在后续文章中,我将使用Apache Spark在Elasticsearch中为数据建立索引。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
ES的一知半解
ElasticSearch部署架构和容量规划
Elasticsearch存储深入详解
GitHub使用elasticsearch遇到的一些问题及解决方法
图解 ElasticSearch 原理,写得太好了!
Elasticsearch集群部署
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服