Lucene

Lucene

lucene是什么

  • Lucene在维基百科的定义

    Lucene是一套用于全文检索和搜索的开放源代码程序库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程序接口,能够做全文索引和搜索,在java开发环境里Lucene是一个成熟的免费开放源代码工具;就其本身而论,Lucene是最近几年最受欢迎的java信息检索程序库

  • but,Lucene不提供爬虫功能,需要自建爬虫应用。Lucene只做索引和搜索工作。

  • Lucene和Solr

    • Lucene是一个程序化库,你不呢个原样使用,而Solr是一个完整的应用程序,您可以立即使用它。

全文检索是什么

全文检索在百度百科中的定义

全文数据库是全文检索系统的主要构成部分。所谓全文数据库是将一个完整的信息源的全部内容转化为计算机可以识别、处理的信息单元而形成的数据集合。全文数据库不仅存储了信息,而且还有对全文数据进行词、字、段落等更深层次的编辑、加工的功能,而且所有全文数据库无一不是海量信息数据库。

  • 全文检索首先将要查询的目标文档中的词提取出来,组成索引,通过查询索引达到搜索目标文档的目的。这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)
  • 全文检索(Full-Text Retrieval)是指以文本作为检索对象,找出含有指定词汇的文本。
  • 全面、准确和快速是衡量全文检索系统的关键指标
  • 关于全文检索,我们要知道:
    • 只处理文本
    • 不处理语义
    • 搜索时英文不区分大小写
    • 结果列表有相关度排序。(查出的结果如果没有相关度排序,那么系统不知道我想要的结果在哪一页。我们在使用百度搜索时,一般不需要翻页,为什么?因为百度做了相关度排序:为每一条结果打一个分数,这条结果越符合搜索条件,得分就越高,叫做相关度得分,结果列表会按照这个分数由高到低排列,所以第1页的结果就是我们最想要的结果。) 在信息检索工具中,全文检索是最具通用性和实用性的。
  • 全文检索和数据库搜索的区别、
    • 简单来说,这两者解决的问题不一样。数据库搜索在匹配效果、速度、效率等方面都逊色于全文检索。

Lucene实现全文检索流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TXikm81i-1572599722136)(…\images\lucene-1.png)]

  • 全文检索的流程分为两大部分:索引流程、搜索流程
    • 索引流程:即采集数据构建文档对象分析文档(分词)创建索引
    • 搜索流程:即用户通过搜索界面创建查询执行搜索,搜索器从索引库搜索渲染搜索结果

Lucene数据模型

  • Lucene中包含了四种基本数据类型
    • Index:索引,由很多的Document组成
    • Document:由很多的Field组成,是Index和Search的最小单位
    • Field:由很多的Term组成,包括Field Name和Field Value
    • Term:由很多的字节组成。一般将Text类型的Field Value分词之后的每个最小单元叫做Term
  • 在Lucene中,读写路径是分离的。写入的时候创建一个IndexWriter,而读的时候会创建一个IndexSearcher

IndexWriter

// initialization
Directory index = new NIOFSDirectory(Paths.get("/index"));
IndexWriterConfig config = new IndexWriterConfig();
IndexWriter writer = new IndexWriter(index, config);
// create a document
Document doc = new Document();
doc.add(new TextField("title", "Lucene - IndexWriter", Field.Store.YES));
doc.add(new StringField("content", "测试 ", Field.Store.YES));
// index the document
writer.addDocument(doc);
writer.commit();
  • 上述代码总共分为三个步骤:
    • 初始化:初始化 IndexWriter 必要的两个元素是 Directory 和 IndexWriterConfig,Directory 是 Lucene 中数据持久层的抽象接口,通过这层接口可以实现很多不同类型的数据持久层,例如本地文件系统、网络文件系统、数据库或者是分布式文件系统。IndexWriterConfig 内提供了很多可配置的高级参数,提供给高级玩家进行性能调优和功能定制,它提供的几个关键参数后面会细说。
    • 构造文档:Lucene中文档由Document表示,Document由Field构成。Lucene提供多种不同类型的Field,其FieldType决定了它所支持的索引模式,当然也支持自定义Field。
    • 写入文档:通过 IndexWriter 的 addDocument 函数写入文档,写入时同时根据 FieldType 创建不同的索引。文档写入完成后,还不可被搜索,最后需要调用 IndexWriter 的 commit,在 commit 完后 Lucene 才保证文档被持久化并且是 searchable 的。

