京东亿级商品搜索核心技术解密
<h3><strong>京东商品搜索简介</strong></h3> <p>京东商品搜索引擎是搜索推荐部自主研发的商品搜索引擎,主要功能是为海量京东用户提供精准、快速的购物体验。目前入口主要有PC/移动/微信/手Q搜索、移动列表页、店铺搜索、店铺列表等。虽然只有短短几年的时间,系统已经能够支持日均PV过亿的请求,并且经过了多次618店庆和双11的考验。</p> <p>与人们日常使用的如谷歌、百度等大搜索(或称为“全文搜索”)引擎相比,京东商品搜索引擎与前者有相通之处,比如“覆盖海量数据”、“超高并发查询”以及“超快速的请求响应时间”,同时又有自身显著的业务特点:</p> <ul> <li> <p>结构化的商品数据,需要从商品、库存、价格、促销、仓储等多个系统进行抽取;</p> </li> <li> <p>极高的召回率要求,保证每一个状态正常的商品都能够被搜索到;</p> </li> <li> <p>商品信息的及时更新,目的是为了保证用户极佳的购物体验——比如不能给用户展示出下柜的商品,或者商品的实时价格超出了用户搜索限定的范围。这就要求我们的搜索引擎要做到和各个系统的信息时刻保持同步,目前每天更新次数过亿;</p> </li> <li> <p>逻辑复杂的商品业务,需要存储的商品属性信息是倒排索引信息的2倍之多;</p> </li> <li> <p>用户购物的个性化需求,要求系统实现用户标签与商品标签的匹配。</p> </li> </ul> <p>正是由于既要兼顾大搜索引擎的通用需求,同时要契合京东的业务特点,我们将系统架构分为四个部分:1. 爬虫系统、2. 离线信息处理系统、3. 索引系统、4. 搜索服务系统。</p> <p>为了使各位读者能够深入了解京东商品搜索引擎的架构,本文首先介绍了商品搜索的总体架构,然后依次介绍了爬虫系统、离线信息处理系统等各个部分,并且对搜索技术的最新研究方向做展望,希望对各位读者有所帮助。</p> <h3><strong>总体架构</strong></h3> <p>京东商品搜索引擎的整体架构如下图所示:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/8f3235e7cc342d616cd50604e3b88483.jpg"></p> <p>从上到下共分为3层。最上层是由搜索的前端UI层,负责页面展示。</p> <p>中间层是由搜索索引服务、SUG搜索、相关搜索、划词服务和兜底服务组成。其中,SUG搜索提供输入框下拉提示词功能;相关搜索提供与query相关的其他搜索词服务;划词服务提供去除query部分词的功能;兜底服务用于索引服务异常情况下提供托底,保证用户基本的搜索可用。</p> <p>最下层是索引生产端,主要功能是对接商品、库存、价格、促销、仓储等众多外部系统,整合相关数据生产全量和增量数据的索引,为在线检索服务集群提供全量索引和实时索引数据。</p> <h3><strong>爬虫系统</strong></h3> <p>商品搜索引擎的核心是建立商品索引,而建立索引需要详细的商品信息数据。我们利用大数据平台的数据库抽取接口和中间件系统,实现了站内商品爬虫系统,用来抽取数据库中的商品信息和及时发现变化的商品信息。从实践的效果上来看,爬虫系统表现是非常稳定和可靠的。</p> <h3><strong>离线信息处理系统</strong></h3> <p>离线信息处理系统主要功能是用来建立商品搜索引擎的待索引数据,包括全量待索引数据和增量待索引数据。</p> <p>目前商品全量待索引数据按天进行更新,一部分是商品的基础属性信息,如商品sku、商品名称、颜色、规格、风格、材质面料等等,属于比较稳定、短时期内不会变化的数据。另外一部分是商品销售信息,如商品销量、销售额、评论等,属于易变数据。这些数据散布于多个系统中,使用的存储也各不相同。因此需要对这些来源分散的数据在商品维度进行合并,生成“商品全量待索引宽表”。目前我们建立的全量待索引宽表,不仅应用于搜索引擎服务,还同时应用于个性化推荐等其他产品服务当中。但是仅生成宽表是无法完成搜索引擎的索引需求的,因此我们利用Hadoop/MapReduce计算框架对宽表数据进行清洗,并且依照离线业务逻辑规则对数据进行二次“加工”,最终生成一份全量待索引数据。</p> <p>有些商品信息,比如“价格”、“库存”、“上下架”等,经常会产生变化,因此对这些数据做全量索引满足不了商品搜索引擎的需求。为了解决数据实时性的强需求,我们建立了增量索引作为全量索引的补充。具体细节上,采用和全量索引类似的方法对数据进行处理,生成增量待索引数据。为了保证增量数据的及时性和准确性,离线信息处理系统会实时调用各商品信息接口获取数据,完成增量待索引数据的在线组装和生产。</p> <h3><strong>索引系统</strong></h3> <p>索引系统是商品搜索引擎的核心,主要功能是把以商品为维度进行存储的待索引数据,转换成以关键字为维度进行存储的数据,用于搜索引擎上层服务进行调用。这里待索引数据指前面离线信息处理系统生成的全量待索引数据和增量待索引数据。</p> <p>此系统对于全量和增量的处理是一致的,唯一的区别在于待处理数据量的差异。一般情况下,全量数据索引由于数据量庞大,采用Hadoop/MapReduce进行;实时数据量小,采用单机进行索引生产。</p> <p>为了满足分布式检索的需求,索引系统还会对索引数据进行分片处理,即按照一定策略将索引数据拆分成较小索引片,用于搜索服务系统调用。</p> <h3><strong>搜索服务系统</strong></h3> <p>搜索索引服务系统主要功能是接受用户请求并响应,返回搜索结果。搜索服务系统的发展也经历了从无到有,从简单到丰富到过程。主要分为如下几个阶段:</p> <ul> <li> <p>最初,搜索服务只有1列searcher组成在线检索服务,能够完成一些简单的商品搜索;</p> </li> <li> <p>随着访问量的增长,搜索服务系统增加了缓存模块,大大加快了请求处理的速度;</p> </li> <li> <p>接下来为了提高用户体验,我们增加了Query Processor服务,负责用户查询意图分析,提升搜索的准确性。目前Query Processor已经成为了一个融合自然语言处理、机器学习等先进技术的成熟服务,并且还在不断的进行优化;</p> </li> <li> <p>为了支持个性化,增加了User Profile服务,负责查询用户标签。将商品的标签与用户标签是否匹配,作为一个特征加入排序因子,实现搜索的千人千面;</p> </li> <li> <p>接着随着数据量(商品量)的增长,我们将结果包装功能从检索服务中独立出去,成为detail服务(基于缓存云实现的商品信息KV查询服务);</p> </li> <li> <p>将检索服务进行分片化处理,即采用类似数据库分库分表的思想,对商品id,进行hash处理后进行分片,保证各个分片数据均匀。查询时,将一个搜索请求分配到多个searcher列上,并行检索,进行局部排序后返回给merger。然后merger服务,将多个分片的检索结果进行归并,然后再进行业务排序和加工,确定要返回的商品,最后调用detail服务包装,将结果返给给blender。blender将多个搜索的结果进行融合,返回给前端。需要说明的是,此时搜索服务系统已经成为了一个“多blender&多Searcher&多merger”的系统。今后无论是访问量的增长或者数据量的增长,都可以通过扩容来满足。尤其对于618店庆、11.11之类的峰值搜索量剧增的情况下,可通过增加每个searcher列服务器的数量来满足需求。随着商品数据的不断增加,只要适时对数据做更多的分片,相应增加searcher列就可以了。检索服务分片化机制的建立也标志着京东搜索基础服务系统已经趋于完备。</p> </li> </ul> <p>完整的搜索索引服务架构,如下图所示:</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/13b8255a6d2cae9f77ee8cb76d98131f.png"></p> <p>搜索请求流程如下:</p> <ol> <li> <p>外部请求通过vip到达blender;</p> </li> <li> <p>Blender调用QP,QP调用运营平台,其中运营平台主要负责将日常运营数据服务化,QP负责分析query;</p> </li> <li> <p>Blender同时请求Merger和其他垂直搜索服务;</p> </li> <li> <p>Merger调用UserProfile获取用户标签信息;</p> </li> <li> <p>Merger将请求发给每列searcher;</p> </li> <li> <p>每个searcher召回商品并返给Merger;</p> </li> <li> <p>Merger合并多列searcher的结果,确定需要输出的商品,请求Datail包装对应的商品信息;</p> </li> <li> <p>Detail包装商品信息返给Merger;</p> </li> <li> <p>Merger将包装好的商品返给blender;</p> </li> <li> <p>Blender将merger返回的结果与其他垂直搜索结果进行合并,最终返回给前端。</p> </li> </ol> <p>Blender、Merger、Searcher和Detail是整个系统的核心组件,它们之间的调用关系由Clustermap管理。各个模块将自己的服务注册到ClusterMap,同时从ClusterMap订阅其调用模块的信息来确定实际调用关系。</p> <p>简要搜索服务流程,如下图所示( 搜索服务系统内部处理流程 ):</p> <p style="text-align: center;"><img src="https://simg.open-open.com/show/7d70cbfaf6f364b882e5b807d7cd3988.jpg"></p> <p>图中名词解释如下:</p> <ul> <li> <p>Page cache:页面缓存,blender模块直接缓存输出的页面,merger缓存了多页商品id;</p> </li> <li> <p>Attr cache:属性缓存,缓存的搜索属性导航区的数据;</p> </li> <li> <p>Doc cache:缓存查询词从全量索引召回的结果;</p> </li> <li> <p>OP:运营平台服务,负责搜索运营数据的服务化;</p> </li> <li> <p>QP:query processor,负责query意图识别。</p> </li> </ul> <p>用户请求发送到blender,首先解析参数。如果命中blender page cache直接返回给用户。如果没有命中,则调用运营平台服务(OP)和QP,并将其传给Merger,Merge会检查是否命中Attr cache,如果命中并且恰好仅请求属性汇总结果,直接返回给blender。否则进一步查看是否命中merger page cahce,如果命中直接调用detail包装,返给blender。如果没有命中,则调用User Profile获取用户标签,将其传给searcher(篇幅所限,图中只列了一个searcher,实际是多个)。Searcher接到请求,判断是否命中doc cache,如果命中doc cache,则拉取增量结果;如果没有命中doc cahe,则拉取全量和增量结果。然后依次进行排序、在线业务处理,把结果返给merger。Merger合并多个searcher结果,排序、在线业务处理,最后调用detail包装,最后将结果返给blender,blender合并多个搜索结果后返回给用户。</p> <p>作为一个高并发系统,为了保证高召回率和低响应延时,我们把整个搜索服务流程的处理全部放在内存当中进行计算。多个searcher并发处理请求,同时单个searcher内部采用线程池技术,即所有线程之间共享倒排索引和商品属性信息,提高内存使用效率;每个查询使用一个独立线程串行执行,保证并发的多个查询线程之间互不影响。此外通过合理的设置线程池的大小,我们可以保证系统的CPU资源得到充分利用。在上述两个方面对系统进行优化之后,整个搜索服务系统的稳定性、召回率、内存使用率、计算速度等指标都有大幅度的提高。但是我们改进系统的步伐并没有停歇,因为通过实践发现基于内存和线程池的搜索服务仍然有几个瓶颈点亟需解决,主要包括:拉取倒排、排序和在线业务处理。针对这些问题,我们进行了二次优化,主要包括如下措施:</p> <p><strong>1. 多级缓存策略</strong></p> <ol> <li> <p>Blender Page cache:由于搜索符合互联网的二八法则,20%热门查询频度非常高,占每天搜索请求量80%。针对这一特点,搜索第一级缓存以查询请求为key,将返回给用户的页面作为value。对于完全相同的请求,直接从缓存返回结果。页面缓存策略上线伊始,缓存命中率就接近了30%,基本解决了当时的性能问题。</p> </li> <li> <p>Merge Page cache:随着业务的发展,排序结果需要针对不同用户实现个性化订制,这就导致请求中会包含用户的user pin。如果直接将user pin放入缓存作为key,会导致blender cache的key数量暴增,不但需要超大的缓存空间,同时缓存的命中率也会极低,最终会导致线上个性化服务的体验满意度降低。为了解决这个问题,将user_pin加入key,但是value只保存排序好的商品id,这样需要的缓存空间远远小于blender cache。当命中缓存后,调用detail直接进行结果包装。为了进一步提高缓存命中率,利用用户搜索的翻页习惯,即离线统计出用户的翻页数TP99,然后在value中缓存这些页面涉及到所有的商品id,从实践效果来看,用户后续的翻页请求大部分会命中cache。</p> </li> <li> <p>在深入分析了业务和排序的需求之后,我们发现拉取倒排的结果只和“查询词&筛选条件”有关,而与用户无关,因此可以按照“查询词&筛选条件”作为key的方式对其进行缓存。</p> </li> </ol> <p>虽然拉取倒排结果缓存的key很快就解决了,但是我们在解决Value的存储时遇到了两个问题:1)拉取倒排的结果非常之多,导致缓存过大;2)对此结果缓存,会降低实时索引的时效性。</p> <p>对于问题1),在分析了业务之后,对需要缓存的信息进行了大量的精简并采用压缩存储,最终将一个查询的缓存控制在0.5M以下。</p> <p>对于问题2),我们将拉取倒排结果分为两部分,第一部分是从全量索引拉取倒排的结果,第二部分是从实时索引拉取倒排的结果。为了和全量索引的更新频率保持同步,我们把第一部分数据进行缓存的周期置为1天。对于第二部分数据,由于增量结果远远少于全量结果(一般增量只有全量5%不到),每次缓存都进行实时计算,这就是图3中的doc cache机制。从实践中来看,命中doc cache的响应时间比未命中的降低了1-2个数量级。将来随着增量结果的积累,如果实时拉取倒排结果成为性能瓶颈,可以对增量索引分段也进行缓存。</p> <p><strong>2. 截断策略</strong></p> <p>对于有些热门查询,由于其结果较多,比如“男装”、“鞋”之类的query,原始查询结果几千万个,如果对这些结果挨个进行处理,性能会非常差。同时,从用户角度分析,一个查询只有排在最前面的结果对用户才有意义。通过分析用户翻页次数,可以得到截断保留topN结果。如何保证截断不影响用户体验呢?首先我们对商品建立离线模型,即为每个商品计算出一个质量分数据。然后在索引阶段,将所有商品按照质量分降序排列,保证在倒排链中,排在前面的商品质量分总是高于后面的。在线从前往后拉取倒排过程中,如果结果数达到10*topN时,停止拉取倒排。随后对结果计算文本相关性,再按照文本相关性取topN个。截断算法上线前后,虽然KPI指标无明显变化,但是对大结果查询性能提升了一个数量级。</p> <p><strong>3. 均匀分片策略</strong></p> <p>从总体架构图中我们可以看到,如果我们将一个term的倒排链进行均分,那么相应term的拉取倒排也会被分配至各个searcher列。正是由于各个searcher列是并行计算的,这样的均分操作就可以大大减少每个查询的平均响应时间。从理论上来讲,我们采用的均匀分片策略,也有效的契合了拉取倒排、排序、在线业务处理等CPU密集型的任务。但是分片增加,会带来硬件成本增高的后果,同时集群节点间的通信成本也会增加,需要进一步权衡折衷。</p> <p><strong>4. 业务优化</strong></p> <p>京东的搜索业务并不只有上面所述的策略和工程逻辑,还必须融合很多业务逻辑。由于每一次搜索几乎都会召回很多结果,如果业务逻辑处理不好,也会导致搜索体验不好。针对这一问题并没有通用的解决方法,但是通过实践我们总结出一个基本原则:在离线阶段完成尽可能多的业务逻辑,减少在线计算量!例如进行搜索排序时,我们需要根据用户搜索历史行为(浏览、点击、购买等)对召回的结果进行排序上的调整,在工程实现上我们会先离线统计出同一个query下所有用户对每个展示商品的行为,然后建立模型,计算出该query下每个商品的权重,将其以hash结构存储;在线排序时,直接以query+商品id为key,取出权重作为反馈特征参与综合排序。</p> <h3><strong>搜索技术的新发展</strong></h3> <p>我们在当前的架构基础之上,正在进行一些新的探索,比如场景搜索和图像搜索。</p> <h3><strong>场景搜索</strong></h3> <p>随着目前京东集团的业务的扩展,用户在使用搜索时,目的不仅仅是查找商品,还可能查询促销活动信息。为了满足这些新的需求,我们在目前商品索引融合了促销系统的数据。我们首先在Query Processor中增加对应意图的识别,然后将促销等数据转换为索引数据。只要Query Processor识别出用户提出这方便的查询意图,将对应的结果返回。</p> <h3><strong>图像搜索</strong></h3> <p>传统搜索仅仅针对文字,但是电商系统的商品图片非常重要,很多购买决策依赖于它。目前我们利用deep learning技术离线训练图片特征,并将其做成索引。当用户使用实拍图或者网图来搜索时,采用相同的方式提取特征,然后从索引中召回最相似商品返回给用户。</p> <p> </p> <p> </p> <p>来自:http://mp.weixin.qq.com/s/N2va4w1XERoEIh7ZwT4AUQ</p> <p> </p>
本文由用户 VaniaTeakle 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!