系统设计指南设计搜索引擎自动补全
Posted 指尖上的手艺人呢
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了系统设计指南设计搜索引擎自动补全相关的知识,希望对你有一定的参考价值。
设计搜索引擎自动补全
在谷歌搜索或在亚马逊购物时,在键入搜索框中时,搜索字词的一个或多个匹配项将呈现给您。此功能称为自动填充,typeAhead,搜索和you-type或增量搜索。图13-1显示了谷歌搜索的示例,显示了“晚餐”输入搜索框时的自动完成结果列表。搜索AutoComplete是许多产品的重要特色。这会导致我们到面试问题:设计搜索自动完成系统,也称为“设计顶部k”或“设计顶部K最大搜索的查询 。
图13-1
第1步 - 了解问题并建立设计范围
解决任何系统设计面试问题的第一步是提出足够的问题来澄清要求。这是候选人面试官的例子。
候选人:匹配仅在搜索查询或中间的开头支持吗?
面试官:只在搜索查询的开头。
候选人:系统应返回多少个自动完成建议?
面试官:5
候选人:系统如何知道哪个5个建议返回?
面试官:这是由人气决定的,由历史查询频率决定。
候选人:系统是否支持拼写检查?
采访者:不支持拼写检查或自动更正。
候选人:是英文搜索查询?
采访者:是的。如果时间允许最后,我们可以讨论多语言支持。
候选人:我们允许大写和特殊字符吗?采访者:否,我们假设所有搜索查询都有小写字母字符。
候选人:有多少用户使用该产品?
采访者:1000万DAU。
要求
以下要求是要求的摘要:
•快速响应时间:作为用户类型搜索查询,自动完成建议必须足够快地显示。关于Facebook的自动完成系统[1]的一篇文章揭示了系统需要在100毫秒内返回结果。否则会导致系统口吃。•相关:自动完成建议应与搜索项相关。•排序:系统返回的结果必须按流行度或其他排名模型排序。•可扩展:系统可以处理高流量卷。•高度可用性:当系统脱机时,系统应保持可用,可访问,速度减慢,或遇到意外的网络错误
信封估计的背面
假设1000万天的活跃用户(DAU)
普通人每天执行10个搜索
每个查询字符串20个字节的数据
• 假设我们使用ASCII字符编码。1个字符= 1字节
•假设查询包含4个字,每个单词平均包含5个字符
•每查询4 x 5 = 20个字节
•对于输入到搜索框中的每个字符,客户端向AutoCompleTe建议的后端发送请求。平均而言,为每个搜索查询发送20个请求。例如,在完成键入的时间“晚餐时,将在后端发送以下6个请求。
search?q=d
search?q=di
search?q=din
search?q=dinn
search?q=dinne
search?q=dinner
•〜每秒〜24,000查询(QPS)= 10,000,000名用户* 10查询/日* 20个字符/ 24小时/ 3600秒•峰值QP = QPS * 2 =〜48,000•假设每日查询的20%是新的。1000万* 10查询/日每查询20字节 20%= 0.4 GB。这意味着每天添加0.4GB的新数据 。
第2步 - 提出高级设计并获得买入
在高级别,系统分为两个:
•数据收集服务:收集用户输入查询并实时聚合它们。大数据集的实时处理不实用; 但是,这是一个很好的起点。我们将深入探讨更现实的解决方案。•查询服务:给定搜索查询或前缀,返回5个最常搜索的术语。
数据收集服务
让我们使用简化的示例来查看数据收集服务的工作原理。假设我们有一个频率表,它存储查询字符串及其频率,如图13-2所示。在开始时,频率表是空的。稍后,用户顺序地输入查询“Twitch”,“Twitter”,“Twitter”和“Twillo”。图13-2显示了如何更新频率表。
图13-2
查询服务
假设我们有一个频率表,如表13-1所示。它有两个领域
•查询:它存储查询字符串•频率:它表示查询已搜索的次数
表13-1
当搜索框中的用户类型“tw”时,显示以下前5个搜索查询(图13-3),假设频率表基于表13-1
图13-3
要获得前5名频繁搜索的查询,请执行以下SQL查询:
SELECT FROM frequency_ table WHERE query Like prefix ORDER BY frequency DESC LエMエT5
当数据集很小时,这是一个可接受的解决方案。当它很大时,访问数据库成为瓶颈。我们将探索对涉及方案的深度优化。
第3步-深度设计
在高级设计中,我们讨论了数据收集服务和查询服务。高级设计不是最佳的,但它用作一个良好的起点。在本节中,我们将深入了解一些组件并探索优化如下:
•Trie数据结构•数据收集服务•查询服务•缩放存储•Trie运营
Trie数据结构
关系数据库用于高级设计中的存储。但是,从关系数据库中获取前5个搜索查询效率低。数据结构TRIE(前缀树)用于克服问题。由于TRIE数据结构对系统至关重要,我们将致力于设计定制的Trie。请注意,一些想法来自文章[2]和[3]。
了解基本的Trie数据结构对于此面试问题至关重要。但是,这更像是数据结构问题而不是系统设计问题。此外,许多在线材料解释了这一概念。在本章中,我们只会讨论Trie数据结构的概述,并专注于如何优化基本特色以改善响应时间。
Trie(发音为“尝试”)是一种可以紧凑地存储字符串的树状数据结构。该名称来自检索单词,表示它旨在为String检索操作设计。Trie的主要思想包括以下内容:
•Trie是一种类似树的数据结构•根代表空字符串•每个节点存储一个字符,并有26个孩子,一个是每个可能的字符。要节省空间,我们不会绘制空链接•每个树节点表示单个单词或前缀字符串
图13-5显示了一个有搜索查询“树”的特色,“试试”,“真实”,“玩具”,“希望”,“获胜”。搜索查询用更厚的边框突出显示。
图13-5
基本TRIE数据结构存储节点中的字符。要支持按频率进行排序,需要包含在节点中的频率信息。假设我们有以下频率表。
表13-2
在向节点添加频率信息后,更新的TRIE数据结构如图13-6所示。
图13-6
自动完成如何与Trie一起工作?在潜入算法之前,让我们定义一些约定
•P:前缀的长度•N:Trie中的节点总数•C:给定节点的子数
下面列出获取顶级K最高搜索查询的步骤:
•找到前缀。时间复杂性:o(p )•从前缀节点遍历子树以获取所有有效的子项。如果它可以形成有效的查询字符串,则为一个孩子。时间复杂性:o(c)•对孩子们进行排序并获得顶级k。时间复杂性:o(clogc )
让我们使用如图13-7所示的示例来解释算法。假设K等于搜索框中的用户类型“tr”。该算法工作如下:
•第1步:找到前缀节点“tr•第2步:遍历子树以获取所有有效的孩子。在这种情况下,节点[树:10],[true:35],[尝试:29]有效 。•第3步:对孩子进行排序并获取前2. [真:35]和[尝试:29]是前2个查询,具有前缀“tr。
图13-7
该算法的时间复杂度是在上述每个步骤上花费的时间和:O(P)+ O(C)+ O(CLOGC)
以上算法很简单。但是,它太慢,因为我们需要遍历整个特色以获得最糟糕的情况,导致最坏情况。以下是两种优化。
1.限制前缀的最大长度2.缓存在每个节点上的顶部搜索查询
让我们一个接一个地看待这些优化
限制前缀的最大长度
用户很少在搜索框中键入长搜索查询。因此,可以肯定地说p是一个较小的整数,例如50。如果我们限制前缀的长度,则“查找前缀”的时间复杂度可以从O(p)降低为O(small constant), 又名 O(1)。
缓存在每个节点上的顶部搜索查询
为避免遍历整个Trie,我们将顶部K存储在每个节点上的最常用查询。由于5到10个自动完成建议足以让用户足够少数。在我们的特定情况下,只缓存了前5名搜索查询。
通过在每个节点中缓存顶部搜索查询,我们显着降低了检索前5名查询的时间复杂度。但是,这种设计需要大量的空间来存储每个节点的顶级查询。随着时间的快速响应时间非常重要,交易空间非常重要。
图13-8显示了更新的TRIE数据结构。前5名查询存储在每个节点上。例如,具有前缀“是”存储以下内容的节点:[最佳:35,BET:29,BEE:20,BE:15,啤酒:10 。
图13-8
让我们在应用这两种优化后重新审视算法的时间复杂性:
1.找到前缀节点。时间复杂度:O(1)2.返回顶部k。由于高速缓存顶部K查询,因此此步骤的时间复杂度为O(1)。由于每个步骤的时间复杂程度减少到O(1),我们的算法只需要o(1)来获取顶部k查询。
数据收集服务
在我们以前的设计中,每当用户键入搜索查询时,数据就会实时更新。这种方法是不实际的,因为以下两个原因:
•用户可能每天输入数十亿个查询。在每个查询上更新TRIE显着降低查询服务。•一旦Trie建造,比较好的的建议可能不会发生太多。因此,不必经常更新Trie。
要设计可扩展的数据收集服务,我们会检查数据来自哪些数据以及如何使用数据。Twitter等实时应用需要最新的自动完成建议。但是,许多Google关键字的自动完成建议可能不会每天更改。
尽管使用情况差异,但数据收集服务的潜在基础保持不变,因为用于构建TRIE的数据通常来自分析或记录服务。
图13-9显示了重新设计的数据收集服务。每个组件一次接一个。
图13-9
分析日志
它存储有关搜索查询的原始数据。日志只能追加,不编入索引。表 13-3 显示了日志文件的示例。
表13-3
聚集器
分析日志的大小通常非常大,数据不是正确的格式。我们需要汇总数据,以便我们的系统很容易处理。
根据用例,我们可以以不同的方式聚合数据。对于Twitter等实时应用程序,我们以更短的时间间隔聚合数据,因为实时结果很重要。另一方面,频繁汇总数据,每周说一次,可能对许多用例都足够好。在面试会话期间,验证实时结果是否重要。我们假设Trie每周重建。
汇总数据
表13-4显示了聚合的每周数据的示例。“时间”字段代表一周的开始时间。“频率”字段是本周相应查询的出现总和
表13-4
工人
工人是一系列服务器,定期执行异步作业。它们构建了Trie数据结构并将其存储在Trie DB中。
Trie缓存
Trie Cache是一个分布式缓存系统,可让TRIE在内存中保持快速读取。它需要一个每周的db快照。
Trie DB
Trie DB是持久存储。有两个选项可用于存储数据。
1.文档数据库:由于新特色是每周建立的,我们可以定期拍摄它的快照,序列化它,并将序列化数据存储在数据库中。像MongoDB [4]这样的文档存储是序列化数据的良好拟合。2.键值存储:通过应用以下逻辑,可以在哈希表[4]中表示TRIE:
•Trie中的每个前缀都映射到哈希表中的密钥•每个TRIE节点上的数据映射到哈希表中的值
图13-10显示了Trie和哈希表之间的映射
图13-10
在图13-10中,左侧的每个Trie节点映射到右侧的<key,值>对。如果您不清楚键值存储如何工作,请参阅第6章:设计键值存储。
查询服务
在高级设计中,查询服务直接调用数据库以获取前5个结果。图13-11显示了改进的设计,以前的设计效率低下。
图13-11
1.将搜索查询发送到负载均衡器。2.负载均衡器将请求路由到 API 服务器3.PI服务器从Trie Cache获取Trie数据,并为客户端构造自动完成建议4.如果数据不在Trie缓存中,我们将数据补充回缓存。这样,从缓存返回相同前缀的所有后续请求。当缓存服务器不足或脱机时,可能会发生缓存未命中。
查询服务需要闪电快速。我们提出以下优化
•Ajax请求。对于Web应用程序,浏览器通常会发送Ajax请求以获取自动完成结果。Ajax的主要好处是发送/接收请求/响应不会刷新整个网页。•浏览器缓存。对于许多应用程序,自动完成搜索建议可能不会在短时间内更改。因此,可以在浏览器缓存中保存自动完成建议,以允许后续请求直接从缓存中获取结果。Google搜索引擎使用相同的缓存机制。图13-12显示了在Google搜索引擎上键入“系统设计访谈”时响应标题。如您所见,Google将浏览器中的结果缓存为1小时。请注意:“缓存控制中的”私有“表示结果适用于单个用户,不得被共享缓存缓存。“Max-Age = 3600”表示缓存有效3600秒,AKA,一小时。
图13-12
•数据采样:对于大型系统,记录每个搜索查询需要大量的处理能力和存储。数据采样很重要。例如,系统只记录每 N 个请求中的 1 个 。
Trie维护
Trie 是自动完成系统的核心组件。让我们看看 trie 操作(创建、更新和删除)是如何工作的。
创造
Trie是由使用聚合数据的工人创建的。数据源来自分析日志/数据库。
更新
有两种方法可以更新Trie
选项1:每周更新Trie。创建一个新的Trie后,新的Trie替换了旧的。
选项 2:直接更新单个 trie 节点。我们尽量避免这种操作,因为它很慢。但是,如果特里树的大小很小,这是一个可以接受的解决方案。当我们更新一个 trie 节点时,它的祖先一直到根节点都必须更新,因为祖先存储了子节点的顶级查询。图 13-13 显示了更新操作如何工作的示例。在左侧,搜索查询“beer”的原始值是 10。在右侧,它更新为 30。如您所见,节点及其祖先的“beer”值更新为 30。
图13-13
删除
我们必须删除可恶,暴力,性明确或危险的一些自动完成建议。我们在Trie Cache前面添加过滤层(图13-14)以过滤掉不需要的建议。具有过滤器层使我们能够根据不同的过滤规则去除结果。不需要的建议在异步地从数据库中实际删除,因此正确的数据集将用于在下一次更新周期中构建TRIE。
图13-14
缩放存储
现在我们已经开发了一个系统为用户带来自动完成查询,是时候解决了可扩展性问题时,当TRIE增长太大以适合一个服务器时。
由于英语是唯一支持的语言,野生碎片的野生方式是基于第一个字符。这里有些例子。
•如果我们需要两个服务器进行存储,我们可以将以“A”开始的查询存储在第一台服务器上的“M”,以及第二个服务器上的“n”到“z” 。•如果我们需要三个服务器,我们可以将查询分成“a”,以“我”,'j','r'和's'到'z 。
在此逻辑之后,我们可以拆分最多26个服务器的查询,因为英语中有26个字母字符。让我们根据第一级分片,根据第一个字符定义分片。要将数据存储超过26个服务器,我们可以在第二个甚至是第三级的第二个甚至碎片。例如,以'a'开头的数据查询可以分成4个服务器:'aa-ag','ah-a','ao-au'和'av-az。
在第一次浏览这种方法似乎是合理的,直到你意识到有很多单词,从字母'c'开头而不是'x'。这会产生不均匀的分布。为了缓解数据不平衡问题,我们分析了历史数据分布模式,并涂上更智能的分片逻辑,如图13-15所示。Shard Map Manager维护查找数据库,用于识别应存储行的位置。例如,如果有类似数量的历史查询,并且'u','v','w','x','y'和'z'组合,我们可以维护两个碎片:一个 对于's'而一个是'你'到'z。
图13-5
第4步总结
在你完成深度设计后,你的面试官可能会问你一些跟进问题。
•
面试官:如何扩展您的设计以支持多种语言
为了支持其他非英语查询,我们将Unicode字符存储在Trie节点中。如果您不熟悉Unicode,这是定义:“编码标准涵盖了世界上所有写作系统的所有字符,现代和古代[5]
•
面试官:如果一个国家的顶级搜索查询与其它国家不同
在这种情况下,我们可能会为不同国家建立不同的尝试。为了改善响应时间,我们可以存储在CDN中的尝试。
•
面试官:我们如何支持趋势(实时)搜索查询
假设新闻事件爆发,搜索查询突然变得流行。我们的原始设计不起作用,因为:
•离线工作人员未安排更新TRIE,因为这计划每周运行。•即使是安排,也需要太长而构建Trie。构建实时搜索自动完成复杂,超出本书的范围,因此我们只会提供一些想法:•减少通过分片的工作数据设置。•更改排名模型并为最近的搜索查询分配更多权重。•数据可以作为流播放,因此我们无法立即访问所有数据。流数据意味着持续生成数据。流处理需要不同的系统集:Apache Hadoop MapReduce [6],Apache Spark Streaming [7],Apache Storm [8],Apache Kafka [9]等。因为所有这些主题都需要特定的域知识,我们不会进行 这里详细介绍
恭喜迈出了一点!现在让自己放在背上。做得好!
参考资料
以上是关于系统设计指南设计搜索引擎自动补全的主要内容,如果未能解决你的问题,请参考以下文章