甩出11张图-让我们来构想(实现)一个倒排索引

数据检索系列文章

倒排索引的简介

在介绍倒排索引之前,先看看传统b+tree索引是如何存储数据的,每次新增数据的时候,b+tree就会往自身节点上添加上新增数据的key值,如果节点达到了分裂的条件,那么还会将一个节点分裂成两个节点。

想一个场景,如果对用户的性别建立b+tree索引,性别只有男女之分,这样存在b+tree里是不是会存很多重复的key,如果用户数据量很大,我们想筛选出性别是男性的用户,是不是要遍历大量的数据。

而这种选择性不高的数据,用倒排索引来做就很合适,倒排索引在面对那么多用户数据的情况下,只会存两个key值,分别是男女,然后key对应的value值就是用户id的集合,能一下就找到特定性别的用户。

那么倒排索引究竟长什么样呢?

还是拿刚才那个场景举例,用于查询的key,构成倒排索引的词典,而每个词(key)都对应一个倒排列表,例如男性的key 对应的倒排列表 就包含了所有性别为男性的uid,而女性的key对应的则是另一个倒排列表,这个列表的uid全为女性。

那么这样的索引结构,我们究竟应该如何来实现呢。为了简单起见,先来构建一个在内存上能用的倒排索引。

第一版 实现一个内存上的倒排索引

当我们在搜索特定key对应的uid集合时,目的是找到容纳uid集合的倒排列表,要找到倒排列表就必须找到与之对应的在词典中的key值,所以我们先来看看如果你来实现一个词典的结构,你会怎么做。

如何实现词典与倒排列表

词典不外乎就是在一堆key值中找到某个特定的key值,我们可以直接采用hash结构嘛。直接将词典中的每个key设置为hashmap的key,倒排列表设置为hashmap的value值,这样不就行了吗。

而关于倒排列表的实现是不是可以简单的用一个集合来表示呢?这样我在查男性或者女性的时候直接取出对应的集合,这样不就实现了吗?

当然,这是最简单的版本,试想下其他场景,比如对用户标签建立倒排索引,一个用户可以打上多个标签,现在需要找出标签同时为标签A和标签B的用户,应该怎么查询。

如图,我们的倒排列表是集合,首先是不是可以找到标签A和标签B各自的倒排列表,集合无序但是可以遍历,两个for循环遍历两个倒排列表便可以求出其中即属于标签A又属于标签B的用户了。假设两个倒排列表的长度分别是m,n,那么这样的时间复杂度将会是O(m*n)。

能不能优化呢?可以。

如上图所示,如果将倒排列表设计成有序链表,那么是不是可以用归并排序的方式来遍历两个列表,这样时间复杂度是O(m+n),链表查询是快了,但是我们在往倒排列表中插入数据时,还得判断数据是不是已经存在链表中,这样就得遍历整个链表,能不能优化这个过程呢?可以。

我们遍历无非就是要找到这个数据是不是已经在倒排列表中了,如果在的话,我就不插入链表了。

那么我们在最初插入链表时,除了将数据插入链表,还要再将数据插入到一个hash或者集合里,等到后续再插入数据的时候,首先查看map里是否存在相同数据,相同则不进行接下来的插入工作,这样是不是可以优化掉一部分链表的无效遍历了。并且如果将倒排链表设计成双向链表,在删除的时候也可以先查看hash结构查找到需要删除的节点,然后直接根据节点的前后指针,便可以在O(1)的时间复杂度完成删除操作。 如图:

好了,说了这么多,关于内存上如何实现一个倒排索引,我觉得大家完全可以发挥自己的想象,在不同的场景下选择不同的数据结构进行组装,便能很轻松的实现一个基于内存的倒排索引。接下来,我们来看看,当倒排索引越来越大,大到内存放不下的时候,我们又该怎么做。

第二版 实现一个磁盘上的倒排索引

既然是数据存储,必然倒排索引会有内存不能完全放下的一天,这个时候,想想看,有没有什么办法能在磁盘上很好的表示一个倒排索引结构?

