文章目录:
1、搜索引擎的工作原理
搜索引擎的信息来源于互联网网页,通过网络爬虫将整个互联网网页的信息获取到本地,然后将互联网页面中近似重复或完全重复的内容去除,网页内容通过倒排索引这种高效查询数据结构来保存,网页之间的链接关系也保存下来。 当搜索引擎接收到用户的查询词后,需要对查询词进行分析,首先在缓存中查找缓存信息,如果能够在缓存系统找到,则可以直接将搜索结果返回给用户,如果保存在缓存的信息无法满足用户需求,搜索引擎需要调用“网页排序”模块功能,根据用户的查询实时计算哪些网页是满足用户信息需求的,并排序输出作为搜索结果。
2、为什么将数据存在搜索引擎中?
文档通常保存在数据库中,但是搜索引擎中的数据不能保存到数据库中,原因有两点: ① 搜索引擎中的数据量非常庞大,需要处理数以亿计的网页,面对海量数据使用关系型数据库很难管理。② 搜索引擎中的数据非常简单,一般只需增删改查这几个基本功能,大量用户检索则要求搜索引擎响应时间必须很快,检索效率要非常高,数据库系统不能满足需求。 数据库中的索引是为了提高表的搜索效率而对某些字段中的值建立的目录,在搜索引擎中使用倒排索引这种数据结构来存储网页信息,从而提高网页的查询效率。
3、正排索引
索引分为正排索引和倒排索引,在搜索引擎中,正排索引和倒排索引都有涉及。
在正排索引中,以文章为key,以以查询语句的分词列表为value。而在实际搜索网页或文章时,恰恰与此结构相反,即在搜索时是以查询语句的分词列表为key来进行搜索的。因此,为了提高搜索效率,我们需要将其转化为以分词词汇为key、以网页或文章列表为value的结构,而这个结构就是倒排索引。
4、倒排索引基础概念
如图,单词—文档矩阵是表达单词与文档之间所具有的一种包含关系的概念模型,每列代表一个文档,每行代表一个单词,笑脸的位置代表包含关系。
每列代表文档包含了哪些单词,比如文档1包含了词汇1和词汇3。每行代表了哪些文档包含了某个单词。比如对于词汇1来说,文档1和文档4中出现过词汇1。 搜索引擎的索引其实就是实现单词—文档矩阵的具体数据结构。可以有不同的方式来实现上述概念模型,比如倒排索引、签名文件、后缀树等方式。但实验证明,倒排索引是单词到文档映射关系的最佳实现方式。通过倒排索引,可以根据单词快速获取包含这个单词的文档列表。 在倒排索引中,主要由单词词典,倒排列表组成: ① 单词词典:搜索引擎通常的索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息及指向倒排列表的指针。 ② 倒排表:倒排列表记载了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项。根据倒排列表,即可获知哪些文档包含某个单词。 ③ 倒排文件:所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件即被称为倒排文件,倒排文件是存储倒排索引的物理文件
5、倒排索引简单示例
如图,假设文档集合包含5个文档,最左端一栏是每个文档对应的文档编号,我们的任务就是对这个文档集合建立倒排索引。
倒排索引中需要记录的信息: ① 首先利用分词系统对文档进行分词,需要对每个不同的单词赋予唯一的单词编号。 ② 记录文档编号信息(DocID),包含某个单词的所有文档的文档编号。 ③ 记录单词频率信息(TF),即单词在某个文档中出现的次数,以方便排序时进行分值计算。 ④ 记录文档频率信息,即文档集合中有多少个文档包含某个单词,方便排序。 ⑤ 记录单词位置信息(Position),即单词在某个文档中出现位置的信息。
以单词“谷歌”为例,其单词编号为1,文档频率为3,代表整个文档集合中有3个文档包含这个单词,对应的倒排列表为{(1;1;<1>), (2;1;<1>), (3;2;<1>)},其含义为在文档1和文档2以及文档3出现过这个单词,单词频率都为1,在文档1和文档2中的出现位置都是1,即文档中第1个单词是“谷歌”,在文档3中出现的位置为1和5,即文档3的第1个和第5个单词是“谷歌”。 比如用户输入查询词“Facebook”,搜索系统查找倒排索引,从中可以读出包含这个单词的文档,这些文档就是提供给用户的搜索结果,而利用单词频率信息、文档频率信息即可对这些候选搜索结果进行排序,计算文档和查询的相似性,按照相似性得分由高到低排序输出,此即为搜索系统的部分内部流程。
6、单词词典
单词词典是倒排索引中非常重要的组成部分,它用来维护文档集合中出现过的所有单词的相关信息,同时用来记载某个单词对应的倒排列表在倒排文件中的位置信息。对单词词典进行构建和查找,常用的数据结构包括哈希加链表结构和树形词典结构。
6.1、哈希表加链表
如图为哈希表+链表的词典结构,主体部分是哈希表,每个哈希表项保存一个指针,指针指向冲突链表,在冲突链表里,相同哈希值的单词形成链表结构。 在建立索引的过程中,词典结构也会相应地被构建出来。比如在解析一个新文档的时候,对于某个在文档中出现的单词,首先利用哈希函数获得其哈希值,之后根据哈希值对应的哈希表项读取其中保存的指针,就找到了对应的冲突链表。如果冲突链表里已经存在这个单词,说明单词在之前解析的文档里已经出现过。如果在冲突链表里没有发现这个单词,说明该单词是首次碰到,则将其加入冲突链表里。通过这种方式,当文档集合内所有文档解析完毕时,相应的词典结构也就建立起来了。
在响应用户查询请求时,其过程与建立词典类似, 假设用户输入的查询请求为单词3,对这个单词进行哈希,定位到哈希表内的2号槽,从其保留的指针可以获得冲突链表,依次将单词3和冲突链表内的单词比较,发现单词3在冲突链表内,于是找到这个单词,之后可以读出这个单词对应的倒排列表来进行后续的工作,如果没有找到这个单词,说明文档集合内没有任何文档包含单词,则搜索结果为空。
6.2、树形结构
B树(或者B+树)是另外一种高效查找结构,B树与哈希方式查找不同,需要字典项能够按照大小排序。B树形成了层级查找结构,中间节点用于指出一定顺序范围的词典项目存储在哪个子树中,起到根据词典项比较大小进行导航的作用,最底层的叶子节点存储单词的地址信息,根据这个地址就可以提取出单词字符串。
7、倒排列表
倒排列表用来记录有哪些文档包含了A单词,在包含A单词的文档集合中,每个文档都会记录文档编号 (docId),A单词在这个文档中出现的次数 (tf),A单词在文档中哪些位置出现过 (position)等信息,这样与一个文档相关的信息被称做倒排索引项,包含A单词的一系列倒排索引项形成了列表结构,这就是A单词对应的倒排列表,在文档集合中出现过的所有单词及其对应的倒排列表组成了倒排索引。
在实际的搜索引擎系统中,并不存储倒排索引项中的实际文档编号,而文档编号差值,即倒排列表中相邻的两个倒排索引项文档编号的差值,主要原因是为了更好地对数据进行压缩,原始文档编号一般都是大数值,通过差值计算,就有效地将大数值转换为了小数值,而这有助于增加数据的压缩率。一般在索引构建过程中,可以保证倒排列表中后面出现的文档编号大于之前出现的文档编号,所以文档编号差值总是大于0的整数。如图,原始的3个文档编号分别是187、196和199,通过编号差值计算,在实际存储的时候就转化成了:187、9、3。
8、建立索引
索引结构如果建立好了,可以提高搜索的速度,那么给定一个文档集合,索引是如何建立起来的呢?
8.1、两遍遍历法
第一遍文档遍历 在第一遍扫描文档集合时,并没有立即开始建立索引,而是收集一些全局的统计信息。比如文档集合包含的文档个数N,文档集合内所包含的不同单词个数M,每个单词在多少个文档中出现过的信息DF。将所有单词对应的DF值全部相加,就可以知道建立最终索引所需的内存大小是多少,比如,一个单词对应的DF值如果是10,说明有10个文档包含这个单词,那么这个单词对应的倒排列表应该包含10项内容,每一项记载某个文档的文档ID和单词在该文档对应的出现次数TF。在获得了上述3类信息后,就可以知道最终索引的大小,于是在内存中分配足够大的空间,用来存储倒排索引内容。 如图,在内存中可以开辟连续存储区域,因为第一遍扫描已经获得了每个单词的DF信息,所以将连续存储区划分成不同大小的片段,词典内某个单词根据自己对应的DF信息,可以通过指针,指向属于自己的内存片段的起始位置和终止位置,将来在第二遍扫描时,这个单词对应的倒排列表信息会被填充进这个片段中。
第二遍文档遍历 在第二遍扫描的时候,开始真正建立每个单词的倒排列表信息,即对某个单词来说,获得包含这个单词的每个文档的文档ID,以及这个单词在文档中的出现次数TF,这样就可以不断填充第一遍扫描所分配的内存空间。当第二遍扫描结束的时候,分配的内存空间正好被填充满,而每个单词用指针所指向的内存区域“片段”,其起始位置和终止位置之间的数据就是这个单词对应的倒排列表。 经过两遍扫描完成索引建立后,即可将内存的倒排列表和词典信息写入磁盘,这样就完成了建立索引的过程。从上述流程可以看出,索引的构建完全是在内存中完成的,这就要求内存一定要足够大,否则如果文档集合太大,内存未必能够满足需求。从另外一个角度看,在建立索引的过程中,从磁盘读取文档并解析文档基本是最消耗时间的一个步骤,而两遍扫描法因为要对文档集合进行两遍遍历,所以从速度上不占优势,在实际中采用这种方法的系统并不常见。
8.2、排序法
排序法在建立索引的过程中,始终在内存中分配固定大小的空间,用来存放词典信息和索引的中间结果,当分配的空间被消耗光的时候,把中间结果写入磁盘,清空内存里中间结果所占空间,以用做下一轮存放索引中间结果的存储区。这种方法由于只需要固定大小的内存,所以可以对任意大小的文档集合建立索引。
① 读入文档后,对文档进行编号,赋予唯一的文档ID,并对文档内容解析。 ② 对于文档中出现的单词,通过查词典将单词转换为对应的单词ID,如果词典中没有这个单词,说明是第一次碰到,则赋予单词以唯一的单词ID并插入词典中,完成由单词映射为单词ID的过程。 ③ 对该文档内每个单词建立一个(单词ID、文档ID、单词频率)三元组,将这个三元组追加进中间结果存储区末尾。 ④ 如果文档内的所有单词都经过如此处理,形成三元组序列的形式,则该文档被处理完成,开始依次序处理下一文档,过程与此类似。 随着新的文档不断被处理完成,存储三元组集合的中间结果所占用的内存会越来越大,词典里包含的新单词也越来越多,当分配的内存定额被占满时,该方法对三元组中间结果进行排序。排序的原则是:主键是单词ID,即首先要按照单词ID由小到大排序;次键是文档ID,即在相同单词ID的情况下,按照文档ID由小到大排序。通过以上方式,三元组变为有序形式。为了腾出内存空间,将排好序的三元组写入磁盘临时文件中,这样就空出内存来进行后续文档的处理。这里需要注意的是:在建立索引的过程中,词典是一直存储在内存中的,每次清空内存只是将中间结果写入磁盘。随着处理文档的增加,词典占用的内存会逐渐增加,由于分配内存是固定大小,而词典占用内存越来越大,也就是说,越往后,可用来存储三元组的空间是越来越少的。 每一轮处理都会在磁盘产生一个对应的中间结果文件,当所有文档处理完成后,在磁盘中会有多个中间结果文件,为了产生最终的索引,需要将这些中间结果文件合并。
在合并中间结果的过程中,系统为每个中间结果文件在内存中开辟一个数据缓冲区,用来存放文件的部分数据。因为在形成中间结果文件前,已经按照单词ID和文档ID进行了排序,所以进入缓冲区的数据已经是有序的。合并过程中,将不同缓冲区中包含的同一个单词ID的三元组进行合并,如果某个单词ID的所有三元组全部合并完成,说明这个单词的倒排列表已经构建完成,则将其写入最终索引中,同时将各个缓冲区中对应这个单词ID的三元组内容清空,这样缓冲区就可以继续从中间结果文件中读入后续的三元组来进行下一个单词的三元组合并。当所有中间结果文件都依次被读入缓冲区,在合并完成后,就形成了最终的索引文件。
8.3、归并法
在分配的内存定额被消耗光时,排序法只是将中间结果写入磁盘,而词典信息一直在内存中进行维护,随着处理的文档越来越多,词典里包含的词典项越来越多,所以占用内存越来越大,导致后期中间结果可用内存越来越少。归并法对此做出了改进,即每次将内存中数据写入磁盘时,包括词典在内的所有中间结果信息都被写入磁盘,这样内存所有内容都可以被清空,后续建立索引可以使用全部的定额内存。 其整体流程和排序法大致相同,也是分为两个大的阶段,首先在内存里维护中间结果,当内存占满后,将内存数据写入磁盘临时文件,第二阶段对临时文件进行归并形成最终索引。
尽管从整体流程看,和排序法大致相同,但是在具体实现方式上有较大差异: ① 排序法在内存中存放的是词典信息和三元组数据,在建立索引的过程中,词典和三元组数据并没有直接的联系。而归并法则是在内存中建立一个完整的内存索引结构,相当于对目前处理的文档子集单独在内存中建立起了一整套倒排索引,和最终索引相比,其结构和形式是相同的,区别只是这个索引只是部分文档的索引而非全部文档的索引。 ② 在将中间结果写入磁盘临时文件时,归并法会将整个内存的倒排索引写入临时文件,对于某个单词的倒排列表在写入磁盘文件时,将词典项放在列表最前端,之后跟随相应的倒排列表,这样依次将单词和对应的倒排列表写入磁盘文件,随后彻底清空所占内存。而排序法只是将三元组数据排序后写入磁盘临时文件,词典作为一个映射表一直存储在内存中。 ③ 在最后的临时文件合并为最终索引的过程中,两者也有差异。排序法因为保存的是有序三元组信息,所以在合并时,是对同一单词的三元组依次进行合并;而归并法的临时文件则是每个单词对应的部分倒排列表,所以在合并时针对每个单词的倒排列表进行合并,形成这个单词的最终倒排列表。另外,归并法在最后的合并过程中形成最终的词典信息。
9、动态索引
提出问题:如果搜索引擎需要处理的文档集合是静态集合,那么在索引建立好之后,就可以一直用建好的索引响应用户查询请求。但是,在真实环境中,搜索引擎需要处理的文档集合往往是动态集合,即在建好初始的索引后,后续不断有新文档进入系统,同时原先的文档集合内有些文档可能被删除或者内容被更改,索引系统如何能够做到实时反映这种变化呢?动态索引可以实现这种实时性要求。 在这种动态索引中,有3个关键的索引结构:倒排索引、临时索引和已删除文档列表。
倒排索引就是对初始文档集合建立好的索引结构,一般单词词典存储在内存,对应的倒排列表存储在磁盘文件中。临时索引是在内存中实时建立的倒排索引,其结构和前述的倒排索引是一样的,区别在于词典和倒排列表都在内存中存储。当有新文档进入系统时,实时解析文档并将其追加进这个临时索引结构中。已删除文档列表则用来存储已被删除的文档的相应文档ID,形成一个文档ID列表。 这里需要注意的是:当一篇文档内容被更改,可以认为是旧文档先被删除,之后向系统内增加一篇新的文档,通过这种间接方式实现对内容更改的支持。当系统发现有新文档进入时,立即将其加入临时索引中。有文档被删除时,则将其加入删除文档队列。文档被更改时,则将原先文档放入删除队列,解析更改后的文档内容,并将其加入临时索引中。通过这种方式可以满足实时性的要求。 如果用户输入查询请求,则搜索引擎同时从倒排索引和临时索引中读取用户查询单词的倒排列表,找到包含用户查询的文档集合,并对两个结果进行合并,之后利用删除文档列表进行过滤,将搜索结果中那些已经被删除的文档从结果中过滤,形成最终的搜索结果,并返回给用户。这样就能够实现动态环境下的准实时搜索功能。
|