【Java】搜索引擎设计:信息搜索怎么避免大海捞针?

一、内容分析

我们准备开发一个针对全网内容的搜索引擎,产品名称为“Bingoo”。

Bingoo的主要技术挑战包括:

  1. 针对爬虫获取的海量数据,如何高效地进行数据管理;
  2. 当用户输入搜索词的时候,如何快速查找包含搜索词的网页内容;
  3. 如何对搜索结果的网页内容进行排序,使排在搜索结果列表前面的网页,正好是用户期望看到的内容。

12.1 概要设计

一个完整的搜索引擎包括分布式爬虫、索引构造器、网页排名算法、搜索器等组成部分,Bingoo的系统架构如下。

分布式爬虫通过存储服务器将爬取的网页存储到分布式文件集群HDFS,为了提高存储效率,网页将被压缩后存储。存储的时候,网页一个文件挨着一个文件地连续存储,存储格式如下。

每个网页被分配得到一个8字节长整型docID,docID之后用2个字节记录网页的URL的长度,之后4个字节记录压缩后网页内容数据的长度,所有存储的网页的头14个字节都是同样的格式。之后存储URL字符串和压缩后的网页内容数据。读取文件的时候,先读14个字节的头信息,根据头信息中记录的URL长度和数据长度,再读取对应长度的URL和网页内容数据。

搜索引擎能够快速查找的核心就是利用索引,根据用户的查询内容查找匹配的索引,根据索引列表构建结果页面。索引的构造主要通过索引构造器完成,索引构造器读取HDFS中的网页内容,解压缩后提取网页中的单词,构建一个“docID->单词列表”的正排索引。然后,索引构造器再根据这个正排索引构建一个“单词->docID列表”的倒排索引,“docID列表”就是包含了这个单词的所有网页列表。利用这个倒排索引,搜索器可以快速获得用户搜索词对应的所有网页。

网页中所有的单词构成了一个词典,实际上,词典就是一个Hash表,key就是单词,value就是倒排索引的网页列表。虽然互联网页的内容非常庞大,但是使用到的单词其实是非常有限的。根据Google的报告,256M内存可以存放1400万个单词,这差不多就是英文单词的全部了。

在构建索引的过程中,因为要不断修改索引列表,还要进行排序,所以,有很多操作是需要进行加锁同步完成的。对于海量的互联网页的计算,这样的索引构建速度太慢了。因此我们设计了64个索引桶,根据docID取模,将不同网页分配到不同的桶中,在每个桶中分别进行索引构建,通过并行计算来加快索引处理速度。

索引构造器在读取网页内容、构造索引的时候,还会调用URL提取器,将网页中包含的URL提取出来,构建一个链接关系表。链接关系表的格式是“docID->docID”,前一个docID是当前网页的docID,后一个docID是当前网页中包含的URL对应的docID。一个网页中会包含很多个URL,也就是会构建出很多个这样的链接关系。后面会利用这个链接关系表,使用PageRank排名算法对所有网页进行打分排名,当索引器得到查找的网页列表时,利用PageRank值进行排名,最终呈现给用户,保证用户最先看到的网页是最接近用户期望的结果页面。

12.2 详细设计

一个运行良好的搜索引擎的核心技术就是索引和排名,所以我们将分别说明这两种技术要点。

12.2.1 索引

索引构造器从HDFS读取网页内容后,解析每个页面,提取网页里的每个单词。如果是英文,那么每个单词都用空格分隔,比较容易;如果是中文,需要使用中文分词器才能提取到每个单词,比如“高并发架构”,使用中文分词器得到的就是“高并发”、“架构”两个词。

首先,索引构造器将所有的网页都读取完,构建出所有的“docID->单词列表”正排索引。

然后遍历所有的正排索引,再按照“单词→docID列表”的方式组织起来,就是倒排索引了。

我们这个例子中只有两个单词、7个网页。事实上,Bingoo数以千亿的网页就是这样通过倒排索引组织起来的,网页数量虽然庞大,但是单词数却是比较有限的。所以,整个倒排索引的大小相比于网页数量要小得多。Bingoo将每个单词对应的网页列表存储在硬盘中,而单词则存储在内存的Hash表,也就是词典中,词典示例:

对于部分热门的单词,整个网页列表也可以存储在内存中,相当于缓存。在词典中,每个单词记录下硬盘或者内存中的网页列表地址,这样只要搜索单词,就可以快速得到对应的网页地址列表。Bingoo根据列表中的网页编号docID,展示对应的网页信息摘要,就完成了海量数据的快速检索。

