一.前言
二.搜索系统架构演进
2.1.到家搜索系统1.0
基于LBS搜索召回场景
建立“可用”的搜索系统
小结
2.2.到家搜索系统2.0
重构召回
排序模型小试牛刀
建立索引容灾能力
小结
2.3.到家搜索系统3.0
精细化多路召回
模型升级 算法赋能
路由平台自动容灾
三.搜索系统整体架构
四.展望与总结
京东到家作为行业领先的即时零售电商平台,依托达达快送的高效配送和大量优秀零售合作伙伴,为消费者提供海量商品小时达的极致服务体验。随着平台中的商品供给日益丰富,帮助用户准确寻找目标商品的能力就会显得越来越重要,而搜索系统在电商平台中即承担着这样的职责。
在到家平台中,搜索系统承接了多个主要流量入口,在不同业务场景下为用户提供了多元化的关键字商品检索能力。目前,搜索系统在平台中覆盖的主要场景有首页搜索、频道页搜索、秒杀活动页搜索、券购搜索及到家小程序平台搜索等,如下图所示:
随着到家业务发展,到家的用户量级也在持续增加,为了满足用户购物需求,给用户一个便捷的购物体验,搜索系统一直在持续地进行架构迭代和策略升级。本文将对到家搜索系统架构演进历程进行详细介绍。
由于到家是基于LBS的即时零售业务场景,所以在搜索系统架构上与传统电商搜索相比也会有所区别。基于LBS的搜索系统在保证商品相关性和排序结果合理性的同时还要通过地理位置因素对召回商品范围进行限定。在到家系统中,我们采取的方法是通过墨卡托投影原理将门店的配送范围映射为多个有限区域块,再结合倒排索引找出覆盖目标位置的门店供给列表。对应到搜索场景中,我们所需要做的就是在给定的门店列表范围内通过Query进行高效地商品查询。
在到家发展初期,搜索系统设计思路以简单、可用为目标,架构上采用分层单体架构。在实现上,通过对当前供给门店列表进行ES terms查询来保证LBS条件限定,而Query与商品的匹配主要依靠对Sku名称进行ES的query_string查询。同时,对用户输入的Query会通过分词方式进行简单的品牌品类成分信息提取,并用于对查询语句的boost提权中。ES查询结果顺序就是搜索系统的返回顺序,分页也交由ES来控制。这样一来,就实现了1.0版本的到家搜索系统。
随着业务的发展,平台上门店和商品的供给数量呈爆发式增长,最初这版搜索架构在业务支持上显得愈发吃力。主要体现在以下几点:
1. ES深分页瓶颈在召回期间通过ES的from size方式进行分页控制。在深度分页情况下,这种查询方式非常低效,查询性能会直线下降。
2. 缺乏完整召回能力由于通过ES进行分页控制,所以每次请求搜索服务内部只能从ES拿到当前页的返回数据,无法得到完整召回数据。但一个完整的搜索系统应该包含对搜索结果多维度筛选功能、搜索结果聚合展示功能,如果每次只能拿到一页数据,那么在业务玩法上就会受到极大的限制。
3. 排序干预能力有限在商品搜索场景中,ES的bm25评分可以反映出Query与Sku名称间的文本相关性,但除此之外,搜索排序逻辑还需要关注商品的价格、评价、门店信用、商家质量、促销力度等维度特征,仅通过bm25评分作为排序依据是远远不够的。但如果所有排序因素都通过ES加权语句实现,最终查询语句就会变得十分复杂,并且在索引中还要维护所有排序特征信息,这显然不太现实。所以,需要建立一套独立的搜索排序服务来承接这样的职责。
4. ES集群单点依赖搜索业务最基本的能力就是召回,而召回能力又建立在了底层索引数据基础之上。在搜索1.0版本中底层索引由一套ES集群支撑,但这就会造成整个搜索系统对这套ES集群的单点依赖,如果ES出现问题那么整个搜索业务就会面临瘫痪的风险。
将1.0版本搜索系统中的主要问题进行归类可以发现,除了底层集群单点依赖之外,其余三个问题之间都有着直接或间接关系。
ES分页依赖 ——> 无法完整召回 ——> 排序干预能力有限
所以,优化工作的核心目标是要替换掉当前的ES分页方式,将全部命中数据完整召回。基于以上思考,我们提出了以下方案:
弃用ES分页,全量召回内存分页
抽象排序模块,独立服务化部署
首先需要考虑的问题是如何将全量数据召回到内存中。如果from 0 size n设置一个较大的数值,那么ES每一个分片均会召回n条数据后再汇总重排,引发上文中提到的深分页问题。而ES的另外两种分页方式scroll和search after更是不满足C端场景使用需求,尤其是scroll在高并发场景下还会有极大的性能风险。相比之下,还是from size的查询方式更适合当前C端搜索场景需要。
在确定from size查询方式后,我们尝试找到一个即满足业务需求又符合性能要求的size临界值。随着size增大,ES的查询性能持续下降。例如,在600qps和5000qps场景分别测试不同size查询性能,得出如下结果:
并发 | size=1000 |
size=1500 |
size=2000 |
---|---|---|---|
600qps | TP99=80ms | TP99=110ms | TP99=170ms |
5000qps | TP99=140ms | TP99=220ms | TP99=360ms |
再结合上下游接口超时要求,最终确定size可取的最大值为1500左右(不同业务需要结合实际使用的ES集群配置、索引结构以及查询语句进行实际测试)。但是,面对区域内动辄上千的门店覆盖,召回1500个商品显然无法满足业务需要。
对此,我们采取了一个折中逻辑,将全部门店等量拆分为多组(以下图为例,每个group包含m个门店),每组门店单独进行一次ES查询,size设置为1500。虽然1500的召回量不足以覆盖所有门店的全部相关商品,但对于一组门店还是相对充足的。通过调整门店分组大小可以对召回率和ES负载进行平衡,在到家搜索的实践场景中,采取的是30个门店一组。
在有了完整召回能力后,下一步优化的重点就是建立对全量召回结果的排序能力,我们的具体做法如下:
首先,将排序逻辑与搜索核心服务解耦,抽象后独立部署维护,以便后期排序逻辑的频繁迭代不会对搜索召回模块造成影响。
其次,引入大量业务特征,例如促销、价格、销量、复购等,并将ES返回的bm25评分作为其中一项特征,通过所有特征加权分来进行排序。这种排序方式简单有效,在早期时候也取得了不小的收益。
最后,在此基础上引入了LR线性模型,通过LR模型来计算权重w取值,排序效果又进一步得到了提升。
到家在这个时期无论业务体量还是用户体量相比最初均有了几何倍数增长,所以解决单点依赖风险保障服务的高可用性也同样十分迫切。
从宏观链路上看,整个搜索服务中最薄弱的环节就是底层索引。虽然ES采用了高可用的分布式节点部署、主副分片自动平衡等优秀的架构设计,但在极端情况下依然会有集群异常情况产生,例如ES集群由网络异常引起的脑裂问题、主副分片节点同时宕机问题等。
所以,建立索引层容灾能力才是搜索系统稳定性的关键,常见的做法有以下几种:
1. ES节点跨机房异地部署
描述:将ES集群中不同节点部署在不同机房,降低单机房网络或故障导致集群不可用的风险。
优点:实现简单、无需额外操作、索引文件一定无法横向扩展。
缺点:需要解决跨机房网络访问延迟问题,集群存在脑裂风险增加。
2. 定期快照
描述:定期对ES索引生成快照,并将快照数据备份到外部存储。备份的数据可以在备集群中恢复。
优点:如果集群宕机,可以通过快照文件将数据恢复到其他备用集群,保证主要数据不会丢失。
缺点:部分增量数据丢失、恢复时间长、无法提供集群的横向扩展能力。
3. 集群双写
描述:在上层写入应用中将索引数据双写到两(多)个集群中,多个集群数据保持一致。
优点:由双集群保证高可用,异常概率大大降低。还可以借助上层路由策略,对请求流量在集群间分流,做到真正意义上高扩展。
缺点:多集群数据难以保证绝对一致;硬件资源成本成倍增加;多集群使用需要增加路由逻辑控制。
在到家的搜索应用场景中,我们更在意的是服务稳定性以及故障短时间恢复能力,所以最终采用了异地部署加集群双写的综合方案。底层索引由两套ES集群支撑,所有ES节点分布在两个机房部署。上层服务双写保证索引数据的准一致。
在应用服务内部使用Supplier的方式对ES连接进行管理,再通过外部Zookeeper进行集群路由配置的存储和通知。当需要修改路由配置时可在Zookeeper中进行调整,由Zookeeper通知所有应用服务实例。具体如下图所示:
通过一系列优化改造,搜索召回能力得到极大提升,漏召回现象明显减少。排序方面借助线性模型对召回集合整体排序,也做到了将高质量、高销量类型商品排在前边。但是此时整体召排结构和业内高水准架构设计相比还是有不小差距,很快又遇到了新的痛点:
召回策略单一所有Query均使用统一分词匹配+品牌分类提权的方式召回,在召回策略控制上过于单一。不同Query在语义上强调的重点不同,很难通过一套策略适配所有Query。
线性排序模型瓶颈虽然线性模型排序结果相比1.0版本上有了很大的提升,但线性模型也存在一些天然的弊端。例如需要对特征归一化处理,O2O场景非标品比例较高,特征统一归一效果太差,导致模型效果不及预期。另外线性模型学习交叉特征能力弱,无法表达复杂场景,模型优化天花板较低。
在容灾能力上,虽然初步实现了对底层多集群的调度控制,但在实际使用场景中也发现了很多问题,例如:
非平台化接入方式接入双集群需要在应用代码中进行额外开发,代码侵入严重,且能力无法复制。
异常处理需要人工介入发生异常时集群流量无法自动熔断,需要人工操作集群切换。
虽然到家搜索2.0在召排能力及容灾能力上都得到了一定的加强,但也只能算是将1.0版本的“可用”水平提升到了“易用”水平,距离“好用”的水平还有着不小的距离。
3.0版本的搜索系统优化目标上主要分成策略和架构两个部分。
策略上,需要在保证召回率的同时限制错召过召问题,对召回结果需要建立一套更完善的排序策略,使排序结果更加合理,整体流量分发更加高效。
架构上,期望打造一个高性能、高可用、高扩展的搜索系统,进一步增强底层容灾能力,提升系统横向扩展能力。
为此,提出了以下几点优化方向:
细分query类型,差异化多路召回
迭代排序模型,提升排序效果
集群路由控制平台化,异常自动熔断
在之前的版本中,搜索主要的索引匹配逻辑是通过Query对ES中Sku名称进行分词查询。但如果底层分词策略调整导致分词字典发生变化,那么就需要对整个索引进行重建才能生效,十分不灵活。另外一些隐藏的商品属性在Sku的名称中可能并不存在,导致无法与Query匹配,这也会引起一系列的漏召问题。
为了提升召回匹配相关性,我们在离线对商品的名称、品牌、分类、属性、聚类主题等进行多维度成分提取,将提取出来的数据作为召回通道存储在ES中。同时对实时请求的Query也做类似的成分提取。在召回时,我们将原来分词匹配方式改为了Query与Sku之间的成分匹配,通过不同成分信息间的组合限制方式保证了召回的相关性。
另外,为了能实现针对不同类型Query灵活制定召回策略,我们将Query按照组成成分结构划分成了多种类型,并且针对每一种Query类型都分别制定不同的三级召回策略(以下简称L1,L2,L3)。三级召回策略定位不同,
L1精准意图召回:必须保证Sku与Query成分完全匹配
L2意图扩展召回:牺牲部分非核心成分匹配限定,但对品类这种核心成分保持了限定,用于扩充召回丰富度
L3补充召回:文本分词匹配,不做成分限定
不同类型下具体三级召回策略均不相同,在语义相关性限制力度上依次递减,L1 > L2 > L3。
在召回阶段,将三级召回策略与2.0版本引入的门店分组召回方式相结合,每组门店分别进行三次策略召回。在3.0版本中对门店分组策略也进行了优化,原来按数量等分的方式可能会将多个供给充足的门店分到同一组中,这样在面对召回范围宽泛的搜索词时就会发生召回截取,导致召回率降低。所以,在处理分店分组阶段,我们结合了门店的静态供给数量、行业类型对门店分组打散处理,将同行业中供给量充足的门店分配到不同组中,降低召回截取概率,进一步提升了召回率。
在系统架构上,将意图识别模块、召回模块从原系统中解耦独立部署。将流程调度以及搜索相关的业务处理逻辑封装在搜索中台内部,与意图服务、召回服务以及排序服务共同组成了搜索的核心模块,在架构上做到低耦合与高内聚。
在2.0版本中我们尝试了通过简单线性模型对搜索结果进行排序优化,并且取得了不错的效果,但也很快就触碰到了线性模型的天花板。业务发展到这个时期,通过几个简单业务特征结合线性模型的排序方式已经难以满足业务需要,需要深入挖掘多维度特征甚至交叉特征,结合更复杂的模型对整个排序流程进行重构。
搜索排序的最基本原则是要优先保证与Query相关性更高的商品排到前边。基于搜索3.0版本在多路召回上的调整,使得召回结果带有了天然的相关性属性,并且L1、L2、L3的召回结果在相关性上逐层递减。但是面向召回的三级分层方式在排序场景下还是显得粒度过粗,所以在召回三级Level的基础之上排序内部又将其分为了六层Rank,所有排序逻辑均在Rank层内执行,不同分层的商品不跨层调整,如图:
在排序流程上,采用了预排、粗排、精排、策略重排这样的四级排序方式,每级排序逻辑的实现方式各有不同,承载的主要职责也有所不同。
预排:主要用于精选召回商品,将召回后的商品通过召回Level等级和静态质量分进行预排序,最终选取Top 3000结果进入到粗排阶段。
粗排:负责初步筛选召回结果中用户可能感兴趣的商品,尽量让这类商品排到前边并有机会进入精排阶段。考虑到粗排商品量级较大,实现上我们选择了性能较好的XGB树模型。
精排:使用TensorFlow深度模型对粗排结果中的Top 200个Sku进行局部的精细化重排。
策略重排:基于模型排序的结果,根据具体的业务规则,对排序结果进行最终的微调。
在算法模型预测逻辑的架构设计上,采用了模型中台的方式,将常用模型在其内部进行了封装,结合配套的模型配置平台可对模型特征及特征获取方式进行配置化管理。上层排序服务可通过预先在配置平台设置的modelTag对模型中台发起预测请求。具体的模型实现逻辑对外部无感知。
为了解决2.0版本中的多集群管理方面的遗留问题,在3.0版本中对底层的集群路由方案也做了全面升级,具体如下图:
首先,为了简化接入流程,将原来应用服务内部代码侵入的管理方式改为了路由管理客户端接入的方式,路由策略配置交由服务端进行管理,可以做到配置调整实时生效。这样任何服务均可以通过平台化的方式接入路由平台,极大降低了接入成本。
其次,应用服务通过通路由客户端获取ES集群连接对象,在执行完成ES请求操作后,路由客户端会将本次ES操作的成功失败状态上报到路由服务端。参考熔断器的设计思想,由服务端对上报信息进行定时汇总统计,如果发现近一个时间窗口内ES的异常请求操作比例触发了熔断阈值,服务端就会通过主动修改路由配置策略屏蔽该集群的路由访问,并将修改结果广播通知到客户端。
最后,在路由平台的设计上还兼容了多套集群(超过两套以上)灾备的问题,任何接入方均可根据需要灵活配置。
具体路由客户端和服务端交互方式如下图所示:
路由客户端内部实现了对所有集群路由规则逻辑的封装,对外部黑盒处理。具体的路由策略配置交给路由服务端管理。搜索服务接入路由平台后,可实时通过路由客户端提供的ES路由查询功能来获取ES客户端代理。完成与ES的请求交互后,客户端代理会将请求的成功失败状态通过队列+异步的方式自动上报到服务端,用于ES集群状态监控。
经过长期迭代,当前的到家搜索系统从宏观架构上看可以被分为如下几个模块:搜索业务模块、搜索核心模块、算法模型预测模块、索引数据模块、辅助功能模块。如下图所示:
搜索业务模块:承接来自前端所有搜索业务相关请求,是搜索服务的最上层出口。向下与搜索中台对接,获取搜索相关业务数据,并根据业务场景需要补全所需数据,结构化封装后返回给前端。起着承上启下的作用。
搜索核心模块:搜索核心模块由搜索中台、意图识别、搜索召回、搜索业务排序几个服务组成,是整个搜索架构中最重要的组成部分。
算法模型预测模块:以模型预测中台服务为统一出口,将常用的模型预测算法进行了统一封装,对接了例如LR、XGB、Tensorflow等算法模型,提供了多样化的算法预测能力。在搜索服务链路中,模型预测模块主要承担着计算搜索结果排序分的作用。
索引数据模块:到家搜索系统底层索引数据由两套ES集群支持,两套集群中索引数据内容相同,由索引构建服务负责对两套ES进行索引全量构建以及增量更新。两套集群既可以独立承接线上流量互为主备,又可以同时承接峰值流量进行压力分流。
辅助功能模块:结合研发、测试、运营人员的使用需求,在搜索平台中提供了搜索结果Debug解释、搜索策略Diff分析、搜索结果badcase干预、搜索字典动态管理配置等实用功能,极大提升了相关人员的工作效率。
从3.0版本的宏观架构上看,我们将搜索这样的复杂业务按领域驱动设计思想进行了系统拆分,每个子系统负责专属业务功能,系统间设立边界隔离,系统内部根据业务特性进行垂直化建设,整体模块划分已相对清晰。但是从细节逻辑的处理方式上看仍然存在着较多的问题,需要我们在未来的工作中继续完善,例如:
L3补充召回合理性
随着L1L2策略日趋完善,L3使用的文本召回的方式已经很难对L1L2进行有效的召回补充,补充的商品大多数相关性较差。
模型维护效率低下
目前模型数量相对可控,模型的训练、更新等操作主要依靠人工完成。在未来模型越来越多的情况下,维护效率难以保证。
欠缺垂类场景专属策略
同样的Query在不同垂类场景下会有不同的意图含义。例如“苹果”在生鲜场景下是水果意图,但在3C场景下却是手机意图。建立不同垂类场景的个性化召排策略也是未来优化工作的一个主要方向。
系统优化永远没有终点,目前我们已经开始着手准备到家搜索4.0版本的优化工作。在下一个版本中,针对遗留问题有以下几个重点优化方向:
向量召回:万物皆可embedding,使用向量召回方式替代现有的L3策略对召回结果进行补充。
自动化模型迭代:构建一个强大的算法平台,通过任务托管的方式将模型训练及上线工作自动化运行,避免因为样本过时导致的模型效果衰减,也可以提高模型的管理效率。
场景化召排策略:针对不同的上层业务入口或者垂类意图,制定差异化的召排策略,挖掘搜索潜力。
经过几个版本的优化,到家搜索系统在逐渐地向“好用”水平看齐。在这个过程中我们也深刻体会到“召回决定搜索的下限,排序决定搜索的上限”,二者相辅相成,是搜索系统最重要的两大核心能力。在未来的工作中,我们依然会坚持以提升用户搜索体验为最终目标,围绕召回、排序两大搜索核心能力不断迭代。
京东物流日本新开自营仓,还要将一盘货推向全球!
1601 阅读快递复盘:哪些网点能赚到钱?巨头们的新动向是什么?
1478 阅读抖音即时零售关于招募合作仓网服务商的公告
1407 阅读解读ASCM 2025年供应链十大趋势:技术创新与地缘政治博弈
1298 阅读系统工程视角下的全球供应链韧性构建
1199 阅读解密极兔云仓:国内快速开拓、仓内履约100%
1126 阅读这件事,顺丰暖心坚持了十年
1110 阅读顺丰无人机配送中国电信号卡实现全国首飞
1086 阅读中国邮政1亿元成立无人机公司
1036 阅读物流巨头分拆大动作:零担业务将上市,估值超2000亿元
954 阅读