1.查询qps,索引容量
2.离线数据流如何建设
2.1LogData数据量太大,可以降频
2.2DataCollection提取LogData计算高频词汇,例如今天天气,10M
2.3数据如何分片,
2.3.1采用consistency hash
2.3.2range方式,需要有服务采取一些策略调度数据,避免某些服务负载
2.4数据如何推送到线上:
2.4.1可以离线pipeline方式,推送到线上,每台机器内存double buffer trie,单个shard需要需有 三副本
2.4.2数据是全量更新,还是增量更新
2.4.3 如何不存在Pipeline的支持,可以单个shard使用Raft协议进行复制,Leader节点后台线程 读取磁盘文件,有数据更新apply对应的(K,V),Leader append entries到follower节点,follower 节点WAL (K,V),超过n/2+1个节点WAL,Leader on_apply到后台buffer,置换double buffer节 点
2.4.4服务启动,机器节点故障,内存数据会丢失。数据需要定时snapshot,将内存trie serialization 到磁盘
3.在线系统如何搭建
3.1内存trie,每个trie node都存储top个数个单词,避免每次查询的时候O(26^n),high latency
3.2优化可以在客户端
3.2.1client可以pre-fetch预取一些结果,等到user下一个输入,只需在client端做一些match
3.2.2client可以缓存history历史查询结果,使用LRU方法剔除历史记录
4.算法解析
(一)理解需求
(二)资源预估,dau,使用次数,预估qps,存储规模
(三)系统图片
(四)数据结构与存储,数据结构表,存储系统选型
(五)核心子服务器
(六)接口设计
(七)可靠性,可伸缩性
一、可靠性:正常情况下,应用行为满足 API 给出的行为,Fault-tolerance
1.无单点故障,无状态Load Balancer,有状态服务,master-slave复制增加replication,读写分离
2.监控&报警
qps,latency,cpu idl,process存活,缓存命中率,线程池空闲线程比率,
二、可伸缩性:系统应对负载增长的能力
1.负载衡量:应用日活月活,qps,latency,数据库读写比率,用户响应时间
2.应对负载:垂直伸缩,水平伸缩,上云
无状态服务,做服务发现,LoadBalance
有状态服务,据需求场景,如读写负载、存储量级、数据复杂度、响应时间、访问模式,来进行取舍,设计合乎需求的架构。
设计一个搜索自动补全系统,也被成为”设计 top K“或”设计top K个被搜索最多的查询“
步骤一,理解问题并完成设计边界
问题:匹配只支持在搜索的开头,还在在中间
回答:只搜索查询的开头
问题:系统应该返回多少建议
回答:5
问题:系统如何知道哪5条建议应该返回
回答:这决定于受欢迎程度和历史查询频率
问题:系统支持拼写检查么
回答:不,拼写检查或自动更正是不支持的
问题:搜索查询用英语么?
回答:是的,如果时间允许,可以讨论多语言
问题:允许大小写和特殊字符么?
回答:不,我们假设所有查询都是小写字母
问题:有多少个用户使用这个产品?
回答:DAU:10million
需求:
快速响应时间:当用户按键搜索,自动补全建议必须显示的足够快,延迟100ms
相关:自动补全建议应该与搜索词相关
排序:系统返回的结果必须按照受欢迎程度或其他排序模型
高扩展行:系统可以出来大流量。
高可用性:当系统部分变慢或遭遇意外网络错误,系统应该保持可用和可访问
估算:
DAU:10 million
平均每人每天10次搜索
每个查询字符串20字节数据:ASCII 1字符=1字节,4个单词,每个单词5个字符
对于搜索框输入的每个字符,客户端会向后端发送一个请求,平均每个搜索查询20个请求
容量预估:
步骤二,提出高层的设计并获得支持
在高层,改系统分为两部分:
数据收集服务:收集用户的输入查询并聚合它们
使用一个简化的示例来了解数据收集服务是如何工作的,假设我们有一个频率表,其中存储了查询字符串及其频率。一开始,频率表是空的。随后,用户依次查询,更新频率表
查询服务:给定一个搜索查询或前缀,返回5个最常用的搜索词
假设有一个频率表。有两个字段:1.Query:查询存储字符串 2.频率:表示查询被搜索的次数
去获取5个最高频率的查询词:
当数据集较小时,这一个可接受的方案。放数据访问很大时,访问数据库就成为瓶颈。
步骤三、深入设计
在高层设计中,我们讨论了数据采集服务和查询服务。高层次设计并不是最优的,但它却是一个很好的起点,在本节,深入研究几个组件,并探索如下优化:
Trie data structure
在高级设计中使用数据库进行存储。然而,从关系数据库中获取前5个搜索查询是低效的。采用Trie来克服这个问题
如何使用trie自动完成工作?一些术语:
p:前缀长度
n:trie中全部节点总数
c:给定节点的子节点数量
该算法花费时间的总和:O(p)+O(c)+O(clogc)
上面的算法简单,但是他太慢了,因为最坏情况下,需要遍历整个trie,以获得前k个结果。以下是两个优化:
1.限制前缀的最大长度
用户很少在搜索框中输入长查询。因此,可以肯定说p是一个小整数,比如50。如果限制前缀的长度,查找前缀的时间复杂度可以从O(p)降到O(small constant),也就是O(1)
2.在每个trie节点,缓存查询top k 查询词
为了避免遍历整个trie,我们在每个节点上存储最常用的top k。
通常在每个节点缓存top search queries,我们大大降低了检索前5个查询的时间复杂度。然而这种设计需要大量空间来存储每个节点的top 5 queries。用空间换时间是非常值得的,因为快速时间非常重要
应用了这两种优化后,让我们重新审视算法的时间复杂度:
1.找到前缀节点,时间复杂度O(1)
2.返回top k。因为top K查询被缓存,所以这一步的时间复杂度是O(1)
由于每步的时间复杂度降低到O(1),我们的算法只需要O(1)就可以获取top k queries
Data gathering service
在我们之前的设计中,每当用户搜索查询是,数据就会实时更新
这种方法不实用,原因有二:
1.用户每天可能输入数十亿次查询,在每次查询上更新trie会显著降低查询服务器的速度
2.一旦建立了trie,顶级建议可能不会变化太多,因此,不需要平凡更新trie。
为了设计一个可伸缩的数据收集服务,我们需要研究数据的来源和使用方式。
像twitter这样的实时应用程序需要最新的建议。然而许多谷歌关键字的自动补全建议在每天的基础上可能不会有太大的变化。
尽管用例不同,但数据收集服务的底层基础是相同的。因为用于构建trie的数据通常来自分析或日志服务
Aggregator
analytics log通常非常大,数据格式也不正确。我们需要聚合数据,这样系统可以很容易的处理这些数据。
根据用例的不同,我们可以不同的方式集合数据。对于Twitter这样的实时应用,我们在更短的时间内聚合数据,因为实时结果很重要。
另一方面,不那么频繁的聚合数据,比如每周一次,对于需要用例来说可能已经足够好了。
在面试过程中,验证实时结果是否重要。我们假设每周更新重建三次。
Aggregator Data
一周数据聚合势力如下。time表示一周的开始时间,frequency对应查询在该周内出现的次数总和。
Workers:是一组定期执行异步作业的任务服务器。他们构建trie,并将其存储在trie DB中
TrieCache:是一个分布式缓存系统,他将Trie保存在内存中,以便快速获取。它每周对数据库进行一次快照
TrieDB:是持久化存储,存储数据有两种方式
1.文档存储
2.key- value存储:一个trie可以通过应用一下逻辑一hash表形式:
(1)trie中每个前缀都映射到hash表中的一个键
(2)每个节点的数据映射hash表中的一个值
Query service
在高级设计中,查询服务直接调用数据库获取前5个结果。下图是改进后的设计,因为之前的设计是低效的。
1.搜索查询被发送到负责均衡器
2.负责均衡器将请求路由到API server
3.API server从trie cache获取trie数据,并为客户端构造自动完成建议
4.如果不在trie cache中,我们将数据补充回缓存。当缓存服务器内存不足时,可能会发生缓存丢失
Trie operatirons
Create:Trie是由worker使用聚合数据创建的。数据来源自Analytics Log/DB
Update:每周更新三次,一旦创建了新的trie,新trie就会替换就的trie
Delete:我们必须删除仇恨的,暴力的,黄色的,或者危险的建议。我们在trie cache前面增加一个filter层来过滤不需要的suggestions。使用过滤层可以根据不同的过滤规则灵活地删除结果。不需要的suggestions从物理上异步地从数据库删除,以便在下一个更新周期中使用正确的数据集合来构建trie