搜索之正排索引

正排索引,也叫前向索引,和倒排索引(也叫反向索引)是相对的,正排索引相对倒排来说简单多了,第二篇文章的时候有下面两个表格(表1和表2)

这个是表1

文档编号文档内容
1这是一个Go语言实现的搜索引擎
2PHP是世界上最好的语言
3Linux是C语言和汇编语言实现的
4谷歌是一个世界上最好的搜索引擎公司

这个是表2

关键词文档编号
Go1
语言1,2,3
实现1,3
搜索引擎1,4
PHP2
世界2,4
最好2,4
汇编3
公司4

我们之前一直在说作为倒排索引的表2,对于表1,我们认为是数据的详情(detail)信息,最后用来做数据内容展示的,如果是放在一个只支持全文搜索的搜索引擎中的话,那确实表1只是用来做最后的数据展示,但是如果我们的搜索引擎还想要一些复杂的功能,那么表1就是一个正排索引,如果我们的搜索引擎同时支持倒排索引和正排索引,我们可以简单的认为这是一个数据库系统(当然,和真正的数据库还差得远啊)。

首先,我们看什么情况下要使用正排索引

很明显,如果倒排索引满足不了搜索要求的时候,就需要引入正排索引,比如一个电商的搜索引擎,那么正排索引就是必须的了,假如我们有以下几个商品需要上架:

商品编号商品标题发布时间价格品牌
10001锤子手机T92026-06-065000锤子
10002小米手机102020-02-021999小米
10003华为手机P202022-12-123999华为

搜索的时候我们可能需要搜索价格在一个区间的手机,那么仅仅用全文倒排索引就比较难完成任务了,而且我们在使用电商的搜索引擎的时候,经常会在搜索结果的上方看到一些汇总的信息【比如品牌,型号,价格汇总】,这一部分的东西也是通过正排索引来实现的,像下面这个图

所以说,如果我们的搜索需求不仅仅是进行关键词的匹配,还需要进行一些过滤操作(比如价格区间的过滤),汇总操作(比如结果集中每种品牌数量的统计),那么就必须引入正排索引了。

第二,我们看看如何实现一个正排索引

实现正排索引有两种方式:

一种还是基于倒排索引,之前的倒排索引不是通过B+树构建的么,B+树天然的带排序功能,所以是可以进行范围查找的,比如上面那个表格,我们要搜索的关键词为手机,价格区间在1500–4000之间

  • 我们把价格字段和商品标题字段分别建立一个倒排。

  • 首先,通过标题的倒排索引,检索出所有的带手机这个关键词的商品的结果集,他们是【1,2,3】

  • 然后进行价格区间的检索,因为B+树最下面的叶子节点是通过指针连在一起的,我们只需要通过指针遍历叶子节点,就可以遍历出价格区间中所有价格的倒排链,然后把这些链求并集,得到的结果集是【2,3】,就是满足这个价格区间的所以商品了。

  • 最后再和关键词查出来的商品求交集,就是最后的结果了。

这是第一种实现方式,汇总操作大家可以自己想想怎么做,也能做,就是麻烦点。这种实现方式有下面几个特点

  • 没有单独的正排文件,和倒排文件合在一起了,同时也不占用额外的空间。

  • 但是它限制了倒排索引的实现方式只能是B+树这种带排序的字典,如果倒排文件使用哈希表来实现的话,就不能这么干了。

  • 检索的时候如果是区间搜索的话,需要进行多次求并集操作,效率上需要进行优化。

  • 由于只有倒排文件,那么最后用来做数据展示的时候还需要一个辅助的Detail文件或者和数据库绑定在一起才能进行最终的结果展示。

除了上面那个,还有一种实现方式,就是通过一个数组来实现,数组的下表就是文档编号(docid,不是商品编号,商品编号是主键),由于在搜索引擎中,docid是自增的,而且不会进行删除,所以也是唯一的,正好可以和一个一维数组的下标对上,所以可以用一个数组来存储正排索引,就像下面这个表格,分别表示价格和品牌建立的正排索引,其实就是把表1的数据拆开来进行存储了而已。(为了节省空间,我把两个写在一起了)

DOCID价格DOCID品牌
050000锤子
119991小米
239992华为

