当前位置: 首页 > 后端技术 > Java

Lucene基础知识

时间:2023-04-01 21:59:09 Java

Lucene采用基于倒排列表的设计原理,可以非常高效的实现文本搜索。它在底层采用了分段存储的方式,从而几乎完全避免了读写时锁的发生,大大提高了读写性能。核心模块lucene的写入流程和读取流程如图所示。其中,虚线箭头(a、b、c、d)表示编写索引的主要过程,实线箭头(1-9)表示查询的主要过程。lucene的主要模块(可以结合上图)分析模块:主要负责词法分析和语言处理,也就是常说的分词,通过它可以最终形成存储或搜索的最小单位,Term.索引模块:主要负责索引的创建。store模块:主要负责索引的读写,主要是对文件的一些操作,其主要目的是抽象出与平台文件系统无关的存储。queryParser:主要负责语法分析,将我们的查询语句生成成Lucene底层可以识别的条件。搜索模块:主要负责索引的搜索相似度模块:主要负责差异打分和排序的实现分词后的词。词典(TermDictionary):是Term的集合。字典的数据结构有很多,比如排序数组通过二分查找检索数据;HashMap比排序数据速度快,但占用空间大;fst(finite-statetransducer有限状态转换器)具有更高的数据压缩率和查询效率。fst是默认的数据结构。附上lucene中的数据结构介绍:elasticsearch中的数据结构倒排表:一篇文章通常由多个单词组成,倒排表记录了某个单词出现在哪些文章中。正向信息:原始文档信息,可以是用于排序、聚合和展示等。段:索引中最小的独立存储单元。一个索引文件由一个或多个段组成。Lucene中的段是不可变的。段一旦产生,只能对其进行读操作,不能对其进行写操作。Lucene的底层存储格式如下图所示。里面的字典就是Term集合。字典中Term指向的文档链表的集合称为倒排表。字典和发帖列表是快速检索的重要基础。它们分为两部分存储。在发帖列表中,不仅存储了文献编号,还存储了词频等信息。检索方式在Lucene的查询过程中,主要有以下四种检索方式。单词查询是指查询一个Term。例如,要查找包含字符串'lucene'的文档,只需要在字典中查找Term'lucene',然后在发帖列表中获取对应的文档链表即可。AND批次与多个集合相交。例如,搜索同时包含字符串'lucene'和字符串'solr'的文档,搜索步骤如下:1)在字典中查找Term'lucene',得到'对应的文档链表卢塞恩'。2)在字典中找到Term'solr',得到'solr'对应的文档列表。3)合并链表,对两个文档链表进行交集操作。OR指的是多个集合的并集。例如,如果要查找包含字符串“lucene”或字符串“solr”的文档,搜索步骤如下。1)在字典中找到Term“lucene”,得到“lucene”对应的文档列表。2)在字典中找到Term“solr”,得到“solr”对应的文档列表。3)合并链表,对两个文档链表进行并集操作,合并结果包含“lucene”或“solr”。NOT是指取多个集合的差值。例如,如果要查找包含字符串“solr”但不包含字符串“lucene”的文档,则搜索步骤如下。1)在字典中找到Term“lucene”,得到“lucene”对应的文档列表。2)在字典中找到Term“solr”,得到“solr”对应的文档列表。3)合并链表,对两个文档链表做差分运算,将包含“solr”的文档集减去包含“lucene”的文档集,运算后的结果是包含“solr”但不包含“卢塞恩”。通过以上四种查询方式,我们可以知道,由于Lucene是以倒排列表的形式存储的,所以在Lucene的查找过程中,只需要在字典中查找这些词条,根据term,然后根据具体的查询条件,对链表进行叉、并、差运算,就可以准确的找到我们想要的结果。分段存储和早期全文检索为整个文档集合构建一个大的倒排索引并写入磁盘。如果有更新,需要重新创建一个索引来替换原来的索引。显然,当数据量很大时,这种方法效率很低。现在,在搜索中引入段的概念。每个段都是一个独立的数据集,可以被搜索到,段是不可变的。一旦索引数据写入磁盘,就无法修改。在切分的概念下,写入数据的过程如下:添加。当有新的数据需要创建索引时,由于段的不变性,选择创建一个新的段来存储新添加的数据。删除。当需要删除数据时,由于该段数据只可读不可写,Lucene会在索引文件下增加一个新的.del文件来存放删除的数据id。查询的时候,被删除的数据仍然可以查到,但是被删除的数据只是在合并文档链表的时候才被过滤掉。删除的数据实际上是在执行段合并时删除的。更新。更新操作实际上是删除和添加的结合,先在.del文件中记录旧的数据,然后在新的段中添加一个更新的数据。段不变性的优点是不需要锁。因为数据不会更新,所以不用考虑多线程下读写不一致的问题,可以在内部常驻。段加载到内存后,由于其不可变性,只要内部空间足够大,就可以长期驻留。大多数查询请求会直接访问内存而不访问磁盘缓存。在Segment的生命周期内一直有效,不需要每次数据更新时都重新构建和增量创建。分段可以增量创建索引,并且可以在轻量级更新数据。由于每次创建的成本很低,数据可以经常更新,使系统接近实时更新。段不变性的缺点是浪费空间。删除数据时,不会立即删除旧数据,只有在合并Segments时才会删除,更新时会浪费大量空间。更新由创建和删除两个动作组成,会浪费大量空间,消耗大量服务器资源。由于索引的不可变性,每次更新数据时,都需要添加一个新的段来存储数据。当段数过多时,对服务器资源(如文件句柄)的消耗非常大,查询性能也会受到影响。查询时,需要过滤和删除数据。查询之后,需要对删除的旧数据进行过滤,这样会增加查询的负担。为了提高写的性能。Lucene并不是每增加一条新数据就增加一个段,而是采用延迟写入的策略。每当有新数据时,先写入内存,再批量写入磁盘。如果一个segment被写入磁盘,就会产生一个commitpoint,这是一个用来记录所有committedsegment信息的文件。一旦一个segment有了commitpoint,就意味着这个segment只有读权限,没有写权限;反之,当段在内存中时,只有写数据权限,没有读数据权限,因此无法取回。所以严格来说,Lucene或者ES只能称为准实时搜索引擎。indexwrite过程数据写入时,并不是直接写入磁盘,而是暂时写入内存。默认为1秒,或者当内存中的数据量达到一定阶段,再分批提交到磁盘。可以通过延迟写入策略来提高整体写入性能。触发条件满足后,缓存在内存中的数据一次性写入磁盘,并产生一个commitpoint。清空内存,等待新数据写入。需要注意的是因为延迟写入,在断电等情况下数据会丢失,为此ES使用事务日志来保证事务安全。段合并策略会在每次有新数据加入时加入一个段,时间长了,索引中会出现大量的段,严重消耗服务资源,影响性能。我们知道索引检索的过程是:在所有段中查询满足查询条件的数据,然后合并每个段中的查询结果集。因此,为了控制索引中的段数,我们需要周期性地进行段合并操作。Lucene合并段的思路是将段按照大小分组,然后将属于同一组的段合并。由于合并那些特别大的segment需要更多的资源,当segment的大小达到一定大小或者segment中的数据达到一定数量时,Lucene是不会合并的。SimilarityScoringLucene的查询过程是:首先在字典中查找每个Term,根据Term得到每个Term的文档链表,然后根据查询条件对该链表进行交、并、差操作,而链表合并后的结果就是我们要找的数据。但是,当我们一次查询很多数据的时候,这些数据和我们的查询条件有多大关系呢?它的文本相似度是多少?它们是在相似性模块中完成的。Lucene最经典的双文本相似度算法:基于向量空间模型的算法和基于概率的算法。..如果你不明白下面的内容,我会写在这里。有兴趣的可以阅读原文。