IndexWriterConfig

  • 列举几个主要参数
    • IndexDeletionPolicy:Lucene 开放对 commit point 的管理,通过对 commit point 的管理可以实现例如 snapshot 等功能。Lucene 默认配置的 DeletionPolicy,只会保留最新的一个 commit point。
    • Similarity:搜索的核心是相关性,Similarity是相关性算法的抽象接口,Lucene默认实现了TF-IDF和BM25算法。相关性计算在数据写入和搜索时都会发生,数据写入时的相关性计算称为Index-time boosting,计算Normalization并写入索引,搜索时的相关性计算称为query-time boosting
    • MergePolicy:Lucene 内部数据写入会产生很多 Segment,查询时会对多个 Segment 查询并合并结果。所以 Segment 的数量一定程度上会影响查询的效率,所以需要对 Segment 进行合并,合并的过程就称为 Merge,而何时触发 Merge 由 MergePolicy 决定。
    • MergeScheduler:当 MergePolicy 触发 Merge 后,执行 Merge 会由 MergeScheduler 来管理。Merge 通常是比较耗 CPU 和 IO 的过程,MergeScheduler 提供了对 Merge 过程定制管理的能力。
    • Codec:Codec可以说是Lucene中最核心的部分,定义了Lucene内部所有类型索引的Encoder和Decoder。Lucene在Config这一层将Codec配置化,主要目的是提供对不同版本数据的处理能力。对于Lucene 用户来说,这一层的定制需求通常较少,能玩 Codec 的通常都是顶级玩家了。
    • IndexerThreadPool:管理 IndexWriter 内部索引线程(DocumentsWriterPerThread)池,这也是 Lucene 内部定制资源管理的一部分。
    • FlushPolicy:FlushPolicy决定了In-memeroy buffer合适被flush,默认的实现会根据 RAM 大小和文档个数来判断 Flush 的时机,FlushPolicy 会在每次文档 add/update/delete 时调用判定。
    • MaxBufferedDoc:Lucene 提供的默认 FlushPolicy 的实现 FlushByRamOrCountsPolicy 中允许 DocumentsWriterPerThread 使用的最大文档数上限,超过则触发 Flush。
    • RAMBufferSizeMB:Lucene 提供的默认 FlushPolicy 的实现 FlushByRamOrCountsPolicy 中允许 DocumentsWriterPerThread 使用的最大内存上限,超过则触发 flush。
    • RAMPerThreadHardLimitMB:除了 FlushPolicy 能决定 Flush 外,Lucene 还会有一个指标强制限制 DocumentsWriterPerThread 占用的内存大小,当超过阈值则强制 flush。
    • Analyzer:即分词器,这个通常是定制化最多的,特别是针对不同的语言。

核心操作

  • IndexWrite提供的核心API如下:
    • addDocument:比较纯粹的一个 API,就是向 Lucene 内新增一个文档。Lucene 内部没有主键索引,所有新增文档都会被认为一个新的文档,分配一个独立的 docId。
    • updateDocuments:更新文档,但是和数据库的更新不太一样。数据库的更新是查询后更新,Lucene的更新是查询后删除再新增。流程是先 delete by term,后 add document。但是这个流程又和直接先调用 delete 后调用 add 效果不一样,只有 update 能够保证在 Thread 内部删除和新增保证原子性,具体可看后面详述。
    • deleteDocument:删除文档,支持两种类型删除,by term 和 by query。在 IndexWriter 内部这两种删除的流程不太一样,具体可看后面详述。
    • flush:触发强制 flush,将所有 Thread 的 In-memory buffer flush 成 segment 文件,这个动作可以清理内存,强制对数据做持久化。
    • prepareCommit/commit/rollback:commit 后数据才可被搜索,commit 是一个二阶段操作,prepareCommit 是二阶段操作的第一个阶段,也可以通过调用 commit 一步完成,rollback 提供了回滚到 last commit 的操作。
    • maybeMerge/forceMerge:maybeMerge 触发一次 MergePolicy 的判定,而 forceMerge 则触发一次强制 merge。

数据路径

  • 下面是IndexWriter内部核心流程架构图,接下来我们将以add/update/delete/commit这些主要操作来讲解IndexWriter内部的数据路径

在这里插入图片描述

