lucene入门介绍篇

一 基本概念

1.lucene介绍

(1)简介

lucene是最受欢迎的java开源全文搜索引擎开发工具包。lucene使用反向索引,提供了完整的查询引擎和索引引擎,部分文本分词引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便在目标系统中实现全文检索功能,或者是以此为基础建立起完整的全文检索引擎。lucene的适用场景包括:①开发独立的搜索引擎服务②在应用中为数据库中的数据提供全文检索实现(优势:相对于顺序扫描全文检索是一次索引,多次使用)③实现对非结构化数据的查询。它是Apache的子项目,网址为:http://lucene.apache.org

(2)特性

 ①稳定、索引性能高

每小时能够索引150GB以上的数据。

对内存的要求小——只需要1MB的堆内存

增量索引和批量索引一样快

 索引的大小约为索引文本大小的20%~30%

②高效、准确、高性能的搜索算法

强大的查询方式支持:短语查询、通配符查询、临近查询、范围查询,模糊查询,布尔查询(组合查询)等

支持字段搜索(如标题、作者、内容)

可根据任意字段排序

支持多个索引查询结果合并

支持更新操作和查询操作同时进行

支持高亮、join、分组结果功能

可扩展排序模块,内置包含向量空间模型、BM25模型可选

可配置存储引擎

③跨平台

纯java编写

作为Apache开源许可下的开源项目,你可在商业或开源项目中使用

 Lucene还有多种其他语言实现版可选(如C、C++、Python等)

2.常用术语

(1)Index(索引)

索引,由很多的Document组成。类似数据库的表的概念,但又有很大不同。传统关系型数据库或者NoSQL数据库的表,在创建时要定义表的Scheme,定义表的主键或列等,有明确定义的约束。但是Lucene的Index完全没有约束。

(2)Document(文档)

建立索引和查询的最小单位,由很多Field组成,类似数据库的记录或者文档数据库中文档的概念。写入Index的Document会被分配一个唯一的ID,即Sequence Number(更多被叫做DocId)。

(3)Field(字段)

Lucene中数据索引的最小定义单位,由Field name,Field value和Field type组成,不同的Filed type(StringField、TextField、LongFiled或NumericDocValuesField)决定了不同的索引方式(Store Field,DocValues等等),也可以通过实现IndexableField接口来设置Field的属性。

(4)Term 和 Term Dictionary

一个Field(主要指TextField的value)会由一个或多个Term组成。Term是由Field经过Analyzer(分词)产生,表示文本的一个词语。它有两个元素组成:词语的内容(查询时的关键字)和文本所在的域(即Field name)。这里需要与tocken区别开,Token是一个在分词过程中产生的对象,对应的Token对象包含了这个词语的自身内容,在这句话中的开始位置、结束位置以及一个可以储存其他信息的payload对象(Tocken在lucene2.9之后被Attuibutes接口实现类代替)。Term Dictionary即Term词典,lucene使用FST数据结构来存储term字典,lucene是根据Term词典来查询Term对应的文档ID。

(5)Segment

一个Index会由一个或多个sub-index构成,sub-index被称为Segment。Lucene中的数据会先写入内存的一个Buffer(类似LSM的MemTable,但是不可读),当Buffer内数据达到一定量后会被flush成一个Segment,每个Segment有自己独立的索引,可独立被查询,但数据永远不能被更改。这种模式避免了随机写,数据写入都是Batch和Append,能达到很高的吞吐量。Segment中写入的文档不可被修改,但可被删除,删除的方式也不是在文件内部原地更改,而是会由另外一个文件保存需要被删除的文档的DocID,保证数据文件不可被修改。Index的查询需要对多个Segment进行查询并对结果进行合并,还需要处理被删除的文档,为了对查询进行优化,Lucene会有策略对多个Segment进行合并,这点与LSM对SSTable的Merge类似。

(6)反向索引

反向索引,即由单词->文档的索引,每个单词对应文档id列表,与正向索引相反(文档->单词,每个文档对应单词id列表)。在没有搜索引擎时,我们是直接输入一个网址,然后获取网站内容,这时我们的行为是:document -> to -> words。 通过文章,获取里面的单词,此谓「正向索引」,forward index。后来,我们希望能够输入一个单词,找到含有这个单词,或者和这个单词有关系的文章:word -> to -> documents,这就是反向索引,也译为倒排索引。

2.lucene架构和使用过程

(1)lucene的架构图

                                                                            图2.1 lucene架构和过程

从图2.1可以知道,lucene是由索引和搜索两个过程,包含了创建索引,索引库和搜索三个要点。

