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

news2024/11/13 15:22:45

一、内容分析

我们准备开发一个针对全网内容的搜索引擎,产品名称为“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.coloradmin.cn/o/1911474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【代码随想录】【算法训练营】【第59天】 [卡码110]字符串接龙 [卡码105]有向图的完全可达性 [卡码106]岛屿的周长

前言 思路及算法思维,指路 代码随想录。 题目来自 卡码网。 day 59,周五,继续ding~ 题目详情 [卡码110] 字符串接龙 题目描述 卡码110 字符串接龙 解题思路 前提: 思路: 重点: 代码实现 C语言 […

深度整合全球资源,分贝通打造高效、合规的海外差旅管理平台

在全球化商业活动的背景下,中国企业出海已成为常态。然而,随着海外差旅市场的全面增长,企业在海外支出管理上面临诸多挑战。据2023年数据显示,分贝通出海差旅业务GMV同比增长高达500倍,这一增长背后隐藏着企业对于更省钱、更高效管控方式的迫切需求。 面对与日俱增的开支,企业开…

自动化办公004—有效解决ChatGPT数学公式无法复制到Office、MarkDown的问题

ChatGPT在输出数学公式的时候是正常的,但是粘贴到word、粘贴到MarkDown笔记里就直接变成了一堆代码,其实是有办法解决的,本篇文章主要解决一下无法复制问题 目录 一、问题复现二、效果展示1. 测试案例2. 开始转换(1)运…

【保姆级介绍下C语言中的运算符的优先级】

🌈个人主页: 程序员不想敲代码啊 🏆CSDN优质创作者,CSDN实力新星,CSDN博客专家 👍点赞⭐评论⭐收藏 🤝希望本文对您有所裨益,如有不足之处,欢迎在评论区提出指正,让我们共同学习、交流进步! 🧇C语言中的运算符的优先级 🧇C语言中的运算符的优先级决定了表达…

2024-07-09 Linux 使用gpio-keyboard标准方式获取按键事件实例代码

一、kernel dts 配置修改: diff --git a/sysdrv/source/kernel/arch/arm/boot/dts/rv1106g-evb1-v11.dts b/sysdrv/source/kernel/arch/arm/boot/dts/rv1106g-evb1-v11.dts index d0d059a3b..584f3a00a 100755 --- a/sysdrv/source/kernel/arch/arm/boot/dts/rv110…

Hospital Management Startup 1.0 SQL 注入漏洞(CVE-2022-23366)

前言 CVE-2022-23366是一个影响HMS v1.0的SQL注入漏洞。该漏洞存在于patientlogin.php文件中,允许攻击者通过特定的SQL注入来获取或修改数据库中的敏感信息。 具体来说,攻击者可以通过向patientlogin.php发送恶意构造的SQL语句来绕过身份验证&#xff…

LabVIEW开发阀门自动校准装置

1. 装置概述与目标 在工业和实验室环境中,阀门的准确性和稳定性对于流体控制和实验数据的可靠性非常重要。LabVIEW可以作为开发阀门自动校准装置的理想工具,提供高度可定制化的解决方案。 2. 硬件与设备选择 型号选择:为了实现阀门自动校准…

三维点云配准 -- ICP 算法原理及推导

三维点云配准 -- ICP 算法原理及推导 - 知乎 (zhihu.com) 三维点云配准 -- ICP 算法 | Yilins Blog Alex Segal - Research - Generalized-ICP (ox.ac.uk)

Redisson分布式锁、可重入锁

介绍Redisson 什么是 Redisson?来自于官网上的描述内容如下! Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格客户端(In-Memory Data Grid)。它不仅提供了一系列的 redis 常用数据结构命令服务,还提供了…

线程池案例

秒杀 需求 10个礼物20个客户抢随机10个客户获取礼物&#xff0c;另外10无法获取礼物 任务类 记得给共享资源加锁 public class MyTask implements Runnable{// 礼物列表private ArrayList<String> gifts ;// 用户名private String username;public MyTask( String user…

第十八节 LLaVA如何按需构建LORA训练(视觉、语言、映射多个组合训练)

文章目录 前言一、基于llava源码构建新的参数1、添加lora_vit参数2、训练命令脚本设置二、修改源码,构建lora训练1、修改源码-lora训练2、LLM模型lora加载3、VIT模型加载4、权重冻结操作5、结果显示三、实验结果前言 如果看了我前面文章,想必你基本对整个代码有了更深认识。…

技术速递|VS Code Java 6月更新 - 项目设置功能增强!大量 Spring 新特性

作者&#xff1a;Nick Zhu 排版&#xff1a;Alan Wang 大家好&#xff0c;欢迎阅读 Visual Studio Code for Java 的六月更新&#xff01;在这篇博客中&#xff0c;我们将分享项目设置项目的重要更新以及一系列 Spring 的功能改进&#xff0c;让我们开始吧&#xff01; 项目设…

gitlab-runner安装部署CI/CD

手动安装 卸载旧版&#xff1a; gitlab-runner --version gitlab-runner stop yum remove gitlab-runner下载gitlab对应版本的runner # https://docs.gitlab.com/runner/install/bleeding-edge.html#download-any-other-tagged-releasecurl -L --output /usr/bin/gitlab-run…

工业一体机为数字化工厂带来高效作业指导

随着工业4.0的浪潮席卷全球&#xff0c;数字化工厂的概念深入人心。在这一背景下&#xff0c;工业一体机作为数字化转型的重要一环&#xff0c;凭借其强大的功能和灵活的应用&#xff0c;为工厂实现高效作业指导提供了强大的助力。 一、工业一体机的优势&#xff1a;赋能数字化…

解锁算力新极限,Xilinx UltraScale+赋能的高性能低延时FPGA加速卡

01、产品概述 AiHPC-V9P 是一款基于 AMD Virtex UltraScale FPGA VU9P 的 PCIe Gen3.0 x16 接口智能网卡&#xff0c;具有最大2*200GbE /或者16*10GbE(典型应用&#xff09;接入容量的高性能低延时智能网卡。 对外接口支持两组QSFP-DD 最高25Gb/s x8Lane 光口接入&#xf…

机场公厕厕位指引屏,布线简单,安装便捷

在人潮涌动的机场&#xff0c;公厕不仅是旅客的必需设施&#xff0c;更是衡量机场服务质量的重要指标。然而&#xff0c;传统机场公厕往往存在信息不透明、清洁维护滞后、高峰期拥挤等问题&#xff0c;严重影响了旅客的使用体验。近年来&#xff0c;随着智慧机场理念的兴起&…

【第24章】MyBatis-Plus之SQL注入器

文章目录 前言一、概述1. 使用场景2. 功能 二、注入器配置三、自定义全局方法攻略1. 定义SQL2. 注册自定义方法3.定义BaseMapper4.配置SqlInjector 四、注意事项五、更多示例六、实战1. 定义SQL2. 注册自定义方法3.定义BaseMapper4.配置SqlInjector5. 测试类6. 结果 总结 前言 …

如何使用IDEA快速清理无效代码(荣耀典藏版)

大家好&#xff0c;我是月夜枫。 今天分享一下IDEA中很有实用价值的Analyze&#xff0c;那么Analyze是用来做什么的呢&#xff1f; 主要用来清理没有引用的代码&#xff0c;包括方法、实体类以及没有使用的Mapper和Service等。 为了是项目更加整洁&#xff0c;可以使用Idea中…

(十六)视图变换 正交投影 透视投影

视图变换 代码实验 #include <glad/glad.h>//glad必须在glfw头文件之前包含 #include <GLFW/glfw3.h> #include <iostream> #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h"//GLM #include <glm/glm.hpp> #include <glm/gtc/m…

SECS/GEM快速完成半导体设备通讯

金南瓜帮助国内大量从事半导体前道设备开发研制、生产的设备厂商&#xff0c;通过快速提供稳定可靠的SECS/GEM、GEM300产品&#xff0c;为客户在激光退火、湿法设备&#xff08;清洗、镀膜等&#xff09;、离子注入、MOCVD、PVD等客户专注于核心工艺提升&#xff0c;提升企业的…