并发模型
  • IndexWriter提供的核心接口都是线程安全的,并且内部做了特殊的并发优化来优化多线程写入的性能。IndexWriter内部为每个线程都会单独开辟一个空间来写入,这块空间由 DocumentsWriterPerThread 来控制。整个多线程数据处理流程为:
    • 多线程并发调用IndexWriter的写接口,在IndexWriter内部具体请求会由DocumentsWriter来执行。DocumentsWriter内部在处理请求之前,会先根据当前执行操作的Thread来分配DocumentsWriterPerThread
    • 每个线程在其独立的DocumentsWriterPerThread空间内部进行数据处理,包括分词、相关性计算、索引构建等
    • 数据处理完毕后,在DocumentsWriter层面执行一些后续操作,例如触发FlushPolicy的判定等
  • 引入了DocumentWriterPerThread(后续简称为DWPT)后,Lucene内部在处理数据时,整个处理步骤只需要对以上第一步和第三步进行进行加锁,第二步完全不用加锁,每个线程都在自己独立的空间内处理数据。而通常来说,第一步和第三步都是非常轻量级的,而第二步是对计算和内存资源消耗最大的。所以这样做之后,能够将加锁的时间大大缩短,提高并发的效率。每个DWPT内单独包含一个In-memory buffer,这个buffer最终会flush成不同的独立的segment文件
  • 这种方案下,对多线程并发写入性能有很大的提升。特别是针对纯新增文档的场景,所有数据写入都不会有冲突,所以非常适合这种空间隔离式的数据写入方式。但对于删除文档的场景,一次删除动作可能会涉及删除不同线程空间内的数据,这里Lucene也采取了一种特殊的交互方式来降低锁的开销,在解析delete操作时会具体介绍。
  • 在搜索场景中,全量构建索引的阶段,基本是纯新增文档式的写入,而在后续增量索引阶段(特别是数据源是数据库时),会涉及大量的update和delete操作。从原理上来分析,一个最佳实践是包含相同唯一主键Term的文档分配相同的线程来处理,使数据更新发生在一个独立线程空间内,避免跨线程
add&update
  • add接口用于新增文档,update接口用于更新文档。但Lucene的update和数据的update不太一样。数据库的更新是查询后更新,Lucene的更新是查询后删除再新增,不支持更新文档内部分列。流程是先delete by trem,后add document

  • IndexWriter提供的add和update接口,都会映射到DocumentsWriter的update接口,如下面的接口定义:

    long updateDocument(final Iterable<? extends IndexableField> doc, final Analyzer analyzer,
        final Term delTerm) throws IOException, AbortingException 
    
    • 这个函数内的处理流程是:
      • 根据Thread分配DWPT
      • 在DWPT内执行delete
      • 在DWPT内执行add
    • add操作会直接将文档写入DWPT内的In-memory buffer
delete
  • delete相对add和update来说,是完全不同的一个数据路径。而且update和delete虽然内部都会执行数据删除,但这两者又是不同的数据路径。文档删除不会直接影响In-memory buffer内的数据,而是会有另外的方式来达到删除的目的。

在这里插入图片描述

  • 在Delete路径上关键的数据结构就是Deletion queue,在IndexWriter内部会一个全局的Deletion Queue,称为Global Deletion Queue,而在每个DWPT内部,还会有一个独立的Deletion Queue,称为Pending updates。DWPT Pending Updates 会与 Global Deletion Queue 进行双向同步,因为文档删除是全局范围的,不应该只发生在 DWPT 范围内。
  • Pending Updates 内部会按发生顺序记录每个删除动作,并且标记该删除影响的文档范围,文档影响范围通过记录当前已写入的最大 DocId(DocId Upto)来标记,即代表这个删除动作只删除小于等于该 DocId 的文档。
  • update接口和 delete 接口都可以进行文档删除,但是有一些差异:
    • update 只能进行 by term 的文档删除,而 delete 除了 by term,还支持 by query。
    • update 的删除会先作用于 DWPT 内部,后作用于 Global,再由 Global 同步到其他 DWPT。
    • delete 的删除会作用在 Global 级别,后异步同步到 DWPT 级别。
  • update 和 delete 流程上的差异也决定了他们行为上的一些差异,update 的删除操作会先发生在 DWPT 内部,并且是和 add 同时发生,所以能够保证该 DWPT 内部的 delete 和 add 的原子性,即保证在 add 之前的所有符合条件的文档一定被删除。
  • DWPT Pending Updates 里的删除操作什么时候会真正作用于数据呢?在 Lucene Segment 内部,数据实际上并不会被真正删除。Segment 中有一个特殊的文件叫 live docs,内部是一个位图的数据结构,记录了这个 Segment 内部哪些 DocId 是存活的,哪些 DocId 是被删除的。所以删除的过程就是构建 live docs 标记位图的过程,数据实际上不会被真正删除,只是在 live docs 里会被标记删除。Term 删除和 Query 删除会在不同阶段构建 live docs,Term 删除要求先根据 Term 查询出它关联的所有 doc,所以很明显这个会发生在倒排索引构建时。而 Query 删除要求执行一次完整的查询后才能拿到其对应的 docId,所以会发生在 segment 被 flush 完成后,基于 flush 后的索引文件构建 IndexReader 后执行搜索才能完成。
  • 还有一点要注意的是,live docs 只影响倒排,所以在 live docs 里被标记删除的文档没有办法通过倒排索引检索出,但是还能够通过 doc id 查询到 store fields。当然文档数据最终是会被真正物理删除,这个过程会发生在 merge 时。
flush
  • flush 是将 DWPT 内 In-memory buffer 里的数据持久化到文件的过程,flush 会在每次新增文档后由 FlushPolicy 判定自动触发,也可以通过 IndexWriter 的 flush 接口手动触发。
  • 每个 DWPT 会 flush 成一个 segment 文件,flush 完成后这个 segment 文件是不可被搜索的,只有在 commit 之后,所有 commit 之前 flush 的文件才可被搜索。
commit
  • commit会触发数据的一次强制flush,commit完成后在此之前flush的数据才可被搜索。commit动作会触发生成一个commit point,commit point是一个文件。Commit point会由IndexDeletionPolicy管理,lucene默认配置的策略之会保留last commit point,当然lucene提供其他多种不同的策略供选择。
merge
  • merge是对segment文件合并的动作,合并的好处是能够提高查询的效率以及回收一些被删除的文档。Merge会在segment文件flush时触发MergePolicy来判定自动触发,也可通过IndexWriter进行一次force merge
IndexingChain
  • 前面主要介绍了IndexWriter内部各个关键操作的流程,这里会接受最核心的DWPT内部对文档进行索引构建的流程。Lucene内部索引构建最关键的概念是IndexingChain,顾名思义,链式的索引构建。为啥是链式的?这个和Lucene的整个索引体系结构有关系,Lucene提供了各种不同类型的索引类型,例如倒排、正排(列存)、StoreField、DocValues等。每个不同的索引类型对应不同的索引算法、数据结构以及文件存储,有些是列级别的,有些是文档基本的。所以一个文档写入后,需要被这么多种不同索引处理,有些索引会共享memory-buffer,有些则是完全独立的。基于这个架构,理论上Lucene是提供了扩展其他类型索引的可能性,顶级玩家也可以去尝试。

在这里插入图片描述

  • 在IndexWriter内部,indexing chain上索引构建顺序是invert index、store fields、doc values和point values。有些索引类型处理文档后会将索引内容直接写入文件(主要是store field 和term vector),而有些索引类型会先将文档内容写入memory buffer,最后在flush的时候再写入文件。能直接写入文件的索引,通常是文档级的索引,索引构建可以文档级的增量构建。而不能写入文件的索引,例如倒排,则必须等Segment内所有文档全部写入完毕后,会先对Term进行一个全排序,之后才能构建索引,所以必须要有一个memory-buffer先缓存所有文档。
  • 前面提到,IndexWriterConfig支持配置Codec,Codec就是对于每种类型索引的Encoder和Decoder。目前主要有这么几种Codec:
    • BlockTreeTermsWriter:倒排索引对应的 Codec,其中倒排表部分使用 Lucene50PostingsWriter(Block 方式写入倒排链) 和 Lucene50SkipWriter(对 Block 的 SkipList 索引),词典部分则是使用 FST(针对倒排表 Block 级的词典索引)。
    • CompressTermVectorsWriter:对应 Term vector 索引的 Writer,底层是压缩 Block 格式。
    • CompressingStoredFieldsWriter:对应 Store fields 索引的 Writer,底层是压缩 Block 格式。
    • Lucene70DocValuesConsumer:对应 Doc values 索引的 Writer。
    • Lucene60PointsWriter:对应 Point values 索引的 Writer。
  • 这里主要了解IndexingChain内部的文档索引处理流程,核心是链式分阶段的索引,并且不同索引类型支持Codec可配置

Lucene查询过程

  • 在 lucene 中查询是基于 segment。每个 segment 可以看做是一个独立的 subindex,在建立索引的过程中,lucene 会不断的 flush 内存中的数据持久化形成新的 segment。多个 segment 也会不断的被 merge 成一个大的 segment,在老的 segment 还有查询在读取的时候,不会被删除,没有被读取且被 merge 的 segement 会被删除。这个过程类似于 LSM 数据库的 merge 过程。下面我们主要看在一个 segment 内部如何实现高效的查询。
  • 为了方便大家理解,我们以人名字,年龄,学号为例,如何实现查某个名字(有重名)的列表。

在这里插入图片描述

  • 在 lucene 中为了查询 name=XXX 的这样一个条件,会建立基于 name 的倒排链。以上面的数据为例,倒排链如下:

在这里插入图片描述

  • 如果我们还希望按照年龄查询,例如想查年龄 =18 的列表,我们还可以建立另一个倒排链:

在这里插入图片描述

  • 在这里,Alice,Alan,18,这些都是term。所以倒排本质婚丧就是基于term的反向列表,方便进行属性查找。那么如果term非常多,如何快速拿到这个倒排链呢?在lucene里面就引入了term dictionary的概念,也就是term的自动。term字典里我们可以按照term进行排序,那么用一个二分查找就可以定位这个term的地址。这样的复杂度是 logN,在 term 很多,内存放不下的时候,效率还是需要进一步提升。可以用一个hashmap,当有一个term进入,hash继续查找倒排连。这里hashmap的方式可以看作是term dictionary 的一个index。从lucene4开始,为了方便实现rangequery或者前缀,后缀等复杂的查询语句,lucene使用FST数据结构来存储term自动,下面就详细介绍下FST的存储结构。
FST
  • 我们就用 Alice 和 Alan 这两个单词为例,来看下 FST 的构造过程。首先对所有的单词做一下排序为“Alice”,“Alan”。
  • 1.插入Alan

在这里插入图片描述

  • 2.插入Alice

在这里插入图片描述

  • 这样你就得到了一个有向无环图,有这样一个数据结构,就可以很快查找某个人名是否存在。FST在单term查询上可能相比hashmap并没有明显优势,甚至会慢一些。但是在范围,前缀搜索以及压缩了
  • 在通过FST定位到倒排链后,有一件事情需要做,就是倒排链的合并。因为查询条件可能不止一个,例如上面我们想找 name=“alan” and age=“18” 的列表。lucene 是如何实现倒排链的合并呢。这里就需要看一下倒排链存储的数据结构
SkipList
  • 为了能够快速查找docid,lucene采用了SkipList这一数据结构。SkipList有以下几个特征:

    • 元素排序的,对应到我们的倒排链,lucene是按照docid进行排序,从小到大
    • 跳跃有一个固定的间隔,这个是需要建立SkipList的时候制定和,如下图,间隔是3
    • SkipList的层次,这个是指整个SkipList有几层

    在这里插入图片描述

  • 有了这个 SkipList 以后比如我们要查找 docid=12,原来可能需要一个个扫原始链表,1,2,3,5,7,8,10,12。有了 SkipList 以后先访问第一层看到是然后大于 12,进入第 0 层走到 3,8,发现 15 大于 12,然后进入原链表的 8 继续向下经过 10 和 12。

  • 有了 FST 和 SkipList 的介绍以后,我们大体上可以画一个下面的图来说明 lucene 是如何实现整个倒排结构的:

在这里插入图片描述

  • 有了这张图,我们可以理解为什么基于 lucene 可以快速进行倒排链的查找和 docid 查找,下面就来看一下有了这些后如何进行倒排链合并返回最后的结果。
