lucene查询原理

  • 1. lucene 数据模型
  • 2. lucene 查询过程
  • 3. SkipList
    • 哨兵数组 skipDoc
    • docDeltaBuffer
    • Lucene中使用读取跳表SkipList的过程
    • 跳表查询演示
      • 文档号:23
      • 文档号:700
  • 4. 倒排合并

前言:
  lucene 是一个基于java实现的全文信息检索工具包,目前主流的搜索系统 Elasticsearch 和 solr 都是基于 lucene 检索功能实现的。想要理解搜索系统的实现原理,就需要深入 lucene 这一层,看看 lucene 是如何存储需要检索的数据,以及如何完成高效的数据检索。

1. lucene 数据模型

  lucene 中包含四种基本的数据类型:

  • index:索引,由很多的 Document 组成。相当于 mysql 的数据库
  • Document:由很多的 Field 组成。相当于 mysql 的一行数据
  • Field:由 Field Name 和 Field Value 组成。相当于 mysql 的字段和字段值
  • Term:由很多的字节组成。一般将Text类型的 Field Value 分词之后的每个最小单元叫做 Term。

2. lucene 查询过程

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

docid id name age
1 101 Alice 18
2 102 Alice 20
3 103 Alice 21
4 104 Alan 21
5 105 Alan 18

  每个 document 的 name value 经过分词形成多个 term,多个 term 组成 term dictionary,如下显示基于 name 创建倒排链如下:

  在这里,Alice、Alan 都是 term。所以倒排本质上就是基于 term 的反向列表,方便通过属性反向查找 doc_id。
  到这里我们有个很自然的问题,如何快速拿到这个倒排表呢?在 lucene 里面就引入了 term dictonary 的概念,也就是 term 的字典。term 字典里我们可以按照 term 进行排序,那么用一个二分查找就可以找到这个 term 所对应的倒排表信息,这样的复杂度是 logN。
  但在 term 很多,内存放不下的时候,效率还是需要进一步提升。 为解决这个问题,lucene 引用 term dict index 来存放 term 的共享前缀,后缀则放在 term dictonary 中。

  • term dict index:是采用 FST 数据结构,存放在内存中。term dict index 查到对应的 term dict 的 block 位置之后,再去磁盘上找 term,大大减少了磁盘的 random access 次数
  • Term dict:在磁盘上是以分 block 的方式保存的,一个 block 内部利用公共前缀压缩,比如都是 Ab 开头的单词就可以把 Ab 省去
  • SkipList: 是以 docId 建立的数据结构

  如果所有的 term 都是英文字符的话,可能这个 term index 就真的是 26 个英文字符表构成的了。但是实际的情况是,term 未必都是英文字符,term 可以是任意的 byte 数组。

  图示如下:。

  在 term dict index 中需要保存的是 Term 的前面部分字段,以及与 Term Dictionary 之间的映射关系,这使得存储的信息量减少。再结合 FST(Finite State Transducer)压缩技术,term dict index 可以被压缩到足够小,以至于可以被缓存进服务器内存中,而 Term 后缀则存放在 trem dict。
  整个检索过程如下:

  • 内存加载 term dict index(tip文件),通过 FST 匹配前缀找到后缀词块位置。
  • 根据词块位置,读取磁盘中 Term dict(tim 文件),匹配后缀,读取倒排表信息。
  • 根据倒排表位置去 doc文件 中加载文档数据。

  找到了倒排表,怎么通过 docId 找到我们需要的文档呢?

3. SkipList

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

  • skipInterval:该值描述了在 level=0 层,每处理 N 篇 document 文档,就生成一个 skipDatum,该值默认为 128。
  • skipMultiplier:该值描述了所有层,每处理 N 个skipDatum,就在上一层生成一个新的 skipDatum,该值默认为 8
  • numberOfSkipLevels:skipList中的层数

  直接给出一个生成后的跳表:

  文档号0~3455的3456篇文档,我们令 skipInterval 为128,即每处理128篇文档生成一个 skipDatum 和对应的 PackedBlock;令 skipMultiplier 为3(源码中默认值为8),即每生成3个 skipDatum 就在上一层生成一个新的 skipDatum 索引,它是 3个 skipDatum 中的最后一个。对于 level > 0 的 skipDatum 都有一个索引值 SkipChildLevelPointer 字段来指向下一层对应的 skipDatum ,例如 level=1 的第一个 skipDatum(383),SkipChildLevelPointer 值就是 127、255、383。

哨兵数组 skipDoc

  哨兵数组skipDoc的定义如下所示:

int[] skipDoc;

  该数组用来描述每一层中正在处理的 skipDatum ,skipDatum 对应的 PackedBlock 中的最后一篇文档的文档号作为哨兵值添加到哨兵数组中,在初始化阶段,skipDoc 数组中的数组元素如下所示:

