在搜索框输入部分关键字后,自动补全后面的关键字,本质是top k问题。
以下是一些可供参考的需求探讨示例:
此外还一些额外要求:
规模预估:
将系统分成两部分:
简单来说,就是记录每个搜索词被搜索的次数,如下:
假设已经有了搜索词的频率表,它包含下面两列:
当用户输入"tw"时,将显示以下5个推荐的搜索词。
这5个推荐词的生成可以使用下面的SQL语句:
上面的方式在数据规模较小时比较实用,但当数据规模庞大时,数据库查询会成为系统性能瓶颈。
深入设计并优化以下组件:
使用前缀树来处理top k查询问题,它的基本设计如下:
以下是搜索“tree”, “try”, “true”, “toy”, “wish”, “win”之后生成的一棵前缀树:
基本的前缀树只存储字符,为了支持按出现频率排序,需要在每个节点加上出现频率,假设出现频率如下:
加上出现频率后,前缀树如下:
在讨论自动补全之前,先定义一些术语:
根据给定前缀查询top k个搜索词的过程如下:
以下面的前缀树作为示例来演示一下前缀树的查找过程,这里假设k=2,用户输入是"be",则算法的工作流程如下:
以上算法的时间复杂度是:O(p) + O(c) + O(clogc)
上面的算法在最坏情况下可能要遍历整棵前缀树,下面是两项优化措施:
用户通常不会查询一个很长的单词,所以可以将p设置成一个较小值,比如50。限制长度之后,查询前缀的算法复杂度将近似O(1)。
为了避免每次查询都遍历一次子节点,我们可以在每个节点缓存top k的查询结果。因为一般只需要推荐5~10个搜索建议,所以k不会很大,不会消耗大量内存,这里以k = 5作为示例。
通过在节点上缓存top k查询结果,可以显著降低重复查询的时间,相当于以空间换时间。
以下是一个带top k缓存的前缀树,每个节点都缓存了top 5的查询结果。
在应用以上两项优化措施后,新的算法复杂度将变成:
在前面的设计中,用户每搜索一次,前缀树就被更新一次,这种设计存在以下两个显著的问题:
以下是重新设计过的数据搜集服务以及各个组件的描述:
存储了原始的搜索数据,随时间增加,未索引化,以下是一个示例。
分析日志通常体积巨大,并且未被格式过,因此首先需要对日志进行聚合,以生成适合后续处理的数据。
根据应用场景,可以采用不同的聚合方式,比如对于实时类应用,可以采用间隔较小的聚合器,以生成实时的分析结果。对于实时性不敏感的应用,则可以采用间隔较大的聚合器,比如每周聚合一次。
以下是一个聚合之后的数据示例,时间一栏按周进行分隔,频率一栏对应出现次数。
以异步的方式定期性执行构造前缀树并存储到前缀树数据库的任务。
分布式缓存系统,将前缀树缓存到内存以方便快速读取。前缀树缓存每周从数据库中更新一次数据。
用于永久存储前缀树,有两个可供参考的选项:
以下是一个使用哈希表存储前缀树的示例:
查询流程如下:
另外,查询服务要求轻量迅速,可以使用下列方式进行优化:
cache-control
字段控制。前缀树是本系统的核心,以下是一些前缀树的操作(创建,更新,删除)。
工作集群使用聚合之后的数据创建,聚合之后数据来自于分析日志。
有两种更新方式。
选项1:每周更新一次,使用新创建的前缀树替换老的前缀树。
选项2:直接更新前缀树的结点,只适用于小规模系统,在规模较大时速度很慢,因为每更新一个节点,该节点的所有父级以上节点也要同步更新,以下是一个更新示例:
为构建社会主义和协社会,有时需要删除一些不合时宜的搜索词。这里我们在API服务器和前缀树缓存之间加上一层过滤,根据设置不同的过滤规则,可以过滤掉不适合的搜索建议。为了以防万一,被过滤的搜索建议应该直接物理删除。
用于解决前缀树过于庞大,无法使用单服务器进行保存的问题。
由于本系统只支持英文字母,一种简单的办法就是根据第一个字符进行分片,比如:
通过这种逻辑,我们可以将可以将前缀树分片到至多26台服务器上,因为只有26个英文字母。如果还需要扩展服务器,则只能将分片扩展到前两个字符,比如以字符'a'开头的服务器可以再进行分片:'aa~ag', 'ah~an', 'ao~au', 'av~az'。
上面的方式带来的问题就是字母并不是平均分布的,比如以'c'开头的单词比以'x'开头的单词多,这会导致分片不均匀。为了减轻数据不均匀的问题,我们可以在上面分片方式的基础上,通过参考历史数据来进行智能分片,比如通过历史数据发现,'s'开头的查询次数和'u', 'v', 'w', 'x', 'y', 'z'加起来的查询次数相当,那么就可以使用两个分片来存储,一个存储's',另一个存储'u'~'z'。
上面的智能分布可以通过一个分片管理器来处理,这个分片管理器可以返回某个前缀对应的数据库分布。
以下是一些可供讨论的点。
使用Unicode构造前缀树。
对不同的地区构造不同的前缀树,并且使用CDN来加速前缀树的读取。
假如爆发了某个热点事件,那么对应的搜索词会马上被大量搜索。当前系统并不能应对这种情况,因为前缀树是一周构造一次的,而且即使是马上重新构造,也需要大量时间来完成。以下是一些可供参考的方案: