搜索引擎场景下Debug架构设计
🧑🏻💻「 工欲善其事,必先利其器。」
目前绝大部分系统开发均采用分布式+ 微服务的方式设计,采用该类设计方式有诸多优势,例如:
•符合云原生标准,能够单独部署,替换,扩容等•开发效率高,用户反馈能够做到敏捷响应,实现小步快走效果。•合理拆分情况下,能够有效降低系统开发成本,通过接口约束完成对接
但同样分布式系统还会带来一些问题,例如通过RESTful接口通信带来的不稳定情况,RPC调用的一些限制,为保证系统问题需要使用诸多中间件来减少网络抖动带来的影响以及我们团队所一直专注的一个问题。
搜索场景下分布式系统如何进行检索结果排查及Debug
搜索场景是一个非常宽泛的概念,对于一般中小型的系统而言,信息搜索可能仅仅为数据库匹配查询或者Elasticsearch
搜索等对该类系统而言,数据增长量小,数据信息来源较为单一,对于该类搜索系统,结果具有"二象性",即多次搜索结果相同,存在或者不存在。
但是对于搜索引擎而言,架构的设计往往非常复杂,涉及到多个系统,本文会简单介绍下搜索引擎实现原理及架构。
搜索引擎架构
一般搜索引擎分为两个部分:
1.建库系统 : 通过爬虫,爬取网站页面并进行解析,进行特殊的处理,建立网页索引库等2.检索系统 : 对检索词(Query)进行解析,进行后端召回、排序、分析处理
建库系统不归属于本文内容涉及范围,对于搜索引擎建库过程,可参考相关文章。
检索系统的概念十分庞大,以百度为例,经过20年的发展,百度搜索引擎检索端已经发生了翻天覆地的变化,虽然这并非今天文章的主题,但是对于搜索引擎架构有基本了解后,对于之后有关debug内容会更加容易理解,因此这里容我简单介绍下检索架构及检索内容来源,以便能够更好理解Debug对于搜索引擎的应用场景及意义。
举个简单的例子:
在baidu搜索框中键入"股票"这一关键词后,会展示非常多的结果,这些结果以多种形式进行展现
可以看到,整张页面被分割为几块区域,虽然绝大多数人都见到过这种情况,但是实际上,各个区域的结果都来自不同的系统甚至不同的库(或一条结果来源多个库整合),一般我们可粗略分为如下结果:
•Tab区:细化搜索入口,能够提供更加精细的结果查询•首条结果:该结果一般和关键词紧密相关,并且能以更加贴合用户需求的方式进行展现,且能够汇聚多个网页结果•自然结果:检索后几条结果,对应单一网页,属于搜索引擎中数量最巨大的结果,但质量参差不齐•广告结果:基于关键字的商业广告推广,能够进行定向投放,也是搜索引擎场景的主要经济来源•推荐结果:根据用户关键词进行联想,纠错等操作后,进行相关推荐
如何更加智能理解用户需求并通过技术手段处理后,展现给用户最好的期望结果,是搜索引擎一直以来的发展方向和目标,这其中经历了多个阶段,我将它们概括为三个主要阶段,分别为库检索、多元化整合、个性化整合。
库检索
该阶段对于用户的检索行为只能够进行最简单的关键词搜索处理,通过倒排索引、权重计算等功能,将原始页面进行排序,返回给用户排序后的自然结果集合。
该阶段返回的结果虽然具有用户检索关键词,但往往质量参差不齐,且可能篇幅巨大,用户很难在多条结果中关注到自己想要信息,因此很难通过单一的库检索满足用户需求。
多元化整合
搜索引擎仅能够展现自然结果是远远不够的,对于一些Query需求,例如:电影、股票、NBA等词,我们需要直接提供给用户一个直观的信息了解渠道,例如近期电影上映列表或者NBA当日赛事情况,针对该类型需求,出现了多元化整合的方案(特殊结果),该类结果往往是综合了多个网站的信息,进行筛选、构建、模板渲染等处理,最终能够给用户展现一个尽可能友好的界面。
多元化的整合旨在让最有价值的信息以一种汇聚的方式展现在首位,用户可以直观的了解到自己的期望信息,减少在多个网站中寻找目标信息的成本。
个性化整合
一定程度上来讲,多元化整合已经能够满足绝大部分用户的需求,但是针对部分人群,则需要进行更加细化的搜索结果处理,例如部分用户只关注剧情类型电影,则在搜索电影时,系统需要推送更加多的剧情标签的内容。
针对这类细化的需求,出现了个性化整合的系统,该系统实际上是对于多元化的进一步提升,通过收集用户数据,训练相应模型,在不改变检索结果的事实基础上,增加个性化的设置,以此来满足所有用户的需求,即千人千面。
上面所谈及内容绝大部分和技术无关,更多的是搜索引擎在业务场景中,如何更好满足用户的需求,其中花了一定篇幅谈论了检索结果的几个阶段,这些阶段在公司内部有各自的实现,出于保密考虑,就不进行更加细化的讨论,这段内容只是希望没有接触过搜索引擎的同学对其结果划分的方式有基础的概念。
花开两朵,各表一枝。
对于海量的结果,搜索引擎是如何完成信息的检索、排序、过滤、归并、整合等工作呢?这些实际上就是一个搜索引擎架构需要完成的事项,也是本篇内容所高度相关的知识,需要花费一点时间来了解。
互联网存在着巨量的网页数据,搜索引擎的建库端负责将这些网页进行爬取,同时完成相应打分,根据网页质量,我们可以分为多个库层,这些库层从结构上来看呈三角结果,从上到下网页质量依次降低数量依次增多,通过库层的划分,搜索引擎能够更好的完成信息检索,同时减少响应时间。
举个例子,所有的库层中都会拥有有关电影评价的网页数据,这些数据分布在各个库层中,且从上到下数量依次增多(存储为冗余存储,A层数据在其他层同样具有),当我们通过搜索引擎查找电影评价
关键词时,会优先检索最上层库层,如果最上层的数据能够满足所有需求,则直接返回,如果无法查询到,则再次向下层检索。
通过分层检索库层这种方式,尽量减少搜索引擎的检索内容时对于时间的消耗。
我们在上文中谈及的自然结果,都是来源于各个库层的结果,即库检索阶段的结果,接下来我们看下常见的搜索引擎基本架构。
📌 该部分内容为整合归纳,如有雷同纯属巧合。
我将常见的搜索架构进行了部分归纳、抽象,基本可将内容总结为以上架构图。
根据该架构图,我们能够逐一了解搜索引擎常见的结构划分、工作流程及最终展现等内容,以一次检索为例。
小明同学在检索框中输入 "明日天气如何?",他希望搜索引擎能够直接给予他一个明确的答案,温度、气候状况的信息。
我们来看下在这种情况下,搜索引擎是如何工作的。
首先引擎会对检索的关键字进行切分,会被切分为,明日、天气、明日天气等关键词,这些词能够与库中的网页数据构成倒排索引,方便信息的检索(倒排索引结构内容请不熟悉的同学自行查阅)。
完成切词后,引擎会从上文中提到的库中查阅相关信息,与此同时,也会查阅多源整合结果,该类结果依托于自然结果进行构建,完成检索后,进行归并排序、信息过滤筛选,同时在其中加入广告结果了,推荐结果等内容。
当信息检索汇总完成后,将数据送入渲染模板,根据对应的模板渲染对应的页面,这也就是针对不同搜索结果,可能展现的页面差距很大的原因,完成展现后,即可返回用户查看。
以上内容就是一个搜索引擎工作的原理,当然,其中的细节内容非常多,我很难在这么短的篇幅内完成归纳,但掌握以上内容,便能够了解如何在搜索引擎的场景下,构建Debug,检测各类搜索结果。
Debug应用场景及架构
现在,我们再来聊聊Debug。
❓『 搜索场景为什么需要Debug,换而言之,这二者有什么关联性 。』
这是一个很直观的问题,问题的答案也很简单,任何一个搜索引擎必然需要有对应的Debug平台,否则随着库层规模扩大,整个系统必然崩塌。
我们来对比一个例子,以便我们更清楚Debug平台对于搜索端的意义,使用同样的关键词搜索,我们来看下百度和360的结果。
能够发现,NBA相关赛事的比分和详情在两个引擎中能够检索到,但是最直观的数据板展现,百度明显做的更好一些。用户搜索NBA,一般都会关注当天的比分、球队排名、新闻等情况,但是在360中,这类整合数据,仅仅排在了第三条结果,从用户需求及产品的角度而言,这是一个Bad Case。
那么这和Debug有什么关联呢?
对于一个关键词的检索,从最底层的库层中获取到的信息往往是巨量的(万级),但最终通过归并,截断,粗排序,精排序,结果干扰等过程后,一般仅仅保留40条有效内容,这其中还有部分为特殊结果与广告结果。由此可见,上万条结果中绝大部分内容都被去除。关于各个阶段如何进行排序及相关算法内容,非本文重点,不进行阐述,有兴趣的同学可以自行了解。
回到例子,右侧360的Bad Case可能由于算法导致,或者由于在某个阶段排序计算错误所导致,但是往往在搜索引擎中有几十个模块,对于一条数据,想要查看在哪个模块出现问题本就是一个很难的事情,何况在分布式系统中,无法像一个服务一样进行Debug,因此,该类系统需要一个Debug平台进行支持,通过该平台,能够有效提升问题查找效率,简单归纳下Debug平台如何支持搜索引擎业务:
•Bad Case分析:对于不符合预期结果,能够快速寻找各阶段Score值相关信息,判断具体问题模型。•模型调试:获取上游模块处理结果后,添加新内容送测下游模块,测试下游模块对于干扰结果处理能力•漏斗分析:聚焦单条结果,根据某些信息(如id、url)查看在整个链路中状态•竞品对比:对竞品进行相同Query抓取,对比竞品结果在本系统中各个模块中排序状态,人工判断合理性
了解基础业务场景后,简单聊下如何构建这样的平台体系,由于整个搜索引擎架构中的微服务都有着不同的实现,从语言层面及技术实现层面都有着差异性,因此首先要考虑一个问题,如何将所有信息记录和统一格式化处理。
Debug平台架构设计
搜索引擎中的每条结果应有唯一的字段用于鉴别身份(类比数据库中id),通过该字段,能够从底层库中获取到该条检索结果,我们可以称呼这个过程为——召回。
底层库层 > 服务A > 服务B > 服务C
如上述例子,服务A结果为库层召回,服务B中数据为服务A召回,整个搜索引擎就是一个自上而下召回的过程。
当一个服务召回另一个服务的数据时,必然会获得数据关键性信息,通过该类信息能够在库层中获取到唯一的数据内容,同时只要收集到这些内容,并通过某些服务将各个阶段内容进行收集,就能够完成各阶段流程信息整合,同时通过数据规整,存储能够完成CASE复现,我们首先来看下架构图。
该图中的服务层即对应整个搜索架构中的所有微服务,由于各个微服务采用不同于语言开发,因此在Debug侧,提供了多语言开发的Debuglib用于收集各个服务中的阶段信息,信息收集主要需要遵循几个标准:
•信息格式统一:所有信息均以JSON格式进行处理•阶段格式一致:允许收集多阶段信息,但每个阶段信息必须保持一致•信息瘦身:考虑到网络I/O等情况,能够通过底层库层检索得到的信息,不进行收集,减少数据包大小
DebugLib完成信息收集后,通过RPC进行信息传输,将所有的信息发送到Debug Server进行信息处理,以上这部分内容为整个Debug平台的基建部分,我们可以以伪代码的形式展现该过程。
import debuglib
// 构建一个收集器
collection = debuglib.build()
//模拟数据
sort_data = _mock_data()
//数据封装, 并添加阶段性信息
collection.set_tag('something')
.set_stage('stageA')
.set_module('FastSort')
.set_data(sort_data)
.encapsulate()
.send()
上面这段代码基本上就是一个Debuglib收集信息的过程,其中简化了各类内容以及具体实现,有兴趣的同学可以尝试自己实现一个类似的工具。
完成Debug信息收集后,会将信息统一发往Debug Server,该服务主要用于对信息解析,格式化,存储以及对外提供各类获取信息的接口,对于一个网页的信息,我们往往需要收据两种信息。
1.页面原型:页面原型通过保存HTML完成,同时完成ID编号,用于保存CASE实例2.模块信息:需要统计、保存DebugLib中发送的各个模块处理信息
实际上到这里的内容是比较简单的,其开发思路也相对简单,但是由于信息的复杂特性(没有明确结构),因此存储会面临到比较大的问题,好在目前市面上有大量的优秀存储引擎供我们进行选择,这里主要使用了如下几种。
•Table类型:Table提供了列式存储的功能,对于不同阶段的信息有着良好的存储结构,举个例子:
现有如下数据
data_A = {
identity: 12345667,
fieldA: XXX,
fieldB: xxx
}
data_B = {
identity: 123456671,
fieldC: XXX,
fieldD: xxx
}
由于各个实体部分字段具有差别,但是具有相同字段,对于普通的关系型数据库无法满足,因此使用Table类型存储结构能够便捷的完成数据查找及存储操作
•ClickHouse:虽然Table能够完成灵活的数据存储,但是面对大规模的汇聚要求,Table很难完成相关操作,因此使用Clickhouse这类汇聚型数据库进行数据记录存储,便于大数据接入,日志分析等功能•Redis:对于部分在线处理功能,数据更加关注时效性,同时没有持久化需求,因此使用Redis存储非常合适•MySQL:关系型数据库在系统中仍然有一定地位,通常使用该类数据库存储一些数据规模中等,增长量较小的数据
完成数据存储后,通常就是对于数据进行分析操作,实际上从这里开始,实现场景就非常广泛了,对于不同功能,可以通过不同的应用来接入完成,以CASE检测及漏斗分析为例。
用户期望能够查看一条结果从底层到最终展现的各个阶段信息内容,该过程可以归纳为一棵检索树,如下:
对于各类检索,我们提供了一种装配器和算子的概念,其架构如图所示。
算子特指一个最小执行单元,从功能上来讲,有些类似于FaaS,算子集成运行在一套特殊的Workflow工作流中,能够由工作流自动控制算子执行结果,Debug Server将数据完成存储后,CASE分析模块执行对应工作流,工作流接受到数据后,执行流水线上各个算子,算子结果收集后更具配置对应规则继续执行装配逻辑。
装配器特指收集多个算子结果后,通过一定规则进行结果汇聚的一个模块,例如图中调度模块所需的数据可能格式特殊,需要从多个解析后的算子结果中提取部分内容并组装,便可通过装配器进行实现。我司业务中,算子和装配器及工作流整个部分均使用Python构建,使用Python构建的一个好处在于基于脚本语言,能够实现动态替换。
由于我们已经提供了一套完整的Worflow工作流机制,用户只需要编写算子解析自己的数据即可将数据添加到对应的CASE分析树中,这个在线编写、调试的平台使用JupyterLib定制构建,通过这个工作,实现了谁提供数据,谁来解析数据,架构开发人员只需要维护整体架构,有关各类算法模型的分数等内容,只需要算法同学编写对应算子即可完成,大大减少了开发之间沟通成本,提升开发效率。
实际上,Debug平台还能够完成很多内容,但受限于篇幅,今天仅聊这些内容,之后有机会可以再来聊下工作流与动态替换等功能。