剖析Elasticsearch集群系列涵盖了当今最流行的分布式搜索引擎Elasticsearch的底层架构和原型实例。html
本文是这个系列的第一篇,在本文中,咱们将讨论的Elasticsearch的底层存储模型及CRUD(建立、读取、更新和删除)操做的工做原理。node
Elasticsearch是当今最流行的分布式搜索引擎,GitHub、 SalesforceIQ、Netflix等公司将其用于全文检索和分析应用。在Insight,咱们用到了Elasticsearch的诸多不一样功能,好比:linux
正是由于Elasticsearch如此流行而且就在咱们身边,我决定深刻研究一下。本文,我将分享Elasticsearch的存储模型和CRUD操做的工做原理。算法
当我在思考分布式系统是如何工做时,我脑海里的图案是这样的:数据库
水面以上的是API,如下的才是真正的引擎,一切魔幻般的事件都发生在水下。本文所关注的就是水下的部分,咱们将关注:apache
在咱们深刻这些概念以前,让咱们熟悉下相关的术语。缓存
Elasticsearch中的索引是组织数据的逻辑空间(就比如数据库)。1个Elasticsearch的索引有1个或者多个分片(默认是5个)。分片对应实际存储数据的Lucene的索引,分片自身就是一个搜索引擎。每一个分片有0或者多个副本(默认是1个)。Elasticsearch的索引还包含"type"(就像数据库中的表),用于逻辑上隔离索引中的数据。在Elasticsearch的索引中,给定一个type,它的全部文档会拥有相同的属性(就像表的schema)。网络
(点击放大图像)数据结构
图a展现了一个包含3个分片的Elasticsearch索引,每一个分片拥有1个副本。这些分片组成了一个Elasticsearch索引,每一个分片自身是一个Lucene索引。图b展现了Elasticsearch索引、分片、Lucene索引和文档之间的逻辑关系。架构
对应于关系数据库术语
Elasticsearch Index == Database Types == Tables Properties == Schema
如今咱们熟悉了Elasticsearch世界的术语,接下来让咱们看一下节点有哪些不一样的角色。
一个Elasticsearch实例是一个节点,一组节点组成了集群。Elasticsearch集群中的节点能够配置为3种不一样的角色:
主节点:控制Elasticsearch集群,负责集群中的操做,好比建立/删除一个索引,跟踪集群中的节点,分配分片到节点。主节点处理集群的状态并广播到其余节点,并接收其余节点的确认响应。
每一个节点均可以经过设定配置文件elasticsearch.yml中的node.master属性为true(默认)成为主节点。
对于大型的生产集群来讲,推荐使用一个专门的主节点来控制集群,该节点将不处理任何用户请求。
数据节点:持有数据和倒排索引。默认状况下,每一个节点均可以经过设定配置文件elasticsearch.yml中的node.data属性为true(默认)成为数据节点。若是咱们要使用一个专门的主节点,应将其node.data属性设置为false。
客户端节点:若是咱们将node.master属性和node.data属性都设置为false,那么该节点就是一个客户端节点,扮演一个负载均衡的角色,将到来的请求路由到集群中的各个节点。
Elasticsearch集群中做为客户端接入的节点叫协调节点。协调节点会将客户端请求路由到集群中合适的分片上。对于读请求来讲,协调节点每次会选择不一样的分片处理请求,以实现负载均衡。
在咱们开始研究发送给协调节点的CRUD请求是如何在集群中传播并被引擎执行以前,让咱们先来看一下Elasticsearch内部是如何存储数据,以支持全文检索结果的低延迟服务的。
Elasticsearch使用了Apache Lucene,后者是Doug Cutting(Apache Hadoop之父)使用Java开发的全文检索工具库,其内部使用的是被称为倒排索引的数据结构,其设计是为全文检索结果的低延迟提供服务。文档是Elasticsearch的数据单位,对文档中的词项进行分词,并建立去重词项的有序列表,将词项与其在文档中出现的位置列表关联,便造成了倒排索引。
这和一本书后面的索引很是相似,即书中包含的词汇与其出现的页码列表关联。当咱们说文档被索引了,咱们指的是倒排索引。咱们来看下以下2个文档是如何被倒排索引的:
文档1(Doc 1): Insight Data Engineering Fellows Program
文档2(Doc 2): Insight Data Science Fellows Program
若是咱们想找包含词项"insight"的文档,咱们能够扫描这个(单词有序的)倒排索引,找到"insight"并返回包含改词的文档ID,示例中是Doc 1和Doc 2。
为了提升可检索性(好比但愿大小写单词都返回),咱们应当先分析文档再对其索引。分析包括2个部分:
默认状况下,Elasticsearch使用标准分析器,它使用了:
还有不少可用的分析器在此不列举,请参考相关文档。
为了实现查询时能获得对应的结果,查询时应使用与索引时一致的分析器,对文档进行分析。
注意:标准分析器包含了停用词过滤器,但默认状况下没有启用。
如今,倒排索引的概念已经清楚,让咱们开始CRUD操做的研究吧。咱们从写操做开始。
当咱们发送索引一个新文档的请求到协调节点后,将发生以下一组操做:
Elasticsearch集群中的每一个节点都包含了改节点上分片的元数据信息。协调节点(默认)使用文档ID参与计算,以便为路由提供合适的分片。Elasticsearch使用MurMurHash3函数对文档ID进行哈希,其结果再对分片数量取模,获得的结果便是索引文档的分片。
shard = hash(document_id) % (num_of_primary_shards)
下图展现了写请求及其数据流。
(点击放大图像)
删除和更新也都是写操做。可是Elasticsearch中的文档是不可变的,所以不能被删除或者改动以展现其变动。那么,该如何删除和更新文档呢?
磁盘上的每一个段都有一个相应的.del文件。当删除请求发送后,文档并无真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,可是会在结果中被过滤掉。当段合并(咱们将在本系列接下来的文章中讲到)时,在.del文件中被标记为删除的文档将不会被写入新段。
接下来咱们看更新是如何工做的。在新的文档被建立时,Elasticsearch会为该文档指定一个版本号。当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,可是会在结果中被过滤掉。
文档被索引或者更新后,咱们就能够执行查询操做了。让咱们看看在Elasticsearch中是如何处理查询请求的。
读操做包含2部份内容:
咱们来看下每一个阶段是如何工做的。
在这个阶段,协调节点会将查询请求路由到索引的所有分片(主分片或者其副本)上。每一个分片独立执行查询,并为查询结果建立一个优先队列,以相关性得分排序(咱们将在本系列的后续文章中讲到)。所有分片都将匹配文档的ID及其相关性得分返回给协调节点。协调节点建立一个优先队列并对结果进行全局排序。会有不少文档匹配结果,可是,默认状况下,每一个分片只发送前10个结果给协调节点,协调节点为所有分片上的这些结果建立优先队列并返回前10个做为hit。
当协调节点在生成的全局有序的文档列表中,为所有结果排好序后,它将向包含原始文档的分片发起请求。所有分片填充文档信息并将其返回给协调节点。
下图展现了读请求及其数据流。
(点击放大图像)
如上所述,查询结果是按相关性排序的。接下来,让咱们看看相关性是如何定义的。
相关性是由搜索结果中Elasticsearch打给每一个文档的得分决定的。默认使用的排序算法是tf/idf(词频/逆文档频率)。词频衡量了一个词项在文档中出现的次数 (频率越高 == 相关性越高),逆文档频率衡量了词项在所有索引中出现的频率,是一个索引中文档总数的百分比(频率越高 == 相关性越低)。最后的得分是tf-idf得分与其余因子好比(短语查询中的)词项接近度、(模糊查询中的)词项类似度等的组合。
这些CRUD操做由Elasticsearch内部的一些数据结构所支持,这对于理解Elasticsearch的工做机制很是重要。在接下来的系列文章中,我将带你们走进相似的那些概念并告诉你们在使用Elasticsearch中有哪些坑。