int[] skipDoc = {127, 383, 1151, 3455}

  初始化阶段将每一层的第一个 skipDatum 对应的 PackedBlock 中的最后一篇文档的文档号作为哨兵值。

docDeltaBuffer

  docDeltaBuffer 是一个int类型数组,上文我知道一个 skipDatum 可以映射128篇文档,而 docDeltaBuffer 记录了一个 skipDatum 中所有的文档号。

Lucene中使用读取跳表SkipList的过程

  读取过程分为下面三步:

  • 步骤一:获得需要更新哨兵值的层数N

    • 从 skipDoc 数组的第一个哨兵值开始,依次与待处理的文档号比较,找出所有比待处理的文档号小的层
  • 步骤二:从 N 层开始依次更新每一层在 skipDoc 数组中的哨兵值
    • 如果待处理的文档号大于当前层的哨兵值,那么令当前层的下一个 skipDatum 对应的 PackedBlock 中的最后一篇文档的文档号作为新的哨兵值,直到待处理的文档号小于当前层的哨兵值
    • 在处理 level=0 时,更新后的 skipDatum 对应的 PackedBlock 中的文档集合更新到 docDeltaBuffer 中
  • 步骤三:遍历 docDeltaBuffer 数组
    • 取出 PackedBlock 中的所有文档号到 docDeltaBuffer 数组中,依次与待处理的文档号作比较,判断 SkipList 中是否存在该文档号

跳表查询演示

  我们依次处理下面的文档号,判断是否在跳表 SKipList 中来了解读取过程:

文档号:{23, 700}

文档号:23

  更新前的skipDoc数组如下所示:

int[] skipDoc = {127, 383, 1151, 3455}

  当前 skipDoc 数组对应的 SkipList 如下所示:

  由于文档号 23 小于 skipDoc 数组中的所有哨兵值,故不需要更新 skipDoc 数组中的哨兵值,那么直接遍历 docDeltaBuffer,判断文档号23 是否在该数组中即可。

文档号:700

  更新前的skipDoc数组如下所示:

int[] skipDoc = {127, 383, 1151, 3455}

  文档号700小于1151、3455,执行了步骤一之后,判断出需要更新哨兵值的层数N为2(两层),即只要从level=1开始更新level=1以及level=0对应的skipDoc数组中的哨兵值。

  对于 level=1 层,在执行了步骤二后,更新后的skipDoc数组如下所示:

int[] skipDoc = {127, 767, 1151, 3455}

  随后更新 level=0 层,在执行了步骤二后,更新后的 skipDoc 数组如下所示:

int[] skipDoc = {767, 767, 1151, 3455}

  这里要注意的是,level=0 层的 skipDatum 更新过程如下所示:

  在更新的过程中,跳过了两个 skipDatum ,其原因是在图3中,当更新完 level=1 的 skipDatum 之后,该 skipDatum 通过它包含的SkipChildLevelPointer字段,重新设置在 level=0 层的哨兵值为 511,随后在处理 level=0 时,继续往下遍历 skipDatum 找到 767 > 700,并更新 skipDoc ,然后通过 skipDatum(767 )对应的 PackedBlock 遍历看里面是否有 doc_id = 700,有的话返回读取 doc_id = 700 的文档数据。

为啥使用跳表而不是Hash?

  通过区间来查找数据这个操作,跳表天然有序的数据结构效率比 hash 高的多

4. 倒排合并

  到这里还有另一个问题,那就是当我们查询中出现了 name=‘Alan’ and name=‘Alice’ limit 0,20 该如何处理?

  它会拆分为 term = ‘Alan’ 和 term = 'Alice’的查询。分别得到每个term的倒排链(docId列表),再对多个倒排表做交集。那么倒排表怎么做交集呢?

水平分桶,多线程并行

有序集合1{1,3,5,7,8,9, 10,30,50,70,80,90}和有序集合2{2,3,4,5,6,7, 20,30,40,50,60,70},先进行分桶拆分【桶1的范围为[1, 9]、桶2的范围为[10, 100]、桶3的范围为[101, max_int]】,于是拆分成集合1【a={1,3,5,7,8,9}、b={10,30,50,70,80,90}、c={}】和集合2【d={2,3,4,5,6,7}、e={20,30,40,50,60,70}、e={}】,利用多线程对桶1(a和d)、桶2(b和e)、桶3(c和e)分别求交集,最后求并集。

bitmap,大大提高运算并行度,时间复杂度O(n)

假设set1{1,3,5,7,8,9}和set2{2,3,4,5,6,7}的所有元素都在桶值[1, 16]的范围之内,可以用16个bit来描述这两个集合,原集合中的元素x,在这个16bitmap中的第x个bit为1,此时两个bitmap求交集,只需要将两个bitmap进行“与”操作,结果集bitmap的3,5,7位是1,表明原集合的交集为{3,5,7}

