Lucene底层原理和优化经验分享

Posted Java这点事

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lucene底层原理和优化经验分享相关的知识,希望对你有一定的参考价值。

Lucene底层原理

1 索引原理

全文索引由来已久,绝大多数都基于倒排索引来做,顾名思义,它与常规的一篇文章包含哪些词相反,它是一个词记录到那些文档中,由两部分组成--词典和倒排表。
其中词典结构尤为重要,有很多种词典结构,各有各的优缺点,最简单如排序数组,通过二分查找来检索数据,更快的有哈希表,磁盘查找有B树、B+树,但一个能支持TB级数据的倒排索引结构需要在时间和空间上有个平衡,下图列了一些常见词典的优缺点:

dictionary.png


其中可用的有:B+树、跳跃表、FST。
B+树:

Lucene底层原理和优化经验分享

mysqlB.png


理论基础:平衡多路查找树 优点:外存索引、可更新 缺点:空间大、速度不够快

跳跃表:


Lucene底层原理和优化经验分享


说明:假设我们现在要将mop, moth, pop, star, stop and top(term index里的term前缀)映射到序号:012345(term dictionary的block位置)。


FST Lucene现使用的索引结构:


Lucene底层原理和优化经验分享

FST.png

优点:结构简单、跳跃间隔、级数可控,Lucene3.0之前使用的也是跳跃表结构,后换成了FST,但跳跃表在Lucene其他地方还有应用如倒排表合并和文档号索引。 缺点:模糊查询支持不好


2 Lucene索引原理

Lucene(4.10.3)的一个索引文件结构如下图所示,基本可以分为三个部分:词典、倒排表、正向文件、列式存储DocValues。

Lucene底层原理和优化经验分享

luceneStyle.png


2.1 索引结构

Lucene现在采用的数据结构为FST,它的特点就是:

  • 词查找复杂度为O(len(str))

  • 共享前缀、节省空间

  • 内存存放前缀索引、磁盘存放后缀词块

往索引库里插入四个单词abd、abe、acf、acg,看看它的索引文件内容。


Lucene底层原理和优化经验分享

LuceneContent.png

tip部分,每列一个FST索引,所以会有多个FST,每个FST存放前缀和后缀块指针,这里前缀就为a、ab、ac。tim里面存放后缀块和词的其他信息如倒排表指针、TFDF等,doc文件里就为每个单词的倒排表。
所以它的检索过程分为三个步骤:

  • 内存加载tip文件,通过FST匹配前缀找到后缀词块位置。

  • 根据词块位置,读取磁盘中tim文件中后缀块并找到后缀和相应的倒排表位置信息。

  • 根据倒排表位置去doc文件中加载倒排表。
    这里就会有两个问题,第一就是前缀如何计算,第二就是后缀如何写磁盘并通过FST定位,下面将描述下Lucene构建FST过程:
    已知FST要求输入有序,所以Lucene会将解析出来的文档单词预先排序(这里在数据不停进入时,实时排序肯定是不行的,应该是多个文件定期合并,这部分后续再深入研究),然后构建FST,我们假设输入abd、abe、acf、acg,那么整个构建过程如下:

    Lucene底层原理和优化经验分享


  • 1. 插入abd时,没有输出。 2. 插入abe时,计算出前缀ab,但此时不知道后续还不会有其他以ab为前缀的词,所以此时无输出。 3. 插入acf时,因为是有序的,知道不会再有ab前缀的词了,这时就可以写tip和tim了,tim中写入后缀词块d、e和它们的倒排表位置ip_d,ip_e,tip中写入a,b和以ab为前缀的后缀词块位置(真实情况下会写入更多信息如词频等)。 4. 插入acg时,计算出和acf共享前缀ac,这时输入已经结束,所有数据写入磁盘。tim中写入后缀词块f、g和相对应的倒排表位置,tip中写入c和以ac为前缀的后缀词块位置。



2.2 倒排表结构

倒排表就是文档号集合,但怎么存,怎么取也有很多讲究,Lucene现使用的倒排表结构叫Frame of reference,它主要有两个特点:

  • 数据压缩,如下图所示,将6个数字从原先的24bytes压缩到7bytes。


    Lucene底层原理和优化经验分享

    compress.png

  • 跳跃表加速合并,因为布尔查询时,and 和or 操作都需要合并倒排表,这时就需要快速定位相同文档号。

利用跳表(Skip list)的数据结构快速做“与”运算 利用上面提到的bitset按位“与”

假设有下面三个posting list需要联合索引:

Lucene底层原理和优化经验分享

postinglist.png

如果使用跳表,对最短的posting 里身体中每个id,逐个在另外两个posting list查找是否存在,最后得到交集的结果。
如果使用bitset,就很直观了,直接按位与,得到的结果就是最后的交集。

2.3 正向文件

正向文件指的就是原始文档,Lucene对原始文档也提供了存储功能,它存储特点就是分块+压缩,fdt文件就是存放原始文档的文件,它占了索引库90%的磁盘空间,fdx文件为索引文件,通过文档号(自增数字)快速得到文档位置,他们的文件格式如下:

nomalIndex.png


fnm中为元信息存放了各列类型、列名、存储方式等信息。
fdt为文档值,里面一个chunk就是一个块,Lucene索引文档时,先缓存文档,缓存大于16KB时,就会把文档压缩存储。一个chunk包含了该chunk起始文档、多少个文档、压缩后的文档内容。
fdx为文档号索引,倒排表存放的是文档号,通过fdx才能快速定位到文档位置即chunk位置,它的索引结构比较简单,就是跳跃表结构,首先它会把1024个chunk归为一个block,每个block记载了起始文档值,block就相当于一级跳表。
所以查找文档,就分为三步:
第一步二分查找block,定位属于哪个block。
第二步就是根据从block里面每个chunk的起始文档号,找到属于哪个chunk和chunk位置。
第三步就是去加载fdt的chunk,找到文档。


这里还有一个细节就是存放chunk起始文档值和chunk位置不是简单的数组,而是采用了平均值压缩法。所以第Nchunk的起始文档值由 DocBase + AvgChunkDocs * n + DocBaseDeltas[n]恢复而来,而第Nchunkfdt中的位置由 StartPointerBase + AvgChunkSize * n + StartPointerDeltas[n]恢复而来。



从上面分析可以看出,lucene对原始文件的存放是行是存储,并且为了提高空间利用率,是多文档一起压缩,因此取文档时需要读入和解压额外文档,因此取文档过程非常依赖随机IO,以及lucene虽然提供了取特定列,但从存储结构可以看出,并不会减少取文档时间。

2.4 DocValues

我们知道倒排索引能够解决从词到文档的快速映射,但当我们需要对检索结果进行分类、排序、数学计算等聚合操作时需要文档号到值的快速映射,而原先不管是倒排索引还是行式存储的文档都无法满足要求。由此引入DocValues,其实现原理是在建索引的过程中,在设置了DocValues类型的数据直接以正排信息存在到RAM or Disk上,后期基于映射文件方式来读取,脱离JVM堆内存。

Reference

“见识、目标、认清差距、努力+正确的方法”对人生成长无比重要,希望你通过此文有所启发、收获!

如何一起学习,有没有免费资料?






以上是关于Lucene底层原理和优化经验分享的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot部署JAR文件瘦身优化经验分享

Lucene底层储存结构优化和相关度排序

原理+索引+底层+分布式+优化,分享PDF高清版

.NET面试题系列(十三)Lucene底层原理

微信小程序代码片段

lucene底层数据结构——底层filter bitset原理,时间序列数据压缩将同一时间数据压缩为一行