如何实现词典

内存上可以用hashmap来存储词典的key值,但是我们应该如何将词典存在磁盘上呢,还记得之前提到过的b+tree(看了还不懂b+tree本质就来打我)吗,它能很好的应对磁盘随机读的情况,正好可以拿来应用到词典对key值的查询上。

那么如何来实现倒排列表呢?
既然存在磁盘上,那是不是也可以用b+tree存储呢?其实也是可以的,不过这样的设计会导致读取倒排列表不会按文档id递增读取,并且由于倒排列表不是递增,那么在多条件查询时,将不能用多路归并的方法进行文档id的合并,提高了查询时间复杂度。

既然这样,那我们就将数据结构设计成顺序的好了,还记得前面实现内存上的倒排列表时采用的什么数据结构吗,有序链表。那么是不是可以直接将数据顺序写入到磁盘就行了,比如按文档id是int类型且占8个字节计算,那么每次读取按8字节的步长就可以读取每一个文档id了。我们将磁盘想像成一个超大容量的数组。

我们的倒排索引将会变成这样。

词典中的key值指向key在磁盘上的开头位置。

可以看出,如果按固定步长进行磁盘存储,其实存储的是一个有序数组的结构.这样可以很好利用磁盘顺序读写的高效性特点。

但是这样会面临倒排索引更新的问题,因为在磁盘上有序存储,如果要在这个倒排列表上新增一个文档id,那么要移动磁盘数据,这样的代价显然太大。

如果把磁盘上的数据结构变成一个有序链表呢,每次存储时按有序链表的节点进行存储,那么每个节点除了要包含文档id的8个字节,还要再包含指向下一个节点的位置。这样新增一个文档id,是不是可以往倒排列表所在的磁盘空间末尾新增一个节点的空间,然后让倒排节点的末尾节点指向新增的节点即可,不用移动磁盘数据。

这样我们的倒排索引在磁盘上就会变成下面这张图的样子,各个倒排链表之间是通过指针关联在了一起,而词典的key指向的是链表头部的元素在磁盘上的位置。

但是,又是一个但是,如果对这样的倒排列表频繁进行删除和更新会怎么样,之前在讲b+tree本质的时候,有说过由于节点的删除和更新,b+tree相邻父子节点之间只是位置相邻,在磁盘空间上可能并不相邻。

对于链表来说也是一样,频繁的插入和删除就会导致顺序的数据在磁盘上可能分布在不同磁道了,可以看到,同一个key的倒排列表在磁盘上并不相邻, 在读取倒排列表时,将会有过多的随机读产生,严重影响性能。

既然这样,能不能借鉴下LSM对索引合并的思路(剖析LSM索引原理),对索引的更新采取合并的策略,而不是原地更新的方式,比如,在新增文档时,会去构建增量的词典和倒排索引,这一部分索引暂时不可见,只有等到这部分索引和历史可见索引进行合并后,才会真正被搜索到。

如图所示,同一个颜色属于同一个倒排列表。

这样虽然牺牲了一点立即可见性,但由于合并时是写入到一个新的倒排文件里,将会是顺序写,同一个倒排列表基本是在相同的磁道上,这样读取倒排列表时便可以有很好的读取性能。

采用索引合并的策略是不是就没有其他问题呢?它同样面临索引合并的常见问题,一个小数量级的索引和大数量级的索引进行合并时,会产生很多无效合并。新的增量的倒排索引是比较少量的,而历史的索引数量级是比较庞大的,每次由于新增的倒排索引需要合并就要去遍历整个历史的索引文件显然是划不来的。

一个比较简单的解决办法就是,直接将大的索引文件拆分成很多个小段的索引文件,我们将这些由大索引分割的小索引文件叫做segment,每个segment是有它自己的词典和倒排列表

选择合并索引的策略也是选择大小相近的segment索引文件进行合并,每个小的索引文件里有各自的词典和倒排列表。这样查询的时候可能就麻烦点,需要查询多个小的索引文件,不过这样的查询完全可以采用多cpu同时并行查询进行加速。如下图所示,是将增量的倒排索引合并到存量索引的过程,合并时,只将segment2和segment3进行合并。