水平分桶(每个桶内的数据一定处于一个范围之内),使用bitmap来表示集合,能极大提高求交集的效率,但时间复杂度仍旧是O(n),但bitmap需要大量连续空间,占用内存较大。

lucene查询原理相关推荐

  1. 基于Lucene查询原理分析Elasticsearch的性能

    2019独角兽企业重金招聘Python工程师标准>>> 各位新老用户们,如果您已经开了团,但是还不知道怎么玩?小编告诉来告诉你! 首先,进入活动主页面,点击[我要开团],选择您想要购 ...

  2. Es底层查询原理、数据结构、及性能分析

      Elasticsearch是一个很火的分布式搜索系统,提供了非常强大而且易用的查询和分析能力,包括全文索引.模糊查询.多条件组合查询.地理位置查询等等,而且具有一定的分析聚合能力.因为其查询场景非 ...

  3. Lucene 倒排索引原理

    1 前言 Lucene 作为 Apache 开源的一款搜索工具,一直以来是实现搜索功能的神兵利器,现今火热的 Solr 和 Elasticsearch 均基于该工具包进行开发,我们搜索召回组这边也是基 ...

  4. lucene 查询示例_高级Lucene查询示例

    lucene 查询示例 本文是我们名为" Apache Lucene基础知识 "的学院课程的一部分. 在本课程中,您将了解Lucene. 您将了解为什么这样的库很重要,然后了解Lu ...

  5. 高级Lucene查询示例

    本文是我们名为" Apache Lucene基础知识 "的学院课程的一部分. 在本课程中,您将了解Lucene. 您将了解为什么这样的库很重要,然后了解Lucene中搜索的工作方式 ...

  6. Lucene底层原理和优化经验分享(1)-Lucene简介和索引原理

    基于Lucene检索引擎我们开发了自己的全文检索系统,承担起后台PB级.万亿条数据记录的检索工作,这里向大家分享下Lucene底层原理研究和一些优化经验. 从两个方面介绍: 1. Lucene简介和索 ...

  7. 5分钟了解搜索引擎Lucene的原理

    场景 假设现在有10W+份word文档,让你做个web页面,给出关键词能快速搜索结果,你会怎么做?那至少有3种方案, 顺序扫描,每次检测文档中是否包含关键词,包含则加入结果列表,不包含继续查找下一个, ...

  8. Lucene底层原理和优化经验分享(2)-Lucene优化经验总结

    转自:https://blog.csdn.net/njpjsoftdev/article/details/54133548 系统优化遵从木桶原理:一只木桶能盛多少水,并不取决于最高的木板,而取决于最短 ...

  9. Lucene查询的底层实现IndexSearch(上)

    系列文章目录 (一)问答系统的文段检索 (二)lucene全文检索底层原理理解 (三)Lucene查询的底层实现IndexSearch(上) (四)Lucene查询的底层实现IndexSearch(下 ...

最新文章

  1. Charles抓取微信小程序数据 以及 其它应用网站数据
  2. cocospod 安装和使用
  3. [转]25个增强iOS应用程序性能的提示和技巧
  4. poj2942(双联通分量,交叉染色判二分图)
  5. 记一次 Redis Cluster 宕机引发的事故
  6. oracle esb 灾备,两地三中心双活系统灾备切换场景和数据补录问题?
  7. python 嵌入桌面
  8. Flex Builder 中视图状态
  9. 看《一人之下:陈朵篇》——架空世界里的真实
  10. centos7搭建apache服务器
  11. Unity小地图跟随人物移动
  12. Protel DXP使用教程 -建立工程与绘制原理图PCB图
  13. matlab电学成像,利用MATLAB进行电磁学计算及可视化教学.PDF
  14. Linux下scp的用法
  15. mysql动态变量_Mysql的变量一览
  16. openvas linux/window漏洞扫描和整改
  17. css扇子展开,CSS3 绝妙的扇形折叠展开加载动画
  18. 计算机文化基础008,2013 中国成人教育 翻转课堂在高校计算机文化基础课中的应用研究_刘桂花...
  19. 海康服务器系统装完重启转圈蓝屏,win10 1903系统重启后一直在转圈无法正常启动的解决方法...
  20. twilio 短信接口_使用Android Things和Twilio构建短信通知系统

热门文章

  1. surface rt 8.1 越狱应用签名工具
  2. 计算机房按几类防雷,计算机机房防雷方案
  3. Flutter 侧滑栏UI及城市选择UI的实现
  4. [日推荐]『车助手360』车主必备
  5. 计算机内存数值存储方式进制
  6. Python 异常处理 详解
  7. php制作云端网盘项目,用云开发整一个专属网盘,原来如此简单!
  8. java基础面试资料收集篇一
  9. (6)继承与面向对象设计- Effective C++改善程序与设计的55个具体做法(Effective C++: 55 Specific Ways to Improve Your Programs)
  10. 模拟表达式运算(加减乘除,负数,带括号)