搜索引擎 - 闲鱼复杂搜索系统的可靠性优化之路


背景介绍

搜索引擎是电商平台成交链路的核心环节,搜索引擎的高可用直接影响成交效率。闲鱼搜索引擎作为闲鱼关键系统,复杂度和系统体量都非常高,再加上闲鱼所有导购场景都依靠搜索赋能,搜索服务的稳定可靠成为了闲鱼大部分业务场景可用能力的衡量标准;如何保障搜索服务的稳定和高可用成为了极大的挑战。

闲鱼搜索作为闲鱼核心系统,有以下几个突出的特点:

  • 数据体量大:对接闲鱼数十亿的商品,引擎有效商品数亿;
  • 索引庞大:闲鱼非结构化商品需要与算法团队合作,预测抽取有价值的结构化信息,建立索引;已创建数百的索引字段,整个引擎索引数据量为T级别;
  • 增量消息多:日常增量消息QPS 数十万,峰值QPS可以达到 数百万;
  • 查询复杂:很多特殊业务场景,查询条件要求苛刻而复杂;比如召回GROUP分组统计,聚合/打散/去重,关键词复合运算查询等;
  • 实时性性要求高:闲鱼中都是二手商品,卖家商品的库存都是1;商品上下架频繁,对引擎数据的同步更新实时性要求非常高;
  • 智能化扩展:由于闲鱼商品非结构化特性,为保障召回数据的效果以及相关性;需要引擎具备智能插件扩展的能力,能与算法开发人员协同;

鉴于闲鱼商品搜索引擎以上主要特点,本文详细介绍闲鱼搜索在系统高可用上做的各种努力,希望能给读者一些启发。

闲鱼搜索整体架构

正式引出搜索稳定性保障方案之前,我们需要对闲鱼搜索技术有一个简单大致的了解;

我们比较过很多外部开源的搜索引擎,都不能完美支持背景中所列的需求点;闲鱼使用的是阿里巴巴最新研发的搜索引擎平台Ha3,Ha3是一款非常高效,智能强大的搜索引擎,它完全满足闲鱼搜索的要求;

Elasticsearch是基于Lucene的准实时搜索引擎,也是比较常用的开源搜索引擎,但是其在算法扩展支撑/绝对实时的能力上与Ha3相差甚远;在同等硬件条件下,基于1200万数据做单机性能对比测试发现,Ha3比ElasticSearch开源系统的QPS高4倍,查询延迟低4倍;Elasticsearch在大规模数据量场景下的性能和稳定性与HA3相比尚有很大的差距。

闲鱼搜索体系运行流程

下图是闲鱼搜索体系系统结构图,主要分在线和离线两个流程;

图

索引构建流程

索引构建即我们所谓的离线流程,其执行者BuildService①,负责将不同存储类型的纯文本商品数据构建成搜索引擎格式的索引文件。原始的商品数据有两类,一类是存放在存储上的全量商品数据,这个定期(一般以天为周期)通过DUMP②产出,另一类为实时变更的数据,在商品信息变更后,由业务系统即时同步给消息系统Swift③。最终分发给在线服务的Searcher④更新索引。

搜索查询流程

搜索查询即我们所谓的在线流程;闲鱼搜索服务应用A发起搜索请求,通过SP⑤进行服务能力编排;首先SP发起QP⑥算法服务调用,进行用户意图预测,并获取排序辅助信息;然后结合QP返回的结果加上业务系统的查询参数,向Ha3搜索引擎发起查询请求;Ha3搜索引擎QueryService⑦中Qrs⑧接收到查询请求后,分发给QueryService中的Searcher进行倒排索引召回、统计、条件过滤、文档打分及排序、摘要生成;最后Qrs将Searcher返回的结果进行整合后返回给SP,SP经过去重再返回给业务系统;

闲鱼搜索体系团队构成

闲鱼搜索的运维体系,是一个相当复杂的构成;其中涉及很多团队的鼎力协作;

首先必须有Ha3搜索引擎团队在底层提供核心的搜索引擎能力支持;主要负责Ha3搜索引擎核心能力的建设维护;提供并维护引擎运维操作平台和实时引擎搜索服务。

然后是算法团队,在Ha3搜索引擎上进行定制,优化用户搜索体验;对闲鱼非结构化的商品通过算法模型进行理解,预测抽取出结构化信息,供搜索引擎商品索引使用;监控维护QP集群服务;开发并使用Ha3引擎排序插件,进行召回数据分桶实验,验证调优。

最后是我们业务工程团队,串联整个搜索流程,监控维护整个搜索链路的可用性;主要维护搜索对接的数据,Ha3搜索引擎接入管理,进行SP搜索服务编排,制定合理的查询计划;以及闲鱼搜索统一在线查询服务的研发维护工作。

本文亦是从业务工程团队的工作角度出发,阐述如何对复杂搜索业务系统进行稳定性的保障;

稳定性治理

部署架构优化

独立网关部署

Ha3引擎通过SP提供基于HTTP协议的搜索服务API,对类似闲鱼这样复杂的搜索场景,每个闲鱼上层的业务如果都通过拼接SP HTTP接口参数的形式来使用搜索服务,所有上游业务都需要关心SP的拼接语法,会使开发成本剧增,而且如果由于特殊原因SP进行了语法调整或者不兼容升级,那么所有上层业务都需要修正逻辑,这样的设计不合理;为了让业务系统与搜索系统完全解耦,并且提高搜索服务的易用性,闲鱼搜索通过统一的业务搜索网关来提供简单一致的分布式服务,供闲鱼各上层搜索业务使用,并与SP对接,屏蔽掉SP对上游业务系统的穿透;

最开始闲鱼搜索服务与其他很多不相关的业务场景共建在一个比较庞大的底层应用中;这种部署方式对稳定性要求很高的业务模块来说有非常大的安全隐患;

  1. 各业务模块会相互影响;存在一定程度的代码耦合,同时还涉及机器资源的竞争,风险比较高;
  2. 应用太过庞大,严重影响开发协作的效率和代码质量;

于是将闲鱼搜索服务部署到独立的容器分组,新增应用A供闲鱼搜索服务专用,作为各业务使用搜索服务的独立网关,同时对接下游的SP搜索服务;保证服务是隔离和稳定的。

前后部署图如下所示;

图

多机房容灾部署

在最初,闲鱼商品搜索服务对接的Ha3搜索引擎只部署在一个机房;当此机房出现比较严重的问题时,对上游业务影响非常大,甚至会引发故障;鉴于此,对闲鱼商品搜索引擎的在线离线集群进行双机房部署容灾;在详细展开之前,我们先大致理解下Ha3引擎DUMP流程的原理;

图

如上图所示,Ha3引擎DUMP流程大致流程可以简单分为以下几步:

  • 准备源数据:评估业务需求,将需要接入引擎的数据准备好;一般业务数据大部分都是DB数据表,也有少数的ODPS⑨离线数据表;算法团队提供的数据绝大部分都是ODPS离线数据表;
  • DUMP拉取数据:通过Ha3引擎团队提供的运维平台,可以将这些表的某些数据字段接入到创建好的搜索引擎中,后续DUMP执行的时候,Ha3离线引擎会拉取这些接入的表字段数据,形成一份引擎自用的镜像数据表,在这一步中,我们可以使用引擎团队提供的UDF工具,对数据进行清洗/过滤等处理;
  • 数据Merge:引擎将所有的镜像表数据,通过我们指定的主键进行Join;最终形成一份数据大宽表;供引擎创建索引使用;这一步数据Join后,还可以对最终的数据通过UDF进行进一步的清洗/过滤处理,验证通过的数据才会进入到大宽表;
  • 创建更新索引:Ha3离线引擎通过buildService,使用大宽表的数据,与事先我们在Ha3引擎运维平台指定好的索引Schame对齐,重新构建索引;

以上流程可以通过Ha3引擎运维平台手动触发执行,执行完上述流程后,会生成一份新的索引;新的索引集群服务可用后,在线实时模块会将查询服务切换到新的索引集群上,完成一次索引的更新;这个完整流程我们将其称之为"全量";

