系统设计指南设计搜索引擎自动补全

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 LMエ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]等。因为所有这些主题都需要特定的域知识,我们不会进行 这里详细介绍




恭喜迈出了一点!现在让自己放在背上。做得好!

参考资料


以上是关于系统设计指南设计搜索引擎自动补全的主要内容,如果未能解决你的问题,请参考以下文章

搜索引擎anti-spam系统设计指南

C#VS快捷键

C#VS快捷键

C#VS快捷键

C#VS快捷键

《现代命令行工具指南》4. 自动补全:让所有终端都能自动补全 - Fig