这么存的话,检索的时候怎么做呢?如果还是上面那个检索条件要搜索的关键词为手机,价格区间在1500–4000之间

  • 只把标题建立倒排,价格字段建立一个一维数组的正排

  • 首先,通过标题的倒排索引,检索出所有的带手机这个关键词的商品的结果集,他们的DOCID是【1,2,3】

  • 遍历结果集,每遍历一个docid,直接通过那个一维数组和对应的正排文件进行比对,看是否满足条件,满足的留下,不满足的丢弃。

  • 遍历完成以后,得到最终的结果集【2,3】

如果是汇总操作的话,和上述类似,在第二步遍历结果集的时候顺便就可以进行统计了,遍历完了也就统计完了。

条条大路通罗马,通过两种不同的数据结构,最后得到了一样的结果,第二方式有以下几个特点

  • 要为需要进行范围查找的字段单独建立正排索引,不能和倒排的数据结构合并。

  • 通过倒排获取到结果集以后需要对结果集进行一次遍历,然后得到一个新的结果集作为最后的结果,如果结果集特别巨大,那么也需要时间进行遍历。

  • 因为是一维数组来实现的正排,如果文档数非常多的话,内存中是装不下这么多正排文件的,需要在磁盘上来实现这个一维数组。

  • 如果我们将每一个字段都建立一个正排索引的话,那就不需要单独的detail文件或者和数据库对接了,直接正排文件合起来就是一个完整的文档信息,少了外部依赖。

上面就是正排索引的两种实现方式,使用哪一种要看具体的业务需求,比如像百度这种全文搜索引擎,主要的需求其实就是查找关键字,很少用到过滤,汇总操作,那么不用单独来实现正排索引,用第一种方式就行了,而如果是电商类型的搜索引擎的话,有大量的过滤啊,汇总操作,那么通过第二种方式来实现正排索引还是比较必要的。

我的代码里面就是用的第二种方式,并且实现的时候是用mmap的方式在磁盘上实现的,如果内存够大,可以全载入到内存提高检索速度。

索引设计管理

正排索引和倒排索引终于都说完了,这要是搜索引擎最关键的数据结构了,其他所有的东西都是在这个基础上发展起来的,我们已经有了正排和倒排索引的结构,那么如果来构建一个索引系统的,我是这么来做的。

首先,我们需要定一个规矩,所谓规矩就是我们的这个搜索引擎哪些操作我支持,哪些操作我不支持,比如,我为了简单,我就支持全文检索,其他都不支持,那么只需要好好的实现一个倒排索引结构,那数据结构部分就设计的差不多了。而我在做这个搜索引擎的时候,想实现的是下面这些个功能。

  • 支持关键词的倒排,也支持完全匹配类型的倒排。

  • 支持过滤操作,但是只支持整数类型(如果是浮点数根据保留的小数位数转成整数)和日期类型的过滤,对于字符串只提供检索操作,不提供过滤操作。

  • 对于过滤操作,支持大于,小于,等于,不等于,区间的过滤。

  • 支持字段的汇总。

  • 不要外接数据库系统进行数据详情的展示。

既然是这么来实现,那对于每个字段,他可能的类型就是

字段类型行为备注举例
完整匹配的字符串建立倒排,正排(正排只展示,不进行过滤操作)主键,型号
关键词字符串建立倒排,正排(正排只展示,不进行过滤操作)标题,描述
数字只建立正排价格,库存
日期只建立正排上架
仅展示只建立正排(正排只展示,不进行过滤操作)商品详情描述

这样,我们实现的时候,首先实现一个倒排索引(src/FalconIndex/segment/invert.go),然后实现一个正排索引(src/FalconIndex/segment/profile.go),然后实现一个字段类(src/FalconIndex/segment/field.go)用来管理倒排和正排,那么搜索引擎最最基本的数据结构就OK了,对外来说倒排和正排是隐藏的,只有Field类对外暴露,对检索操作来说主要提供几个接口方法:

  • addDocument 添加文档(建立正排或者倒排)

  • query 通过倒排检索文档

  • filter 通过正排过滤文档

  • getValue 通过正排文件获取这个字段的值

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值