一、全文索引
Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎。当然 Elasticsearch 并不仅仅是 Lucene 那么简单,它不仅包括了全文搜索功能,还可以进行以下工作:
- 倒排索引:分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
- 将数据和索引分离
- 索引数据存入内存
- 压缩数据
1、全文索引应用场景
全文索引有很多应用场景,开发者最常见的莫过于日志搜索功能,生活中最常见的莫过于各种网站的搜索功能,如下图所示的商品搜索:
2、为什么有了全文索引
为什么不能用Mysql索引?如下图所示,Mysql可以方便的对各个字段进行存储,而且Mysql也有自己完备且性能较高的B+树索引机制,可是为什么不能用作索引呢,其原因就是因为全文索引需要对一个字段的任意内容进行查询,这就类似于Mysql的like "%***%"查询机制,是不能走索引的。
为什么不用内存数据库呢?我们知道一旦东西放入内存速度就会很快,即使对字段进行全文搜索也不是不可行,可是全文索引场景面临的是大量的数据,比如日志搜索,那么对于全文搜索来说,成本变成了一个巨大的问题。
因此就诞生出了全文索引。
3、倒排索引原理
倒排索引又称为反向索引,倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。带有倒排索引的文件我们称为倒排索引文件,简称倒排文件(inverted file)。
ElasticSearch引擎把文档数据写入到倒排索引(Inverted Index)的数据结构中,倒排索引建立的是分词(Term)和文档(Document)之间的映射关系,在倒排索引中,数据是面向词(Term)而不是面向文档的。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
首先来看看原始文件:
我们来看看三种形式的倒排文件:
标记了出现次数:
标记了出现位置,可用作搜索高亮:
4、索引分词
为了提高可搜索性(例如,为小写字母和小写字提供相同的结果),首先对文档进行分词并对其进行索引。 分词由两部分组成:
- 将句子标记成单词
- 将单词规范化为标准表单
默认情况下,Elasticsearch使用标准分词器
- 标准标记器(Standard tokenizer),用于在单词边界上分割单词
- 小写令牌过滤器(Lowercase token filter)将单词转换为小写
还有许多其他分词器可用,比如对中文支持良好的ik分词器。
注意:标准分词器也使用停止令牌过滤器,但默认情况下禁用。
5、倒排索引-查询过程
查询包含“搜索引擎”的文档
- 通过倒排索引获得“外卖”对应的文档id列表,有2,5
- 通过正排索引查询1和3的完整内容
- 返回最终结果
6、倒排列表(Posting List)
- 倒排列表记录了单词对应的文档集合,有倒排索引项(Posting)组成
- 倒排索引项主要包含如下信息:
- 文档id用于获取原始信息
- 单词频率(TF,Term Frequency),记录该单词在该文档中出现的次数,用于后续相关性算分
- 位置(Posting),记录单词在文档中的分词位置(多个),用于做词语搜索(Phrase Query)
- 偏移(Offset),记录单词在文档的开始和结束位置,用于高亮显示
7、倒排索引结构
如果词典比较多的情况下,全放到内存是否也会出现瓶颈?倒排索引有以下三部分组成,分别是Term Index单词索引,Term Dictionary单词字典,Posting List倒排列表。也有的文档把TermIndex归并到TermDictionary中,统称为单词字典。
由此可知单词索引是单词词典(Term Dictionary)的B+树实现。
8、检索评分
elasticsearch和solr,这两个引擎都是基于lucene服务器开发的。我们搜索一条短语或句子通过倒排索引会检索到相关的文档,有了这些文档我们就需要给这些文档进行评分进而推送给用户。ES和solr这两个引擎都是基于lucene服务器开发的,评分机制都是基于LUCENE的,Lucene的默认评分是TF/IDF算法。
- TF:TF代表分词项在某个点文档中出现的次数(term frequency)
- IDF:IDF代表代表分词项在多少个文档中出现(inverse document frequency)
TF-IDF值均可以通过倒排列表获得。如下所示:
- 单词ID:记录每个单词的单词编号;
- 单词:对应的单词;
- 文档频率:代表文档集合中有多少个文档包含某个单词
- 倒排列表:包含单词ID及其他必要信息
- DocId:单词出现的文档id
- TF:单词在某个文档中出现的次数
- POS:单词在文档中出现的位置
lucene的完整评分公式有6个部分组成:
- coord(q,d) 评分因子,基于文档中出现查询项的个数。越多的查询项在一个文档中,说明文档的匹配程度越高。
- queryNorm(q)查询的标准查询
- tf(t in d) 指项t在文档d中出现的次数frequency。具体值为次数的开根号。
- idf(t) 反转文档频率, 出现项t的文档数docFreq
- t.getBoost 查询时候查询项加权
- norm(t,d) 长度相关的加权因子
评分公式如下:Lucene打分公式的数学推导
二、Elasticsearch基本介绍
1、Elasticsearch的index
Elasticsearch的索引(index)是用于组织数据的逻辑命名空间(如数据库)。Elasticsearch的索引有一个或多个分片(shard)(默认为5)。分片是实际存储数据的Lucene索引,它本身就是一个搜索引擎。每个分片可以有零个或多个副本(replicas)(默认为1)。Elasticsearch索引还具有“类型”(如数据库中的表),允许您在索引中对数据进行逻辑分区。Elasticsearch索引中给定“类型”中的所有文档(documents)具有相同的属性(如表的模式)。
图a显示了一个由三个主分片组成的Elasticsearch集群,每个主分片分别有一个副本。所有这些分片一起形成一个Elasticsearch索引,每个分片是Lucene索引本身。
图b演示了Elasticsearch索引,分片,Lucene索引和文档(document)之间的逻辑关系。
2、Elasticsearch集群的节点类型
Elasticsearch的一个实例是一个节点,一组节点形成一个集群。Elasticsearch集群中的节点可以通过三种不同的方式进行配置:
Master节点
- Master节点控制Elasticsearch集群,并负责在集群范围内创建/删除索引,跟踪哪些节点是集群的一部分,并将分片分配给这些节点。主节点一次处理一个集群状态,并将状态广播到所有其他节点,这些节点需要响应并确认主节点的信息。
- 在elasticsearch.yml中,将nodes.master属性设置为true(默认),可以将节点配置为有资格成为主节点的节点。
- 对于大型生产集群,建议拥有一个专用主节点来控制集群,并且不服务任何用户请求。
Data节点
- 数据节点用来保存数据和倒排索引。默认情况下,每个节点都配置为一个data节点,并且在elasticsearch.yml中将属性node.data设置为true。如果您想要一个专用的master节点,那么将node.data属性更改为false。
Client节点
如果将node.master和node.data设置为false,则将节点配置为客户端节点,并充当负载平衡器,将传入的请求路由到集群中的不同节点。
若你连接的是作为客户端的节点,该节点称为协调节点(coordinating node)。协调节点将客户机请求路由到集群中对应分片所在的节点。对于读取请求,协调节点每次选择不同的分片来提供请求以平衡负载。
在我们开始审查发送到协调节点的CRUD请求如何通过集群传播并由引擎执行之前,让我们看看Elasticsearch如何在内部存储数据,以低延迟为全文搜索提供结果。
三、Elasticsearch实现原理分析
1、write(写)/create(创建)操作实现原理
当您向协调节点发送请求以索引新文档时,将执行以下操作:
- 所有在Elasticsearch集群中的节点都包含:有关哪个分片存在于哪个节点上的元数据。协调节点(coordinating node)使用文档ID(默认)将文档路由到对应的分片。Elasticsearch将文档ID以murmur3作为散列函数进行散列,并通过索引中的主分片数量进行取模运算,以确定文档应被索引到哪个分片。
shard = hash(document_id) % (num_of_primary_shards)
- 当节点接收到来自协调节点的请求时,请求被写入到translog,并将该文档添加到内存缓冲区。如果请求在主分片上成功,则请求将并行发送到副本分片。只有在所有主分片和副本分片上的translog被fsync’ed后,客户端才会收到该请求成功的确认。
- 内存缓冲区以固定的间隔刷新(默认为1秒),并将内容写入文件系统缓存中的新段。此分段的内容更尚未被fsync’ed(未被写入文件系统),分段是打开的,内容可用于搜索。
- translog被清空,并且文件系统缓存每隔30分钟进行一次fsync,或者当translog变得太大时进行一次fsync。这个过程在Elasticsearch中称为flush。在刷新过程中,内存缓冲区被清除,内容被写入新的文件分段(segment)。当文件分段被fsync’ed并刷新到磁盘,会创建一个新的提交点(其实就是会更新文件偏移量,文件系统会自动做这个操作)。旧的translog被删除,一个新的开始。
下图显示了写入请求和数据流程:
2、Update和Delete实现原理
删除和更新操作也是写操作。但是,Elasticsearch中的文档是不可变的(immutable),因此不能删除或修改。那么,如何删除/更新文档呢?
磁盘上的每个分段(segment)都有一个.del文件与它相关联。当发送删除请求时,该文档未被真正删除,而是在.del文件中标记为已删除。此文档可能仍然能被搜索到,但会从结果中过滤掉。当分段合并时,在.del文件中标记为已删除的文档不会被包括在新的合并段中。
至于更新,创建新文档时,Elasticsearch将为该文档分配一个版本号。对文档的每次更改都会产生一个新的版本号。当执行更新时,旧版本在.del文件中被标记为已删除,并且新版本在新的分段中编入索引。旧版本可能仍然与搜索查询匹配,但是从结果中将其过滤掉。
3、Read的实现原理
读操作由两个阶段组成:
- 查询阶段(Query Phase)
- 获取阶段(Fetch Phase)
查询阶段(Query Phase)
在此阶段,协调节点将搜索请求路由到索引(index)中的所有分片(shards)(包括:主要或副本)。分片独立执行搜索,并根据相关性分数创建一个优先级排序结果。所有分片将匹配的文档和相关分数的文档ID返回给协调节点。协调节点创建一个新的优先级队列,并对全局结果进行排序。可以有很多文档匹配结果,但默认情况下,每个分片将前10个结果发送到协调节点,协调创建优先级队列,从所有分片中分选结果并返回前10个匹配。
获取阶段(Fetch Phase)
在协调节点对所有结果进行排序,已生成全局排序的文档列表后,它将从所有分片请求原始文档。
所有的分片都会丰富文档并将其返回到协调节点。