全量完成后,当系统有新的商品信息变动,且相应的数据表有启用实时更新(我们称之为增量功能,DB表是通过binlog/ODPS表是通过Swift消息通知的方式实现),则离线DUMP引擎会感知到此次变动,进而将相应的镜像数据表中商品数据更新,并会按上述离线DUMP流程中的步骤,将这个改动信息一直向引擎上层投递,直至成功更新引擎索引中的相应数据,或者中途被系统规则丢弃为止;这个实时数据更新的流程我们称之为"增量";增量更新还有一条通道:算法同学可以使用特殊的方式,通过Swift增量消息的方式直接将需要更新的数据不通过DUMP流程,直接更新到Ha3引擎索引中。

闲鱼商品量飞速增长,目前已经达到数十亿;接入了数百的索引字段,由于闲鱼商品非结构化的原因,索引字段中只有一小部分供业务使用;另外大部分都是算法接入的索引,比如大量抽出来的标签数据,向量化数据等,这些向量化数据非常大;最终的情形表现为闲鱼商品搜索引擎的DUMP处理逻辑比较复杂,而且索引数据总量异常庞大,增量消息量也处在非常高的水位,再加上闲鱼商品单库存的现状;因此对数据更新的实时性要求非常高,这些都给稳定性带来了极大的制约。

索引数据是搜索引擎的内容核心,如果进入引擎的索引数据有问题,或者新变更的数据没有更新到引擎索引中,将直接影响搜索服务的质量;

搜索引擎单机房部署期间,时常会因为一些不稳定的因素,导致DUMP全量失败,或者增量延迟,甚至停止;一旦引擎DUMP出现问题,需要恢复基本都很困难,很多场景下甚至需要重新跑全量才能解决问题;但是闲鱼商品索引数据体量较大,做一次全量往往要大半天,没有办法快速止血,对业务造成了较大的影响;于是对搜索引擎进行双机房部署容灾(M/N机房),互为备份;

两个离线DUMP机房采用相同的引擎配置和相同的数据源,产出相同的索引数据,分别供两个在线机房使用,两个机房的在线流量比例也可以按需实时调整;当M机房出现不可逆问题时,自动或手动将流量全部切换到N机房,实现线上快速止血,然后再按部就班排查解决M机房的问题。

下图为最终的搜索机房部署情况;

图

进行引擎双机房部署虽然增大了机器资源成本,但是除了上述业务容灾优点外,还有以下好处;

  • 引擎需求的发布,之前缺乏有效的灰度流程;当搜索引擎有重大变更/升级,出现高风险的发布时,可以先在单机房小流量beta测试,数据对比验证通过后,再发布到另一个机房;
  • 平常单机房能支撑全部搜索查询业务的流量,当遇到大促或大型活动时,将两个机房同时挂载提供服务,这样搜索服务能力和容量直接能翻倍;避免了单机房频繁扩缩容的困扰;
  • 性能评估时,可以单独对未承载流量的机房进行压测,即使由于压测导致宕机也不会对线上业务造成影响;

流量隔离

上文独立网关部署一节中讲到,闲鱼搜索通过统一的业务搜索网关来提供简单一致的分布式服务,供闲鱼各上层搜索业务使用;使用统一的微服务,就必然带来上游不同业务优先级和可靠性保障的问题。

闲鱼搜索服务支撑了种类繁多的上游业务,为了统一对各业务场景的流量/服务质量进行度量和管理,在上游业务接入闲鱼搜索服务时,需要申请使用相应的业务来源,这个业务来源标示会伴随着整个搜索查询的生命周期;在日志采集时直接使用,从而可以针对业务维度进行监控告警,实时感知业务运行的健康情况(简单监控视图如下图),也可以对具体业务进行流量管控,降级限流等;

图

分级监控体系

对高稳定性系统,当出现问题,或者即将产生问题时,能即时感知,显得尤为重要;方便实时进行跟踪处理,防止继续扩大;目前使用的主要手段是建立健全完善的多维度监控告警体系;

引擎基础服务监控

使用监控可以快速发现问题,如果监控的粒度够细还能进行问题的快速定位;不过有时也会存在误报或者漏报的情况,因此真实的监控一定要结合每个业务自身系统的特性,梳理出关键链路,针对关键链路进行多维度360度无死角监控,并且进行合理的预警规则设置,监控预警才会比较有效;