如果用户的搜索词正好是一个单词,比如“高并发”,那么直接查找词典,得到网页列表就完成查找了。但是如果用户输入的是一个句话,那么搜索器就需要将这句话拆分成几个单词,然后分别查找倒排索引。这样的话,得到的就是几个网页列表,还需要对这几个网页列表求交集,才能得到最终的结果列表。

比如,用户输入“高并发架构”进行搜索,那么搜索器就会拆分成两个词:“高并发”、“架构”,得到两个倒排索引:

高并发->2,3,5,7

架构->1,2,4

需要对这两个倒排索引求交集,也就是同时包含“高并发”和“架构”的网页才是符合搜索要求的结果,最终的交集结果应该是只有一篇网页,即docID为2的满足要求。

列表求交集最简单的实现就是双层for循环,但是这种算法的时间复杂度是O(n^2),我们的网页列表长度(n)可能有千万级甚至更高,这样的计算效率太低。

一个改进的算法是拉链法,我们将网页列表先按照docID的编号进行排序,得到的就是这样两个有序链表:

同时遍历两个链表,如果其中一个链表当前指向的元素小于另一个链表当前指向的元素,那么这个链表就继续向前遍历;如果两个链表当前指向的元素相同,该元素就是交集元素,记录在结果列表中;依此继续向前遍历,直到其中一个链表指向自己的尾部nil。

拉链法的时间复杂度是O(2n),远优于双层循环。但是对于千万级的数据而言,还是太慢。我们还可以采用数据分片的方式进行并行计算,以实现性能优化。

比如,我们的docID分布在[0, 1万亿)区间,而每个倒排索引链表平均包含1千万个docID。我们把所有的docID按照1千亿进行数据分片,就会得到10个区间[0, 1千亿)[1千亿,2千亿)……[9千亿,1万亿)。每个倒排索引链表大致均匀分布在这10个区间,我们就可以依照这10个区间范围,将每个要遍历的链表切分为10片,每片大约包含1百万个docID。两个链表只在自己对应的分片内求交集即可,因此我们可以启动10个线程对10个分片进行并行计算,速度可提高10倍。

事实上,两个1千万长度的链表求交集,最终的结果可能不过几万,也就是说,大部分的比较都是不相等的。比如下面的例子。

第一个链表遍历到自己的最后一个元素,才和第二个链表的第一个元素相同。那么第一个链表能不能跳过前面那些元素呢?很自然,我们想到可以用跳表来实现,如下图。

跳表实际上是在链表上构建多级索引,在索引上遍历可以跳过底层的部分数据,我们可以利用这个特性实现链表的跳跃式比较,加快计算速度。使用跳表的交集计算时间复杂度大约是O(log(n))。

此外,虽然搜索引擎利用倒排索引已经能很快得到搜索结果了,但搜索引擎应用还会使用缓存对搜索进行加速,将整个搜索词对应的搜索结果直接放入缓存,以减少倒排索引的访问压力,以及不必要的集合计算。

12.2.2 PageRank排名算法

Bingoo使用PageRank算法进行网页结果排名,以保证搜索结果更符合用户期待。

PageRank算法会根据网页的链接关系给网页打分。如果一个网页A包含另一个网页B的超链接,那么就认为A网页给B网页投了一票。一个网页得到的投票越多,说明自己越重要;越重要的网页给自己投票,自己也越重要。

PageRank算法就是计算每个网页的PageRank值,最终的搜索结果也是以网页的PageRank值排序,展示给用户。事实证明,这种排名方法非常有效,PageRank值更高的网页,确实更满足用户的搜索期望。

以下面四个网页A、B、C、D举例,带箭头的线条表示链接。

B网页包含了A、D两个页面的超链接,相当于B网页给A、D每个页面投了一票,如果初始的时候,所有页面都是1分,那么经过这次投票后,B给了A和D每个页面1/2分(B包含了A、D两个超链接,所以每个投票值1/2分),自己从C页面得到1/3分(C包含了A、B、D三个页面的超链接,每个投票值1/3分)。

而A页面则从B、C、D分别得到1/2,1/3,1分。用公式表示就是

\(\\small PR(A) = \\frac{PR(B)}{2}+\\frac{PR(C)}{3}+\\frac{PR(D)}{1}\)

等号左边是经过一次投票后,A页面的PageRank分值;等号右边每一项的分子是包含A页面超链接的页面的PageRank分值,分母是该页面包含的超链接数目。

这样经过一次计算后,每个页面的PageRank分值就会重新分配,重复同样的算法过程,经过几次计算后,根据每个页面PageRank分值进行排序,就得到一个页面重要程度的排名表。根据这个排名表,将用户搜索出来的网页结果排序,排在前面的通常也正是用户期待的结果。

但是这个算法还有个问题,如果某个页面只包含指向自己的超链接,其他页面不断给它送分,而自己一分不出,随着计算执行次数越多,它的分值也就越高,这显然是不合理的。这种情况就像下图所示的,A页面只包含指向自己的超链接。