注意虽然将这种分割后的小词典和它的倒排列表的组合称作segment,但是segment在磁盘上并不一定是只用一个文件,我们完全可以对segment进行编号后,通过不同的后缀对不同类型的文件进行取名。如图所示:

假设 文件segment1.dc代表segment1的词典,segment1.lt文件代表segment1的倒排列表。.dc后缀表明这是一个词典文件,.lt后缀表明这是存储一个倒排列表的文件。

再回到起初 词典是怎么存储在磁盘上的问题上,我们采用了b+tree结构,但是将索引分成多个小的索引文件之后,我们要想查某个词对应的倒排列表,是不是要每个小的索引文件都要去用小索引文件他们各自的b+tree词典去查一遍存不存在这个词呢?这样即使是多cpu查询,但是大量的随机读会把查询瓶颈压在磁盘上。

词典存磁盘访问慢是因为词典无法存在内存里,词典无法存储在内存上的原因是词典过于大了,所以我们得想办法压缩词典的大小。

所以,有没有什么办法能压缩词典的大小呢?

我将直接揭晓答案,采用前缀树的数据结构能很有效的将词典的体积缩小到内存能够容纳的范围。前缀树的节点包含了词中的每个字母,并且表明了从根节点到此节点上的路径是否构成了一个词典中的词。如图:

绿色的节点代表从根节点到该节点的路径上存在一个词,这颗前缀树能够代表的字母为get,go,good。比如英文字母只有26个,这样用前缀树组合形成的词语存储方式可以极大的省掉大量前缀相同的词的存储空间。

这样,每个小的索引文件(segment)便可以将词典对应的前缀树存储到内存中了。这下我们来完成倒排索引最后的架构图。

每一个segment索引文件都有自己的前缀树结构在内存中。

整个倒排索引的查询过程就变成了先查内存中的前缀树结构,找到特定的词所在segment.dl词典文件的位置,我们实现词典文件的存储采用b+tree的方式,然后通过查询词典文件,再找到具体的倒排列表的位置,我们采用有序链表顺序写入倒排列表的方式让词典中属于同一个词的倒排列表中的元素尽量相邻,提高倒排列表从磁盘读取的效率。

至此,我们实现了一个基于磁盘的倒排索引,但优化远远没有结束。例如,业界上在实现倒排列表时,为了极致的压缩存储空间,采用了一些压缩方案例如Frame of ,并且为了提高联合查询效率,将倒排链表设计成跳表,将倒排列表查询结果缓存起来等等。希望我的文章能抛砖引玉,引发大家更多的思考。

创作不易,如果觉得我的文章对你有帮助,关注一下,点个赞吧