闲鱼搜索引擎在线离线流程/各上游重要应用系统的核心链路上,建立了完备的日志数据采集模块,对关键指标进行了精准的监控预警设置;做到任何问题都能及时被感知到。下图是搜索服务相应核心日志以及监控告警情况。

图

模拟用户行为的在线业务监控

上文提到,闲鱼搜索引擎索引体量比较大,需要很多团队共同协作,搜索流程复杂度比较高;而且有算法同学的加入,对闲鱼非结构化的商品做了很多AI识别,加上闲鱼都是单库存商品,对引擎实时性要求非常高;前面已经做了一些容灾的保障方案;但是对实时性的感知上还需要更进一步,才能及时知道数据的准确情况,是否存在更新延迟,以及延迟时间大概是多久等一系列健康度信息;

解法是从业务层面进行实时性的监控告警;提取出闲鱼商品量比较大更新也比较频繁的类目K,在闲鱼的后台业务系统中,通过jkeins间隔一定时间(时间步长可以实时调整),使用类目K作为关键词和品类,根据商品更新时间索引降序招回,模拟用户轮询的方式发送搜索查询请求,召回满足要求的第一页商品;然后根据引擎召回数据的商品更新时间与当前系统时间进行差值比对,大于阈值时长(可以实时调整)说明存在较严重的数据更新延迟,则进行告警信息发送;

压测

全链路压测

对搜索服务以及各上游业务系统进行全链路压测改造;并使用线上真实的用户请求构造大批量的压测数据,在保证不影响线上业务正常进行的前提下,验证链路在超大流量模型下系统的容量和资源分配是否合理,找到链路中的性能瓶颈点,验证网络设备和集群容量。

引擎单链路压测

Ha3搜索引擎在线流程,支持通过回放线上高峰时段查询流量的方式,进行引擎在线服务性能压测。

Ha3搜索引擎离线流程,支持通过回放一段时间Swift增量消息的方式,进行引擎DUMP增量性能压测。

灰度发布

闲鱼商品的非结构化特性,离不开算法赋能;在我们的研发周期中,与两个算法团队,相当多的算法同学保持着深度合作;给闲鱼搜索带来了跨越式的发展,但是在团队协作和研发效率上也给我们带来了极大的挑战。

算法团队、引擎团队、加上业务工程团队,非常大的搜索项目开发小组,每周都有非常多的新算法模型,新的引擎改造,新的业务模块需要上线。

大量的新增逻辑改动直接上线,会带来很多问题;首先是代码层面,虽然预发环境有做充分测试,但也难保没有边缘逻辑存在测试遗漏的情况;即使预发测试都完全覆盖,但线上和预发终究环境不同,线上大流量环境及有可能会暴露一些隐藏的代码问题;第二方面,假使代码没有任何质量问题,但所有功能全部绑定上线,所有逻辑都混杂在一起,如何评定某个模块上线后的效果成为极大的困扰,特别是算法模型的优化,和业务上新模式的尝试,都需要根据详细的效果反馈数据指标来指导进行下一步的优化方向;

因此急需一套灰度实验保障体系,不仅可以用来协调和隔离整个搜索业务中各个模块,做到对各模块进行单独的效果评估;并且还能提高大家的协作效率,让各模块能进行快速试错,快速迭代;

图

为了解决以上非常重要的问题,业务工程团队开发了一套实验管理系统,用来进行搜索实验灰度调度管理,系统功能如上图所示;其具有以下特点。

  • 实验灵活方便,一个实验可以包含多个实验组件,一个实验组件可供多个实验使用;一个实验组件又可以包含多个实验分桶;
  • 各页面模块的实验都可以在系统中实时调控,包括实验的开/关;以及实验之间的关系处理;
  • 搜索实验埋点全链路打通,统计各种实验数据报表;
  • 统计数据接入了闲鱼门户和通天塔,可查看各个指标不同分桶的实验曲线;
  • 提升实验迭代速度,提升算法/业务效率,快速试错,加速搜索成交转化的增长;

应急预案

根据评估分析或经验,对搜索服务中潜在的或可能发生的突发事件的关键点,事先制定好应急处置方案;当满足一定的条件时进行多维度多层级的自动降级限流,或者配置手动预案进行人工干预;