解决方案是,设想浏览一个页面的时候,有一定概率不是点击超链接,而是在地址栏输入一个URL访问其他页面,表示在公式上,就是

\(\\small PR(A) = \\alpha(\\frac{PR(B)}{2}+\\frac{PR(C)}{3}+\\frac{PR(D)}{1})+\\frac{(1-\\alpha)}{4}\)

上面\(\\small (1-\\alpha)\)就是跳转到其他任何页面的概率,通常取经验值0.15(即\(\\small \\alpha\) 为0.85),因为有一定概率输入的URL是自己的,所以加上上面公式最后一项,其中分母4表示所有网页的总数。

那么对于N个网页,任何一个页面\(\\small P_{i}\)的PageRank计算公式如下:

\(\\small PageRank(P_{i})=\\alpha \\sum_{P_{j}\\in M(P_{i})}^{}{\\frac{PageRank(P_{j})}{L(P_{j})}} + \\frac{1-\\alpha}{N}\)

公式中,\(\\small P_{j}\\in M(P_{i})\) 表示所有包含有\(\\small P_{i}\)超链接的\(\\small P_{j}\),\(\\small L(P_{j})\)表示\(\\small P_{j}\)页面包含的超链接数,N表示所有的网页总和。由于Bingoo要对全世界的网页进行排名,所以这里的N是一个万亿级的数字。

计算开始的时候,将所有页面的PageRank值设为1,带入上面公式计算,每个页面都得到一个新的PageRank值。再把这些新的PageRank值带入上面的公式,继续得到更新的PageRank值,如此迭代计算,直到所有页面的PageRank值几乎不再有大的变化才停止。

二、粉丝福利

我根据我从小白到架构师多年的学习经验整理出来了一份50W字面试解析文档、简历模板、学习路线图、java必看学习书籍 、 需要的小伙伴斯我一下,或者评论区扣“求分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/784884.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

YOLOv10改进 | EIoU、SIoU、WIoU、DIoU、FocusIoU等二十余种损失函数

一、本文介绍 这篇文章介绍了YOLOv10的重大改进,特别是在损失函数方面的创新。它不仅包括了多种IoU损失函数的改进和变体,如SIoU、WIoU、GIoU、DIoU、EIOU、CIoU,还融合了“Focus”思想,创造了一系列新的损失函数。这些组合形式的…

深度解密Spark性能优化之道课程

课程通过实战案例解析和性能调优技巧的讲解,帮助学员提升大数据处理系统的性能和效率。课程内容涵盖了Spark性能调优的各个方面,包括内存管理、并行度设置、数据倾斜处理、Shuffle调优、资源配置等关键技术和策略。学员将通过实际案例的演示和分析&#…

Caterpillar on a Tree

首先一个很显然的地方就是使用传送门肯定是在叶子节点使用,我们来考虑一下整个过程是怎么样的 为了方便,我们不妨假设可以传送回根节点\(k1\)次,然后要求最后回到根节点 我们先从根节点走到某一个叶子结点,然后再从这个叶子节点走…

Open3D 计算点云的平均密度

目录 一、概述 1.1基于领域密度计算原理 1.2应用 二、代码实现 三、实现效果 2.1点云显示 2.2密度计算结果 一、概述 在点云处理中,点的密度通常表示为某个点周围一定区域内的点的数量。高密度区域表示点云较密集,低密度区域表示点云较稀疏。计算…

Kubernetes基于helm部署jenkins

Kubernetes基于helm安装jenkins jenkins支持war包、docker镜像、系统安装包、helm安装等。在Kubernetes上使用Helm安装Jenkins可以简化安装和管理Jenkins的过程。同时借助Kubernetes,jenkins可以实现工作节点的动态调用伸缩,更好的提高资源利用率。通过…

拆分pdf文件最简单的方法,pdf怎么拆成一页一张

在数字化的时代,pdf文件已经成为我们日常办公、学习不可或缺的文档格式。然而,有时候我们可能需要对一个大的pdf文件进行拆分,以方便管理和分享。那么,如何将一个pdf文件拆分成多个pdf呢?本文将为你推荐一种好用的拆分…

HNTs-g-PEG-CDs-Biotin NPs;碳量子点修饰接枝生物素化的羟基磷灰石纳米管

HNTs-g-PEG-CDs-Biotin NPs,即碳量子点修饰接枝生物素化的羟基磷灰石纳米管,是一种结合了多种先进材料特性的纳米复合材料。以下是对该材料的详细分析: 一、组成成分及特性 羟基磷灰石纳米管(HNTs): 羟基磷…

多用户挂售转卖竞拍闪拍商城系统/NFT数藏系统/后端PHP+前端UNIAPP源码带教程(亲测源码)

挂售转卖竞拍商城系统源码/竞拍系统/转拍闪拍系统/后端PHP前端UNiapp源码 亲测可用 1、后台管理:系统管理员通过后台可以轻松添加商品进行挂单。这包括商品的详细信息,如名称、描述、价格、库存等。 商品展示:挂单后的商品会在商城前端进行…

22.状态机设计--可乐机设计(投币三元出一瓶可乐)

理论知识: (1)状态机简写为FSM(Finite State Machine),也称为同步有限状态机。同步是指状态的变化都是在时钟的边沿发送变化,有限值得是状态的个数是可数的。 (2)分类&…

Xilinx FPGA DDR4 接口的 PCB 准则

目录 1. 简介 1.1 FPGA-MIG 与 DDR4 介绍 1.2 DDR4 信号介绍 1.2.1 Clock Signals 1.2.2 Address and Command Signals 1.2.3 Address and Command Signals 1.2.4 Data Signals 1.2.5 Other Signals 2. 通用存储器布线准则 3. Xilinx FPGA-MIG 的 PCB 准则 3.1 引脚…

ElasticSearch第一天

学习目标: 能够理解ElasticSearch的作用能够安装ElasticSearch服务能够理解ElasticSearch的相关概念能够使用Postman发送Restful请求操作ElasticSearch能够理解分词器的作用能够使用ElasticSearch集成IK分词器能够完成es集群搭建 第一章 ElasticSearch简介 1.1 什么…

【Unity2D 2022:】制作NPC

一、创建NPC角色 1. 创建JambiNPC并同时创建Jambi站立动画 (1)点击第一张图片,按住shift不松,再选中后两张图片,拖到层级面板中 (2)将动画资源文件保存到Animation Clips文件夹中 (…

YOLOv10改进 | 损失函数篇 | InnerIoU、InnerSIoU、InnerWIoU、FocusIoU等损失函数

一、本文介绍 本文给大家带来的是YOLOv10最新改进,为大家带来最近新提出的InnerIoU的内容同时用Inner的思想结合SIoU、WIoU、GIoU、DIoU、EIOU、CIoU等损失函数,形成 InnerIoU、InnerSIoU、InnerWIoU、等新版本损失函数,同时还结合了Focus和…

PHP源码:线上书店系统(附管理后台+前台)

一. 前言 今天小编给大家带来了一款可学习,可商用的,线上书店 源码,支持二开,无加密。项目的内容是销售书籍,可以扩展成pdf,文档等一些虚拟产品的销售。 详细界面和功能见下面视频演示。 二. 视频演示 线…

一个php文件怎么实现联系表单自动发送邮件

学习PHP:如何编写一个自动发送邮件的联系表单处理器? 无论是反馈意见、业务咨询,还是技术支持,联系表单都能为用户提供便捷的交流途径。AokSend将探讨如何通过一个PHP文件实现联系表单的自动发送邮件功能。 php文件:…

【豆包AI】北京春田知韵

看到有国内AI上线了,网络信息那么多,我该怎么找它的官网呢? 找官方网站3步 1百度 关于抖音豆包的网站是哪个?【www.doubao.com】 豆包属于哪个公司?【北京春田知韵科技有限公司】 www.doubao.com 2查询备案号 PC版本的安装…

外卖跑腿小程序APP软件成品系统和软甲开发APP小程序可进行封装打包

,用户友好界面设计 首先,外卖施限小程序APP应具备用户友好的界面设计。界面应简洁明了,让用户能够方便快捷地议,览和选择所需的菜品或服务。系统应提供详细的菜品描述、价格透明,并允许用户根据口味、偏好进行结进和排序。此外&am…

如何保证队列消息的有序性

要保证队列消息的有序性,你可以采取以下几种策略: 1.单一生产者和消费者:确保只有一个生产者向队列发送消息,以及只有一个消费者从队列接收消息,这样可以保证消息的顺序。 2.使用有序集合:如果你使用Redis&…

GPU发展史(二):改变游戏规则的3Dfx Voodoo

小伙伴们,大家好呀,我是老猫。 在上一篇GPU发展史(一)文章中,我们介绍了1976-1995期间早期显卡的发展故事,今天我们将介绍在1995-1999年这段时间显卡的故事,而这段故事的主角就是——3Dfx 提起…

在idea中查看某个接口的所有实现类图

一、选中某个接口右键 ---> Diagrams ---> show Diagrams,然后就会进入一个新的 tab 页; 二、然后在出来的图上选中某个接口右键 ---> show Implementations,就会显示选中接口的所有实现类列表; 三、最后 ctrl A 全部选…