图2.2是lucene各组件的细节展示。

                                                                                    图2.2 lucene 组件架构

  • 被索引的文档用Document对象表示。
  • IndexWriter通过函数addDocument()将文档添加到索引中,实现创建索引的过程。
  • 当用户有请求时,Query代表用户的查询语句。
  • 在查询之前一般需要对查询的关键字进行分词处理,和建立索引时相同。
  • IndexSearcher通过函数search()搜索Lucene Index。
  • IndexSearcher计算term weight和score并且将结果返回给用户。
  • 返回给用户的文档集合用TopDocsCollector表示

3.lucene使用的数据结构

(1)FST

lucene使用FST数据结构来存储term字典,下面就详细介绍下FST的存储结构。我们就用Alice和Alan这两个单词为例,来看下FST的构造过程。首先对所有的单词做一下排序为“Alice”,“Alan”。

①插入“Alan”

                                                                               图3.1 插入”Alan"  

②插入“Alice"

                                                                             图3.2 插入“Alice" 

这样你就得到了一个有向无环图,有这样一个数据结构,就可以很快查找某个人名是否存在。FST在单term查询上可能相比hashmap并没有明显优势,甚至会慢一些。但是在范围,前缀搜索以及压缩率上都有明显的优势。

在通过FST定位到倒排链后,有一件事情需要做,就是倒排链的合并。因为查询条件可能不止一个,例如上面我们想找name="alan" and age="18"的列表。lucene是如何实现倒排链的合并呢。这里就需要看一下倒排链存储的数据结构。

(2)SkipList

为了能够快速查找docid,lucene采用了SkipList这一数据结构。SkipList有以下几个特征:

①按照docid从小到大进行排序,对应到我们的倒排链。

②跳跃有固定的间隔,在建立SkipList的时候指定,例如下图以间隔是3

③SkipList的层次是指整个SkipList有几层

                                                                                   图 3.3 跳表的例子

基于SkipList,比如我们要查找docid=12,原来可能需要顺序扫原始链表:1,2,3,5,7,8,10,12。有了SkipList,先访问第一。层的15(>12),进入第0层走到3,8,15(>12),然后进入原链表的8,继续向下经过10和12。有了FST和SkipList的介绍以后,图3.4 大致说明lucene是如何实现整个倒排结构。

                                                                         图 3.4 lucene索引存储结构 

(3)倒排合并

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

                                                                            图3.5 多个倒排链 

在lucene中采用下列步骤进行合并。

①从termA开始遍历,得到第一个元素docId = 1

②设置currentDocId的值为当前元素的值(此时为1)

③在下一个倒排链中(termA->termB->termC->termA)中搜索返回大于等于currentDocId的一个docId。如果docId == currentDocId 继续③直到所有链都符合docId == currentDocId返回结果,并进入④;如果docId > currentDocId,则直接进入④

④设置当前链下个元素为currentDocId并开始重复②③

终止条件:某个倒排链遍历到末尾

整个合并步骤我可以发现,如果某个链很短,则会大幅减少比对次数。并且由于SkipList结构的存在,在某个倒排中定位某个docId的速度会很快(不用顺序遍历)。从倒排的定位,查询,合并整个流程组成了lucene的查询过程,和传统数据库的索引相比,lucene合并过程中的优化减少了读取数据的IO,倒排合并的灵活性也解决了传统索引较难支持多条件查询的问题。

(4)BKDTree

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

                                                                                         图3.5 一维KDTree 

如果是多维,kdtree的建立流程会发生一些变化。比如我们以二维为例,建立过程如下:

①确定切分维度,这里维度的选取顺序是最大维度优先。一个直接的理解就是,数据分散越开的维度,我们优先切分。

②切分点的选这个维度最中间的点。

③递归进行步骤①②,我们可以设置一个阈值,点的数目小于阈值就不再切分,直到所有的点都切分好停止。

图3.6显示了二维KDTree。

                                                                                  图3.6 二维KDTree 

KDTree如果有新的节点加入或者节点修改起来,消耗还是比较大。BKDTree是KDTree的变种,类似于LSM的merge思路,BKD也是多个KDTree,然后持续merge最终合并成一个。如果某个term类型使用了BKDTree的索引类型,那么在和普通倒排链merge的时候就没那么高效,所以这里要做一个平衡。一种思路是把另一类term也作为一个维度加入BKDTree索引中。

reference: https://www.cnblogs.com/leeSmall/p/8992887.html,https://zhuanlan.zhihu.com/p/35814539

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值