⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/shenmingxueIT/article/details/121843522 「shenmingik」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

索引

搜索引擎的索引其实是实现<关键词,文档>映射的具体的数据结构,其实现方式也是多种多样的:倒排索引、签名文件以及后缀树等等。实验证明倒排索引是最有效的实现方式,同时也是当前搜索引擎广泛应用的索引技术。

倒排索引

平常我们想要查询一个关键词,最简单的思路肯定是挨个每个文档查看这个文档是否存在这个关键词,这就是建立<文档,关键词>这样映射的索引。详情图示如下,大概解释一下,这里的网页A中假设就是这篇博客,后面跟的是这篇博客中的关键词。

这样我们想查找索引这个关键词的时候,我们就需要先查网页A,然后对它的关键词一个个遍历,直到遍历完所有的网页。这样的过程时间复杂度是O(n2),即便后面的关键词列表我们用的是红黑树等高级数据结构,那样最快的时间复杂度则是O(nlogn)。这样的技术应用在搜索引擎这种特别依赖反应时间来满足用户需求的应用上就显得非常吃力,这也是为什么要采取倒排索引的原因。倒排索引会建立<关键词,文档> 的映射,如下图所示:

这样我们想要搜索关键词的时候,只需要经过一遍O(n)的过程就能得到我们想要的答案。如果用更好的数据结构且不在乎价格的话,甚至可以直接用哈希得到O(1)的效果。而实际上的数据结构可能并不是图示这样,其一般会采用这样的数据结构:

//定义倒排索引
struct InvertedList
{
list<IndexElement> inverted_list_;
};

//定义倒排索引里面的每个元素
struct IndexElement
{
int word_id_; //单词id
string word_; //单词名称
list<DocumentInfo> documents_; //包含单词的文档
};

struct DocumentInfo
{
int document_id_; //文档id
int word_count_; //此文档中出现这个单词的次数
vector<int> word_index_; //每次这个单词出现的位置信息
};

可以看到,我们这里多加了word_count_以及word_index_这两个变量,这是因为文档中单词的频率信息是在计算网页相关度中非常重要的一个指标。而词汇出现的位置对于短语查询来说非常有用,这两个都会在后面的博客中进行介绍。

:实际上并不村存储倒排索引项中实际文档编号,而是代之以文档编号的差值,这样的目的其实是省空间,更好的进行数据的压缩。

单词词典

单词词典是倒排索引中非常重要的组成部分,其用来维护文档集合中出现的所有单词的相关信息。实现方式多种多样,常有的就是哈希+链表、B+树、红黑树等等。

  • 哈希+链表形式:这种形式会形成如下图一般的单词词典,其是单词经过一个哈希函数得到一个下标。假设索引这个单词单词经过如下函数运算得到下标位0,我们就把这个倒排索引放到这个哈希桶里面,但是这个桶里面已经有MySQL这个单词了,这就产生了哈希冲突,这也就是我们为什么需要链表的原因了。另外,哈希+链表的时间复杂度是O(1)+O(m),如果想要进一步优化,那么我们可以借鉴Java的思想,当链表长度超过阈值的时候就改用红黑树。

int get_index(string word)
{
return hash(word);
}

  • B+树:B+数是一种常用的查找结构,其在MySQL中可谓是大放异彩。其和哈希+链表不同的是,其需要单词能够按照大小排序。对于其,由于大家应该挺熟悉的,这里就不对其多加解释了。

动态索引

在搜索引擎的应用中,网页往往不是静态的,其会是动态变化的。搜索引擎为了满足这种动态变化的需求就不得不使用三个工具:倒排索引、临时索引和已删除文档列表。

当搜索引擎系统发现有新的文档被爬虫下载,会先将其加入临时索引中;如果有文档被删除,则将其加入删除文档队列;文档被更改的话,将原来的文档加入删除文档队列,更改后的文档加入临时文档队列。

如果是用户的查询请求的话,就会从临时队列和倒排索引中都进行查询,对两个结果进行合并,之后再查询删除文档队列,进行一个文档过滤。