甩出11张图-让我们来构想(实现)一个倒排索引相关推荐

  1. 11张图演进SeviceMesh服务网格

    本周和大家聊聊架构进化史-大家可文末扫码加入 随着互联网持续高歌猛进,相关技术名词也是层出不穷,ServiceMesh服务网格这两年尤为火爆,然而很少有讲清楚的文章.笔者这里用心整理了一篇文章,用11 ...

  2. AIGC周报|30秒定制一个文生图模型;60美元让AI玩转《我的世界》;手机版“文生图”模型:2秒不到出一张图

    AIGC(AI Generated Content)即人工智能生成内容.近期爆火的 AI 聊天机器人 ChatGPT,以及 Dall·E 2.Stable Diffusion 等文生图模型,都属于 A ...

  3. 11张图步步演进:你一定能看懂的【分布式系统】容错架构设计!

    墨墨导读:讲述大规模分布式系统的容错架构设计.虽然定位是有"分布式"."容错架构"等看起来略显复杂的字眼,但是这里用大白话 + 手绘数张彩图,逐步递进,让每位读 ...

  4. 11张图让你看完苹果发布会

    PM小编提示:①智能手表Apple Watch首发含大陆,4月10日预售,24日发货,最低2588元算公道,但金表最高卖126800元:②12寸视网膜MacBook,配色增为土豪金等三色,9288元起 ...

  5. 11张图揭露了程序员的日常生活,看完笑哭(泪奔)!

    (点击上方公众号,可快速关注) 程序员的日常生活中只有代码和电脑,做为一个程序员,我更了解程序员,下面带你走进程序员的神秘日常生活,程序员大佬们也看看,你的生活是否如我所说呢 1.程序员一般待遇优厚, ...

  6. PMCAFF | 11张图告诉你产品经理的正确思维方式

    作者|cloudxiao PMCAFF专栏作者 产品经理 互联网工匠 擅长以用户为中心设计产品 "思维"在百度词条里的解释是:人脑对客观事物的概括和间接的反应过程. 在互联网时代到 ...

  7. 11张图读模电、数电必备的电路基础知识

    转自:EDN 1.从汇编.C语言为起点,十年FPGA开发设计经验总结 2.成为一个软硬件通吃的技术大牛,有一点很重要! 3.如何设计嵌入式系统?带你理解一个小型嵌入式操作系统的精髓 4.C语言访问MC ...

  8. 11张图告诉你什么是PMP项目管理,程序员必看

    1.项目整合管理 整合管理是指识别.定义.结合.统一与协调项目管理过程组中的各个过程以及项目管理活动,用一句话来总结项目整合管理,其作用犹如项链中的那根线,将项目管理过程组中需要的各个过程进行组合. ...

  9. 11张图读懂可口可乐供应链的管理精髓

    导语: 可口可乐传承了118年,至今其仍是世界饮料业的巨头,是什么让它雄踞碳酸饮料行业之首?可口可乐有什么秘密竞争性武器呢? 来自可口可乐全球供应链总监分享的可口可乐供应链计划.运营.库存管理等供应链 ...

最新文章

  1. Selenium IDE安装
  2. 循环结构_do-while循环
  3. Docker 容器中“TERM environment variable not set.”问题解决
  4. 并不算复杂的正则表达式基础
  5. mysql查询库表变更信息_PythonMySQL进行数据库表变更和查询
  6. python列表乘数值_《利用Python进行数据分析》十一章· 时间序列·学习笔记(一)...
  7. python数据包分析_python | 数据分析(一)- Numpy数据包
  8. APPCAN学习笔记006_创建第一个APPCAN应用
  9. python爬虫-Python爬虫入门这一篇就够了
  10. SVM算法及OpenCV源码分析
  11. RESTClient 工具
  12. SQL查询语句基本练习
  13. html静态页面作业家乡网站设计—我的家乡-四川(9页) HTML+CSS+JavaScript 学生DW网页设计作业成品
  14. JavawebJAVAJSP网吧计费管理系统(JSP网吧管理系统)网吧收费管理系统网吧自动计费管理系统
  15. Python爬虫实战,DecryptLogin模块,Python模拟登录微博实现批量删除微博
  16. matlab中psf2otf作用,Matlab中psf2otf()函数在opencv中的实现
  17. 为什么大型高难度工程的首选支模架是盘扣架?
  18. java图像处理-(指定区域内)灰度化、透明化(alpha通道)处理
  19. P1307 [NOIP2011 普及组] 数字反转
  20. 微信电脑版dat图片文件解码解密方法

热门文章

  1. PPT上如何自动生成对齐参考线?
  2. 唤醒幻数据包禁用会怎么样_Win10关机可以网络唤醒,睡眠无法网络唤醒?
  3. mysql数据库字符类型长度_MySQL数据库入门:mysql字段每个类型长度大小与建表类型长度说明...
  4. 微信小程序获取微信运动数据并解密
  5. 美团监控系统mysql_美团 MySQL 数据库巡检系统的设计与应用
  6. 厦门大学c语言课程资源,厦门大学c语言模拟题讲评及课程复习.ppt
  7. 【WPS表格】条件格式功能的部分运用
  8. python脚本生成测试大批量的测试数据
  9. Android与JNI(一)
  10. 多彩二维码——引爆个性化二维码潮流