倒排合并
  • 假如我们的查询条件是 name = “Alice”,那么按照之前的介绍,首先在 term 字典中定位是否存在这个 term,如果存在的话进入这个 term 的倒排链,并根据参数设定返回分页返回结果即可。这类查询,在数据库中使用二级索引也是可以满足,那 lucene 的优势在哪呢。假如我们有多个条件,例如我们需要按名字或者年龄单独查询,也需要进行组合 name = “Alice” and age = “18” 的查询,那么使用传统二级索引方案,你可能需要建立两张索引表,然后分别查询结果后进行合并,这样如果 age = 18 的结果过多的话,查询合并会很耗时。那么在 lucene 这两个倒排链是怎么合并呢。

  • 假如我们有下面三个倒排链需要进行合并。
    在这里插入图片描述

  • 在lucene中会采用下列顺序进行合并:

    • 在 termA 开始遍历,得到第一个元素 docId=1
    • Set currentDocId=1
    • 在 termB 中 search(currentDocId) = 1 (返回大于等于 currentDocId 的一个 doc),
      • 因为 currentDocId ==1,继续
      • 如果 currentDocId 和返回的不相等,执行 2,然后继续
    • 到 termC 后依然符合,返回结果
    • currentDocId = termC 的 nextItem
    • 然后继续步骤 3 依次循环。直到某个倒排链到末尾。
  • 整个合并步骤我可以发现,如果某个链很短,会大幅减少比对次数,并且由于 SkipList 结构的存在,在某个倒排中定位某个 docid 的速度会比较快不需要一个个遍历。可以很快的返回最终的结果。从倒排的定位,查询,合并整个流程组成了 lucene 的查询过程,和传统数据库的索引相比,lucene 合并过程中的优化减少了读取数据的 IO,倒排合并的灵活性也解决了传统索引较难支持多条件查询的问题。

BKDTree
  • 在 lucene 中如果想做范围查找,根据上面的 FST 模型可以看出来,需要遍历 FST 找到包含这个 range 的一个点然后进入对应的倒排链,然后进行求并集操作。但是如果是数值类型,比如是浮点数,那么潜在的 term 可能会非常多,这样查询起来效率会很低。所以为了支持高效的数值类或者多维度查询,lucene 引入类 BKDTree。BKDTree 是基于 KDTree,对数据进行按照维度划分建立一棵二叉树确保树两边节点数目平衡。在一维的场景下,KDTree 就会退化成一个二叉搜索树,在二叉搜索树中如果我们想查找一个区间,logN 的复杂度就会访问到叶子结点得到对应的倒排链。如下图所示:

在这里插入图片描述

  • 如果是多维,kdtree 的建立流程会发生一些变化。
  • 比如我们以二维为例,建立过程如下:
    • 确定切分维度,这里维度的选取顺序是数据在这个维度方法最大的维度优先。一个直接的理解就是,数据分散越开的维度,我们优先切分。
    • 切分点的选这个维度最中间的点。
    • 递归进行步骤 1,2,我们可以设置一个阈值,点的数目少于多少后就不再切分,直到所有的点都切分好停止。
  • 下图是一个例子

在这里插入图片描述

  • BKDTree 是 KDTree 的变种,因为可以看出来,KDTree 如果有新的节点加入,或者节点修改起来,消耗还是比较大。类似于 LSM 的 merge 思路,BKD 也是多个 KDTREE,然后持续 merge 最终合并成一个。不过我们可以看到如果你某个 term 类型使用了 BKDTree 的索引类型,那么在和普通倒排链 merge 的时候就没那么高效了所以这里要做一个平衡,一种思路是把另一类 term 也作为一个维度加入 BKDTree 索引中。
如何实现返回结果进行排序聚合
  • 通过之前介绍可以看出 lucene 通过倒排的存储模型实现 term 的搜索,那对于有时候我们需要拿到另一个属性的值进行聚合,或者希望返回结果按照另一个属性进行排序。在 lucene4 之前需要把结果全部拿到再读取原文进行排序,这样效率较低,还比较占用内存,为了加速 lucene 实现了 fieldcache,把读过的 field 放进内存中。这样可以减少重复的 IO,但是也会带来新的问题,就是占用较多内存。新版本的 lucene 中引入了 DocValues,DocValues 是一个基于 docid 的列式存储。当我们拿到一系列的 docid 后,进行排序就可以使用这个列式存储,结合一个堆排序进行。当然额外的列式存储会占用额外的空间,lucene 在建索引的时候可以自行选择是否需要 DocValue 存储和哪些字段需要存储。
Lucene的代码目录结构
  • analysis 模块主要负责词法分析及语言处理而形成 Term。
  • codecs 模块主要负责之前提到的一些数据结构的实现,和一些编码压缩算法。包括 skiplist,docvalue 等。
  • document 模块主要包括了 lucene 各类数据类型的定义实现。
  • index 模块主要负责索引的创建,里面有 IndexWriter。
  • store 模块主要负责索引的读写。
  • search 模块主要负责对索引的搜索。
  • geo 模块主要为 geo 查询相关的类实现
  • util 模块是 bkd,fst 等数据结构实现。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值