【Java】搜索引擎设计:信息搜索怎么避免大海捞针?
一、内容分析
我们准备开发一个针对全网内容的搜索引擎,产品名称为“Bingoo”。
Bingoo的主要技术挑战包括:
- 针对爬虫获取的海量数据,如何高效地进行数据管理;
- 当用户输入搜索词的时候,如何快速查找包含搜索词的网页内容;
- 如何对搜索结果的网页内容进行排序,使排在搜索结果列表前面的网页,正好是用户期望看到的内容。
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必看学习书籍 、 需要的小伙伴斯我一下,或者评论区扣“求分享”

相关文章:
【Java】搜索引擎设计:信息搜索怎么避免大海捞针?
一、内容分析 我们准备开发一个针对全网内容的搜索引擎,产品名称为“Bingoo”。 Bingoo的主要技术挑战包括: 针对爬虫获取的海量数据,如何高效地进行数据管理;当用户输入搜索词的时候,如何快速查找包含搜索词的网页…...
【Python】ModuleNotFoundError: No module named ‘distutils.util‘ bug fix
【Python】ModuleNotFoundError: No module named distutils.util bug fix 1. error like this2. how to fix why this error occured , because i remove the origin version python of ubuntu of 20.04. then the system trapped in tty1 , you must make sure the laptop li…...
痉挛性斜颈对生活有哪些影响?
痉挛性斜颈,这个名字听起来可能并不熟悉,但它实际上是一种神经系统疾病,影响着全球数百万人的生活质量。它以一种无法控制的方式,使患者的颈部肌肉发生不自主的收缩,导致头部姿势异常。对于患者来说,痉挛性…...
Javassist 修改 jar 包里的 class 文件
前言 Javassist 是一个用于处理 Java 字节码的类库,可以用以修改 class 文件或 jar 包里的 class 文件。 简单来说我们用Java编写的代码是放在 java 格式的代码文件里,在编译的时候会编译为 class 格式的字节码文件,然后一般所有 class 文件…...
交换机的二三层原理
相同VLAN的交换机交换原理(二层交换原理): 交换机收到数据帧,首先会检查数据帧的VLAN标签和目标MAC,若属于相同VLAN,且该目标MAC在本地MAC表中,则直接根据出接口进行数据转发 不同VLAN的交换机…...
HarmonyOS ArkUi 字符串<展开/收起>功能
效果图: 官方API: ohos.measure (文本计算) 方式一 measure.measureTextSize 跟方式二使用一样,只是API调用不同,可仔细查看官网方式二 API 12 import { display, promptAction } from kit.ArkUI import { MeasureUtils } fr…...
Lianwei 安全周报|2024.07.09
新的一周又开始了,以下是本周「Lianwei周报」,我们总结推荐了本周的政策/标准/指南最新动态、热点资讯和安全事件,保证大家不错过本周的每一个重点! 政策/标准/指南最新动态 01 《数字中国发展报告(2023年)…...
火遍全网的15个Python的实战项目,你该不会还不知道怎么用吧!
经常听到有朋友说,学习编程是一件非常枯燥无味的事情。其实,大家有没有认真想过,可能是我们的学习方法不对? 比方说,你有没有想过,可以通过打游戏来学编程? 今天我想跟大家分享几个Python小游…...
快速使用BRTR公式出具的大模型Prompt提示语
Role:文章模仿大师 Background: 你是一位文章模仿大师,擅长分析文章风格并进行模仿创作。老板常让你学习他人文章后进行模仿创作。 Attention: 请专注在文章模仿任务上,提供高质量的输出。 Profile: Author: 一博Version: 1.0Language: 中文Descri…...
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 Control Signals 1.2.4 Data Signals 1.2.5 Other Signals 2. 通用存储器布线准则 3. Xilinx FPGA-MIG 的 PCB 准则 3.1 引脚配置 3.1.1 …...
神经网络 | Transformer 基本原理
目录 1 为什么使用 Transformer?2 Attention 注意力机制2.1 什么是 Q、K、V 矩阵?2.2 Attention Value 计算流程2.3 Self-Attention 自注意力机制2.3 Multi-Head Attention 多头注意力机制 3 Transformer 模型架构3.1 Positional Encoding 位置编…...
浅析 VO、DTO、DO、PO 的概念
文章目录 I 浅析 VO、DTO、DO、PO1.1 概念1.2 模型1.3 VO与DTO的区别I 浅析 VO、DTO、DO、PO 1.1 概念 VO(View Object) 视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。DTO(Data Transfer Object): 数据传输对象,这个概念来源于J2EE的设…...
7.8 CompletableFuture
Future 接口理论知识复习 Future 接口(FutureTask 实现类)定义了操作异步任务执行的一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。 比如主线程让一个子线程去执行任务,子线…...
iPad锁屏密码忘记怎么办?有什么方法可以解锁?
当我们在日常使用iPad时,偶尔可能会遇到忘记锁屏密码的尴尬情况。这时,不必过于担心,因为有多种方法可以帮助您解锁iPad。接下来,小编将为您详细介绍这些解决方案。 一、使用iCloud的“查找我的iPhone”功能 如果你曾经启用了“查…...
了解并缓解 IP 欺骗攻击
欺骗是黑客用来未经授权访问计算机或网络的一种网络攻击,IP 欺骗是其他欺骗方法中最常见的欺骗类型。通过 IP 欺骗,攻击者可以隐藏 IP 数据包的真实来源,使攻击来源难以知晓。一旦访问网络或设备/主机,网络犯罪分子通常会挖掘其中…...
java LogUtil输出日志打日志的class文件内具体方法和行号
最近琢磨怎么把日志打的更清晰,方便查找问题,又不需要在每个class内都创建Logger对象,还带上不同的颜色做区分,简直不要太爽。利用堆栈的方向顺序拿到日志的class问题。看效果,直接上代码。 1、demo test 2、输出效果…...
02. Hibernate 初体验之持久化对象
1. 前言 本节课程让我们一起体验 Hibernate 的魅力!编写第一个基于 Hibernate 的实例程序。 在本节课程中,你将学到 : Hibernate 的版本发展史;持久化对象的特点。 为了更好地讲解这个内容,这个初体验案例分上下 2…...
MySQL超详细学习教程,2023年硬核学习路线
文章目录 前言1. 数据库的相关概念1.1 数据1.2 数据库1.3 数据库管理系统1.4 数据库系统1.5 SQL 2. MySQL数据库2.1 MySQL安装2.2 MySQL配置2.2.1 添加环境变量2.2.2 新建配置文件2.2.3 初始化MySQL2.2.4 注册MySQL服务2.2.5 启动MySQL服务 2.3 MySQL登录和退出2.4 MySQL卸载2.…...
初识SpringBoot
1.Maven Maven是⼀个项⽬管理⼯具, 通过pom.xml⽂件的配置获取jar包,⽽不⽤⼿动去添加jar包 主要功能 项⽬构建管理依赖 构建Maven项目 1.1项目构建 Maven 提供了标准的,跨平台(Linux, Windows, MacOS等)的⾃动化项⽬构建⽅式 当我们开发了⼀个项⽬之后, 代…...
Qt之元对象系统
Qt的元对象系统提供了信号和槽机制(用于对象间的通信)、运行时类型信息和动态属性系统。 元对象系统基于三个要素: 1、QObject类为那些可以利用元对象系统的对象提供了一个基类。 2、在类声明中使用Q_OBJECT宏用于启用元对象特性,…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
