Elasticsearch 权威指南中文版  https://www.elastic.co/guide/cn/elasticsearch/guide/cn/index.html

 
对于跳跃表,我们看看有一些什么样的优化方式可以让其更加适应一些场景。优化的话,我们一般从空间和时间两个方面来考虑一个优化,对于空间的话,又分成内存空间优化和磁盘空间优化,当然一般首先考虑内存的优化,对于时间来说,也分成构建时间和查询时间两个方面来优化,空间和时间是两个相互矛盾的优化,具体到实际操作上如何取舍就要看具体的场景了。
 
Apache Lucene是一个开源的高性能、可扩展的信息检索引擎,提供了强大的数据检索能力。Lucene已经发展了很多年,其功能越来越强大,架构也越来越精细。它目前不仅仅能支持全文索引,也能够提供多种其他类型的索引方式,来满足不同类型的查询需求。
 
基于Lucene的开源项目有很多,最知名的要属Elasticsearch和Solr,如果说Elasticsearch和Solr是一辆设计精美、性能卓越的跑车,那Lucene就是为其提供强大动力的引擎。为了驾驭这辆跑车让它跑的更快更稳定,我们需要对它的引擎研究透彻。
 
在此之前我们在专栏已经发表了多篇文章来剖析Elasticsearch的数据模型、读写路径、分布式架构以及Data/Meta一致性等问题,这篇文章之后我们会陆续发表一系列的关于Lucene的原理和源码解读,来全面解析Lucene的数据模型和数据读写路径。
 
Lucene官方对自己的优势总结为几点:
  1. Scalable, High-Performance Indexing
  2. Powerful, Accurate and Efficient Search Algorithms
希望通过我们的系列文章,能够让读者理解Lucene是如何达到这些目标的。
 
整个分析会基于Lucene 7.2.1版本,在读这篇文章之前,需要有一定的知识基础,例如了解基本的搜索和索引原理,知道什么是倒排、分词、相关性等基本概念,了解Lucene的基本使用,例如Directory、IndexWriter、IndexSearcher等。

基本概念

在深入解读Lucene之前,先了解下Lucene的几个基本概念,以及这几个概念背后隐藏的一些东西。
 
 如图是一个Index内的基本组成,Segment内数据只是一个抽象表示,不代表其内部真实数据结构。
 
Index(索引)
类似数据库的表的概念,但是与传统表的概念会有很大的不同。传统关系型数据库或者NoSQL数据库的表,在创建时至少要定义表的Scheme,定义表的主键或列等,会有一些明确定义的约束。而Lucene的Index,则完全没有约束。Lucene的Index可以理解为一个文档收纳箱,你可以往内部塞入新的文档,或者从里面拿出文档,但如果你要修改里面的某个文档,则必须先拿出来修改后再塞回去。这个收纳箱可以塞入各种类型的文档,文档里的内容可以任意定义,Lucene都能对其进行索引。
 
Document(文档)
类似数据库内的行或者文档数据库内的文档的概念,一个Index内会包含多个Document。写入Index的Document会被分配一个唯一的ID,即Sequence Number(更多被叫做DocId),关于Sequence Number后面会再细说。
 
Field(字段)
一个Document会由一个或多个Field组成,Field是Lucene中数据索引的最小定义单位。Lucene提供多种不同类型的Field,例如StringField、TextField、LongFiled或NumericDocValuesField等,Lucene根据Field的类型(FieldType)来判断该数据要采用哪种类型的索引方式(Invert Index、Store Field、DocValues或N-dimensional等),关于Field和FieldType后面会再细说。
 
Term和Term Dictionary
Lucene中索引和搜索的最小单位,一个Field会由一个或多个Term组成,Term是由Field经过Analyzer(分词)产生。Term Dictionary即Term词典,是根据条件查找Term的基本索引。
 
Segment
一个Index会由一个或多个sub-index构成,sub-index被称为Segment。Lucene的Segment设计思想,与LSM类似但又有些不同,继承了LSM中数据写入的优点,但是在查询上只能提供近实时而非实时查询。
 
Lucene中的数据写入会先写内存的一个Buffer(类似LSM的MemTable,但是不可读),当Buffer内数据到一定量后会被flush成一个Segment,每个Segment有自己独立的索引,可独立被查询,但数据永远不能被更改。这种模式避免了随机写,数据写入都是Batch和Append,能达到很高的吞吐量。Segment中写入的文档不可被修改,但可被删除,删除的方式也不是在文件内部原地更改,而是会由另外一个文件保存需要被删除的文档的DocID,保证数据文件不可被修改。Index的查询需要对多个Segment进行查询并对结果进行合并,还需要处理被删除的文档,为了对查询进行优化,Lucene会有策略对多个Segment进行合并,这点与LSM对SSTable的Merge类似。
 
Segment在被flush或commit之前,数据保存在内存中,是不可被搜索的,这也就是为什么Lucene被称为提供近实时而非实时查询的原因。读了它的代码后,发现它并不是不能实现数据写入即可查,只是实现起来比较复杂。原因是Lucene中数据搜索依赖构建的索引(例如倒排依赖Term Dictionary),Lucene中对数据索引的构建会在Segment flush时,而非实时构建,目的是为了构建最高效索引。当然它可引入另外一套索引机制,在数据实时写入时即构建,但这套索引实现会与当前Segment内索引不同,需要引入额外的写入时索引以及另外一套查询机制,有一定复杂度。
 
Sequence Number
Sequence Number(后面统一叫DocId)是Lucene中一个很重要的概念,数据库内通过主键来唯一标识一行,而Lucene的Index通过DocId来唯一标识一个Doc。不过有几点要特别注意:
  1. DocId实际上并不在Index内唯一,而是Segment内唯一,Lucene这么做主要是为了做写入和压缩优化。那既然在Segment内才唯一,又是怎么做到在Index级别来唯一标识一个Doc呢?方案很简单,Segment之间是有顺序的,举个简单的例子,一个Index内有两个Segment,每个Segment内分别有100个Doc,在Segment内DocId都是0-100,转换到Index级的DocId,需要将第二个Segment的DocId范围转换为100-200。
  2. DocId在Segment内唯一,取值从0开始递增。但不代表DocId取值一定是连续的,如果有Doc被删除,那可能会存在空洞。
  3. 一个文档对应的DocId可能会发生变化,主要是发生在Segment合并时。
 
Lucene内最核心的倒排索引,本质上就是Term到所有包含该Term的文档的DocId列表的映射。所以Lucene内部在搜索的时候会是一个两阶段的查询,第一阶段是通过给定的Term的条件找到所有Doc的DocId列表,第二阶段是根据DocId查找Doc。Lucene提供基于Term的搜索功能,也提供基于DocId的查询功能。
 
DocId采用一个从0开始底层的Int32值,是一个比较大的优化,同时体现在数据压缩和查询效率上。例如数据压缩上的Delta策略、ZigZag编码,以及倒排列表上采用的SkipList等,这些优化后续会详述。

索引类型