索引的建立

两遍文档遍历法

这个方法需要对文档集合进行两遍扫描。第一遍扫描的时候,并没有立即开始创建索引,而是先收集一些全局的统计信息:网页集合中网页个数WebPageNum、网页集合内包含的单词数量WordCount,每个单词在多少文档中出现过的次数DF。得到这些信息的目的是为了知道我们需要开辟多大的内存,需要什么资源

第二遍遍历就是真正建立倒排索引的时候,我们先会获取包含这个单词的每个文档的文档ID,以及这个单词在文档中出现的次数。不断重复这个过程,知道填满我们申请的内存空间。

两遍文档遍历法是非常消耗IO资源的,所以我们往往采用在内存中去构建索引,这要求我们要有钱整个大的内存。

归并法

归并法是两遍文档遍历法的改进,该方法在建立索引的过程中,始终在内存中分配固定大小的空间,用来存放词典信息和所以对中间结果,当分配的空间被消耗光的时候,就把中间写过写入磁盘,和磁盘中间的结果进行合并。这样内存就可以反复复用,比较省钱。

索引更新

像在内存中维护的索引往往都会到达内存的上线,这个时候我们就要考虑把索引合并到磁盘上,以释放内存空间容纳新的电脑。常用的更新策略如下:

  • 完全重建策略:当新增文档达到一定数量的时候,我们将新增文档和原先的文档进行合并,在这个过程对所有文档重新建立索引。这个过程由于时间较长,在索引重建的过程中,内存仍然维护合并前的索引对查询做出响应。
  • 再合并策略:和完全重建所有文档不同,再合并针对的是新增文档,对其建立索引之后,把新索引和老索引进行合并
  • 原地更新策略:这个是对再合并策略的改进。策略假设就是老索引是没有变化的,这样对于新文档,我们直接把它加入到老索引之中,这样就可以省去在内存建立新文档索引的过程。

多字段索引

在搜索引擎实际应用中,由于网页中是有一定结构的:标题,引用,正文等等。如果说平常索引是判断此文档是否出现过关键词,那么多索引是判断此关键词是在文档的哪一部分出现。而对于这种多字段类型的文档,搜索引擎实现方式主要有以下几种:

  • 多索引方式:其针对每个不同的字段,分别建立一个索引,当用户指定某个字段作为搜索范围的时候,可以从相应的索引中提取结果;当用户没有指定特定字段的时候,搜索引擎会对所有字段都进行查找并合并多个字段的相关性得分,根据得分给出搜索结构。
  • 倒排列表:假设文档包含三个字段:标题、引用、正文。那么我们可以用三个比特位进行标识,这样就会形成如下图的倒排索引结构,这里每个倒排索引项格式如此<DocumentID,WordCount,Filed,Position>,那么第一项就可以表示关键词索引在198号文档中出现了两次,位置是引用和正文两个位置。
  • 扩展列表方式:这个是实际中用的比较多的多字段索引方式,其为每个字段建立一个扩展列表。而倒排索引项会记录<DocumentID,WordCount,Position>这么一个三元关系,但是其还包含多个个扩展项中,其中记录<DocumentID,FIledRange>这么一个关系,具体如下:假设我们搜索关键词索引,这个时候根据倒排列表项,我们知道在198号文档中出现了两次,一次在2号位置,一次在10号位置。根据标题扩展列表,我们知道标题返回是1号~6号位置,那么2号位置在标题中。根据正文扩展列表,我们知道10号位置是正文部分,这样就达到了多索引的目的。虽然这种方式可能有两次查询的过程,但是它可以服用单索引,这也是常用的原因。
文章目录
  1. 1. 索引
  2. 2. 倒排索引
    1. 2.1. 单词词典
    2. 2.2. 动态索引
    3. 2.3. 索引的建立
      1. 2.3.1. 两遍文档遍历法
      2. 2.3.2. 归并法
    4. 2.4. 索引更新
  3. 3. 多字段索引