任何时候发现线上问题,首先需要快速止血,避免问题的扩大;具有自动预案会自动发现问题,自动熔断,我们需要密切关注系统的运行情况,防止反弹;若出现反弹,并且对业务有较大影响时,快速人工介入执行降级预案;完成止血后再详细排查具体原因,当短时间无法确定问题根源时,如在问题出现时有过变更或发布,则第一时间回滚变更或发布。

图

对系统中各级的依赖服务,熔断降级已经系统负载保护,我们使用的是阿里巴巴自主研发的资源调用控制组件Sentinel[4],目前已经开源;或者也可以使用Hytrix降级限流工具;

问题排查

将闲鱼搜索链路接入阿里搜索问题排查平台,搜索实时查询请求的各个步骤输入的参数信息/产出的数据信息都会在此工具平台详细展示,方便各种问题的排查跟进,以及数据信息对比;

  • 可以对各查询条件下各个分桶的实验召回数据进行可视化显示,方便各个实验间的效果对比;以及每个召回商品的各类细节信息查看,包括业务数据和算法标签数据,还包含每个商品对应的各引擎插件算分情况,都能详细阅览;
  • 还可以根据商品Id,卖家Id,卖家Nick进行商品索引信息的披露;可以排查相应商品在引擎索引中的详细数据,如果数据和预想的有出入,具体是离线DUMP哪一步的处理逻辑导致的状态异常,都能一键查询。
  • 接入此问题排查平台后,能非常直观的掌握引擎的运行状况,搜索召回的链路状态;对快速发现问题根源,即时修复问题都有非常重大的作用!

总结与展望

本文主要介绍闲鱼如何保障复杂场景下搜索引擎服务的稳定性,主要从架构部署,隔离性,容量评估,风险感知&管控等方面进行阐述,介绍了如何稳定支撑20+线上搜索业务场景,做到了快速发现恢复线上问题,高效提前预知规避风险案例50+,极大程度提升了搜索服务的用户体验,保障了闲鱼搜索全年无故障;

经过上述治理方案后,闲鱼搜索系统稳定性得到了极大的保障,同时我们也会继续深耕,在搜索能力的高可用、更易用上更进一步,让上游业务更加顺滑;

希望给各位读者带来一些思考和启发。

参考资料

注解

  1. BuildService: 为Ha3提供全量、增量、实时索引build服务的系统。
  2. DUMP: 我们把索引数据的生成过程称作离线DUMP。搜索引擎的全量/增量索引是通过BuildService系统生成的,然后分发给在线部分的Searcher。
  3. Swift: Swift是一个高效可靠的计算与存储分离的消息系统。
  4. Searcher: Searcher是搜索引擎查询的执行者,倒排索引召回、统计、条件过滤、文档打分及排序及摘要生成的过程都是在Searcher上完成的。根据业务的需要,有时也会把摘要(Summary)单独分出来,搭建一套独立的摘要集群。
  5. **SP: **Search Planner,是一个统一的搜索服务接口。它的职责是根据用户指定的查询条件(关键词、类目等),得到Query Plan的结果,制定查询计划,查询搜索的各大系统,直接得到最终结果返回给上游业务。
  6. QP: Query Planner,是用来预测用户query的搜索意图的查询服务。它的职责是通过预测用户query的搜索意图和收集相关信息来决定去哪个数据集合(引擎)检索数据、检索什么样的数据、对检索的数据辅助排序。
  7. QueryService: QueryService是搜索在线查询部分,在其系统内部,包含Qrs和Searcher两种基本的角色。
  8. Qrs: Query result searcher,是用于接收用户查询,将用户查询分发给Searcher,收集Searcher返回的结果作整合,最终返回给上游业务。
  9. ODPS: Open Data Processing Service,开放数据处理服务;ODPS是阿里巴巴自主研发的分布式海量数据处理平台,提供了丰富的数据处理功能和灵活的编程框架。主要服务于批量结构化数据的存储和计算,可以提供海量数据仓库的解决方案以及针对大数据的分析建模服务。本文中的ODPS特指其中存储的离线表数据。

引用资料