Lucene中支持丰富的字段类型,每种字段类型确定了支持的数据类型以及索引方式,目前支持的字段类型包括LongPoint、TextField、StringField、NumericDocValuesField等。
 
 
 
 
如图是Lucene中对于不同类型Field定义的一个基本关系,所有字段类都会继承自Field这个类,Field包含3个重要属性:name(String)、fieldsData(BytesRef)和type(FieldType)。name即字段的名称,fieldsData即字段值,所有类型的字段的值最终都会转换为二进制字节流来表示。type是字段类型,确定了该字段被索引的方式。
FieldType是一个很重要的类,包含多个重要属性,这些属性的值决定了该字段被索引的方式。
Lucene提供的多种不同类型的Field,本质区别就两个:一是不同类型值到fieldData定义了不同的转换方式;二是定义了FieldType内不同属性不同取值的组合。这种模式下,你也能够通过自定义数据以及组合FieldType内索引参数来达到定制类型的目的。
要理解Lucene能够提供哪些索引方式,只需要理解FieldType内每个属性的具体含义,我们来一个一个看:
  • stored: 代表是否需要保存该字段,如果为false,则lucene不会保存这个字段的值,而搜索结果中返回的文档只会包含保存了的字段。
  • tokenized: 代表是否做分词,在lucene中只有TextField这一个字段需要做分词。
  • termVector: 这篇文章很好的解释了term vector的概念,简单来说,term vector保存了一个文档内所有的term的相关信息,包括Term值、出现次数(frequencies)以及位置(positions)等,是一个per-document inverted index,提供了根据docid来查找该文档内所有term信息的能力。对于长度较小的字段不建议开启term verctor,因为只需要重新做一遍分词即可拿到term信息,而针对长度较长或者分词代价较大的字段,则建议开启term vector。Term vector的用途主要有两个,一是关键词高亮,二是做文档间的相似度匹配(more-like-this)。
  • omitNorms: Norms是normalization的缩写,lucene允许每个文档的每个字段都存储一个normalization factor,是和搜索时的相关性计算有关的一个系数。Norms的存储只占一个字节,但是每个文档的每个字段都会独立存储一份,且Norms数据会全部加载到内存。所以若开启了Norms,会消耗额外的存储空间和内存。但若关闭了Norms,则无法做index-time boosting(elasticsearch官方建议使用query-time boosting来替代)以及length normalization
  • indexOptions: Lucene提供倒排索引的5种可选参数(NONE、DOCS、DOCS_AND_FREQS、DOCS_AND_FREQS_AND_POSITIONS、DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS),用于选择该字段是否需要被索引,以及索引哪些内容。
  • docValuesType: DocValue是Lucene 4.0引入的一个正向索引(docid到field的一个列存),大大优化了sorting、faceting或aggregation的效率。DocValues是一个强schema的存储结构,开启DocValues的字段必须拥有严格一致的类型,目前Lucene只提供NUMERIC、BINARY、SORTED、SORTED_NUMERIC和SORTED_SET五种类型。
  • dimension:Lucene支持多维数据的索引,采取特殊的索引来优化对多维数据的查询,这类数据最典型的应用场景是地理位置索引,一般经纬度数据会采取这个索引方式。
 
来看下Lucene中对StringField的一个定义:
 
 
 
StringFiled有两种类型索引定义,TYPE_NOT_STORED和TYPE_STORED,唯一的区别是这个Field是否需要Store。从其他的几个属性也可以解读出,StringFiled选择omitNorms,需要进行倒排索引并且不需要被分词。

Elasticsearch数据类型

Elasticsearch内对用户输入文档内Field的索引,也是按照Lucene能提供的几种模式来提供。除了用户能自定义的Field,Elasticsearch还有自己预留的系统字段,用作一些特殊的目的。这些字段映射到Lucene本质上也是一个Field,与用户自定义的Field无任何区别,只不过Elasticsearch根据这些系统字段不同的使用目的,定制有不同的索引方式。
 
 
 
 
举个例子,上图​是Elasticsearch内两个系统字段_version和_uid的FieldType定义,我们来解读下它们的索引方式。Elasticsearch通过_uid字段唯一标识一个文档,通过_version字段来记录该文档当前的版本。从这两个字段的FieldType定义上可以看到,_uid字段会做倒排索引,不需要分词,需要被Store。而_version字段则不需要被倒排索引,也不需要被Store,但是需要被正排索引。很好理解,因为_uid需要被搜索,而_version不需要。但_version需要通过docId来查询,而且Elasticsearch内versionMap内需要通过docId做大量查询且只需要查询出_version字段,所以_version最合适的是被正排索引。
 
关于Elasticsearch内系统字段全面的解析,可以看下这篇文章

总结

这篇文章主要介绍了Lucene的一些基本概念以及提供的索引类型。后续我们会有一系列文章来解析Lucene提供的IndexWriter的写入流程,其In-Memory Buffer的结构以及持久化后的索引文件结构,来了解Lucene为何能达到如此高效的数据索引性能。也会去解析IndexSearcher的查询流程,以及一些特殊的查询优化的数据结构,来了解为何Lucene能提供如此高效的搜索和查询。

