Lucene 解析 - 基本概念
|
前言Apache Lucene 是一个开源的高性能、可扩展的信息检索引擎,提供了强大的数据检索能力。Lucene 已经发展了很多年,其功能越来越强大,架构也越来越精细。它目前不仅仅能支持全文索引,也能够提供多种其他类型的索引方式,来满足不同类型的查询需求。 基于 Lucene 的开源项目有很多,最知名的要属 Elasticsearch 和 Solr,如果说 Elasticsearch 和 Solr 是一辆设计精美、性能卓越的跑车,那 Lucene 就是为其提供强大动力的引擎。为了驾驭这辆跑车让它跑的更快更稳定,我们需要对它的引擎研究透彻。 在此之前我们在专栏已经发表了多篇文章来剖析 Elasticsearch 的数据模型、读写路径、分布式架构以及 Data/Meta 一致性等问题,这篇文章之后我们会陆续发表一系列的关于 Lucene 的原理和源码解读,来全面解析 Lucene 的数据模型和数据读写路径。
Lucene 官方对自己的优势总结为几点: 整个分析会基于 Lucene 7.2.1 版本,在读这篇文章之前,需要有一定的知识基础,例如了解基本的搜索和索引原理,知道什么是倒排、分词、相关性等基本概念,了解 Lucene 的基本使用,例如 Directory、IndexWriter、IndexSearcher 等。 基本概念在深入解读 Lucene 之前,先了解下 Lucene 的几个基本概念,以及这几个概念背后隐藏的一些东西。
如图是一个 Index 内的基本组成,Segment 内数据只是一个抽象表示,不代表其内部真实数据结构。
Index(索引)
Document(文档)
Field(字段)
Term 和 Term Dictionary
Segment 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 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 是字段类型,确定了该字段被索引的方式。 来看下 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 能提供如此高效的搜索和查询。 |
时间:2018-10-12 21:29 来源: 转发量:次
声明:本站部分作品是由网友自主投稿和发布、编辑整理上传,对此类作品本站仅提供交流平台,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,不为其版权负责。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。