Lucene解析 - 基本概念的更多相关文章

  1. Lucene的基本概念----转载yufenfei的文章

    Lucene的基本概念 Lucene是什么? Lucene是一款高性能.可扩展的信息检索工具库.信息检索是指文档搜索.文档内信息搜索或者文档相关的元数据搜索等操作. 信息检索流程如下: 1. 将即将检 ...

  2. Lucene原理之概念

    概念: 数据分两种: 1.结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等. 2.非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等.(半结构化数据:如XML,HTML等, ...

  3. OAF_MDS系列1_OAF页面元数据结构MDS的解析(概念)

    2014-06-06 Created By BaoXinjian

  4. XML DOM解析 基础概念

    DOM和SAX W3C制定了一套书写XML分析器的标准接口规范——DOM. 除此以外,XML_DEV邮件列表中的成员根据应用的需求也自发地定义了一套对XML文档进行操作的接口规范——SAX. 这两种接 ...

  5. PLSQL_动态语句的解析(概念)

    2014-06-02 Created By BaoXinjian

  6. PLSQL_标准游标类型的解析(概念)

    2014-06-02 Created By BaoXinjian

  7. PLSQL_数据结构类型的解析(概念)

    2014-06-02 Created By BaoXinjian

  8. Ceph源码解析:概念

    Peering:一个PG内的所有副本通过PG日志来达成数据一致的过程.(某PG如果处于Peering将不能对外提供读写服务) Recovery:根据Peering的过程中产生的.依据PG日志推算出的不 ...

  9. RabbitMQ安装、集群搭建、概念解析

    RabbitMQ安装.集群搭建.概念解析 基本概念 为什么会产生MQ 1.解耦:采用异步方式实现业务需求达到解耦的目的. 2.缓冲流量,削峰填谷: 问:为什么会有流量冲击? 答:采用"直接调 ...

随机推荐

  1. [Android Pro] 开发一流Android SDK

    cp from : https://blog.csdn.net/dd864140130/article/details/53558011 本篇文章已授权微信公众号 guolin_blog (郭霖)独家 ...

  2. perf 高级命令简介

    perf 高级命令简介 1.使用 tracepoint 当 perf 根据 tick 时间点进行采样后,人们便能够得到内核代码中的 hot spot. 使用ls命令来演示 sys_enter 这个tr ...

  3. Springmvc的handler method参数绑定常用的注解

    转自:http://blog.longjiazuo.com/archives/1149   1. 简介: handler method参数绑定常用的注解,我们根据他们处理的Request的不同内容部分 ...

  4. 算法-强连通分量和Kosaraju算法

    有向图中,连通性比较好理解,如果两个顶点V和顶点W是可达的,可以称之为强连通的,即存在路径A→B,同时也存在一条有向路径B→A.从之前的有向环的判定过程中其实我们可以得到一个结论就是两个是强连通的当且 ...

  5. C语言头文件的使用(转载)

    C语言头文件的使用 ——by janders 转载请注名作者和出处,谢谢! C语言中的.h文件和我认识由来已久,其使用方法虽不十分复杂,但我却是经过了几个月的“不懂”时期,几年的“一知半解”时期才逐渐 ...

  6. 论文列表——text classification

    https://blog.csdn.net/BitCs_zt/article/details/82938086 列出自己阅读的text classification论文的列表,以后有时间再整理相应的笔 ...

  7. 深度学习哪家强?吴恩达、Udacity和Fast.ai的课程我们替你分析好了

    http://www.jianshu.com/p/28f5473c66a3 翻译 | AI科技大本营(rgznai100) 参与 | reason_W 引言 过去2年,我一直积极专注于深度学习领域.我 ...

  8. (文档)流媒体资源 Streaming Assets

    Most assets in Unity are combined into the project when it is built. However, it is sometimes useful ...

  9. Log4net PatternLayout 参数

    Log4net PatternLayout 参数 来自: https://logging.apache.org/log4net/log4net-1.2.13/release/sdk/log4net.L ...

  10. 给ajax表单提交数据前面加上实体名称

    有时候我们后台做了一个引用类型例如: 下面的实体以C#为例 public class Order{ public string orderId{get;set;} public OrderItem o ...