速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

闲鱼如何高效打造一个多业务、低侵入的搜索链路

  • 2021-03-27
  • 本文字数:4475 字

    阅读完需:约 15 分钟

闲鱼如何高效打造一个多业务、低侵入的搜索链路

问题及现状

闲鱼搜索很多场景基于集团搜索中台能力,纵观闲鱼搜索链路,存在多角色(工程、算法工程、算法等)、多业务(闲鱼无忧购、租房、帖子等)、多节点(离线数据源聚合、在线召回、URF Rank 等),具有明显的复杂性。并且闲鱼主搜仅存在一条链路支持搜索多业务发展,各角色、各业务、各节点处于高耦合串行迭代模式。在大数据量、多业务、多角色并行场景下,以下问题日益明显: 


1、迭代效率低、排期长,无法满足新业务快速迭代诉求:主要体现在数据量大,单次迭代周期长,以及多业务、多角色串行操作,耦合严重; 

2、风险高:不同业务、不同特性均在主引擎召回链路执行修改,侵入大,风险高; 

3、干预能力弱,业务间混排能力不足:缺少干预扶持能力,无法有效为创新业务提供定制化孵化能力。

闲鱼一次请求完整流程如下:



注:QP,即 Query Planner,主要用于预测用户 query 搜索意图。


SP 在测试环节下调用拓扑示例如下:


关键节点:

blender 是 SP 服务核心入口,主要职责包括解析用户 query 意图、引擎分页召回、搜索数据补齐等功能。

uniq_session 是召回核心入口,通过引擎召回、分页处理、URF Rank 等操作,获取最终商品列表 

ha3_searcher 是引擎召回入口,翻译用户 query 为 HA3 引擎查询 query,请求引擎在线服务,召回满足条件商品信息 。


uniq_summary 是商品信息补充入口,依据商品 id 列表,补齐商品详细信息并返回,最终在搜索结果页呈现给用户。

背景概述

闲鱼搜索很多场景基于集团搜索中台能力,搜索引擎分为数据源聚合(离线 dump)、全量/增量/实时索引构建及在线服务等部分,通过集团内部一系列处理阶段,对客户提供高可用高性能的搜索服务。服务架构如下:


其中:

SP/SPL 是集团内一套构建于 Wunder 上的开发工具,提供开发测试打包上线的视图界面以及一套业务函数库。HA3 是一套基于 suez 框架的全文检索引擎,提供丰富的在线查询子句,过滤子句,排序子句,聚合子句且支持用户自定义开发排序插件。


Qrs 用于接收用户查询,将用户查询分发给 Searcher,收集 Searcher 返回的结果作整合,最终返回给用户。Searcher 是搜索查询的执行者,主要包括倒排索引召回、统计、条件过滤、文档打分及排序及摘要生成。在实际的部署中,Qrs 和 Searcher 都是采用多行部署的方式,可以根据业务的流量变化作调整。Searcher 还可以根据业务的数据量调整列数,解决单机内存或磁盘放不下所有数据的问题。


剖开 HA3 引擎,我们可以粗略看下引擎侧索引的构建以及在线召回链路:



蓝色部分表示 HA3 在线服务,提供在线召回商品服务;绿色部分表示引擎数据源聚合(离线 dump)逻辑,将商品信息构建成对应的引擎数据源。下图为一个离线引擎 dump 的数据源图示例:

设计思路

由于我们要解决的问题是如何安全、灵活、高效地支撑多业务接入闲鱼搜索,并提供不同业务间混排能力,我们的设计想法如下:


1、支持业务间互相隔离,从引擎侧深层次隔离,提高整体迭代效率

2、支持多引擎并发召回能力,解决搜索 RT 恶化问题

3、支持多引擎召回结果合并能力,并统一传递给 URF Rank 执行混排

4、支持实时干预能力,满足不同业务孵化诉求


调研集团内部已有实现模式,并结合闲鱼搜索业务发展诉求,我们重新设计并执行闲鱼底层引擎召回逻辑升级流程,从单引擎单路召回架构升级为多引擎多路并发召回架构。



Search Planner 及搜索引擎层升级如下图所示:



主要升级节点如下:

1、步骤 1 中解析用户 query,获取多引擎标识,解析并缓存引擎配置信息 

2、步骤 3 流控模块从配置中心获取引擎定制化配置,并执行 query 改写逻辑,生成对应的 HA3 请求串 

3、步骤 4 依据 query 中携带的多引擎标识,并发请求不同引擎在线服务召回商品,并合并召回结果,统一调用步骤 5 的精排服务能力 

4、步骤 5 算法侧升级支持多引擎结果,支持多业务混排能力

实现方案

依照上节的设计思路,实现方案核心主要关注以下几点:


1、如何解析用户 query,并缓存用户 query 相关引擎配置信息 

每一个引擎均存在唯一的标识 id(便于说明:主搜引擎标识为 0,闲鱼无忧购引擎标识为 1,向量引擎标识为 2),上层搜索业务层依据不同业务场景组装引擎查询 query,包括组装引擎标识列表(比如 bizType=0,1,2,表示同时从主搜引擎、闲鱼无忧购引擎、向量引擎召回商品)。

Search Planner 核心解析逻辑如下:


-- 构建请求的biz列表function get_search_biz_type_list(query, param)     -- 搜索biz配置列表    local search_biz_type_list = {}    if query.bizType then        if type(query.bizType) == "string" then            table.insert(search_biz_type_list, query.bizType)        else             search_biz_type_list = query.bizType        end    end
if search_biz_type_list == nil or #search_biz_type_list < 1 then table.insert(search_biz_type_list, param.default_biz_type) end
local search_list = {} if not query._enable_multiplexed then table.insert(search_list, {biz = param.default_biz_type, biz_type = search_biz_type_list})
return search_list end
for _, type in ipairs(search_biz_type_list) do table.insert(search_list, {biz = type, biz_type=type}) end
return search_listend
复制代码


2、流控模块如何获取引擎定制化配置,执行 query 改写逻辑 


目前流控模块通过阿里巴巴集团内部广泛使用的 diamond 配置中心获取详细配置参数,并与引擎唯一标识绑定映射关系。Diamond 是淘宝内部广泛使用的配置中心,提供持久化管理和动态配置推送服务。采用推送服务,每次请求不再需要实时从远端获取配置,降低搜索 RT,提高搜索体验。同一环境下,Diamond 中通过唯一的 GroupId + DataId 标识参数配置。


function parse_diamond_config(query, param)    query._cluster_info = {}
-- 多引擎配置 local cluster_info_str = param.cluster_info local cluster_info = setup.split(cluster_info_str, ";")
if not cluster_info then return end
for _, info in ipairs(cluster_info) do local single_cluster_info = setup.split(info, ":")
if single_cluster_info and #single_cluster_info > 1 then query._cluster_info[single_cluster_info[1]] = single_cluster_info[2] end end
end
复制代码


query 改写逻辑封装在不同的函数组中,通过前置分支判断,调用函数组中不同改写入口,生成定制化 query。


-- 改写向量召回queryfunction rewrite_embedding_recall(query, switch, param)  -- 创建query  local que = {}  que._biz = query._biz  que._cluster_name = query._cluster_name
que.q = "NULL"
if really_need_search_embedding_recall(query) then local qp_idlefish_table_qp_reserve_str_4rs_query_vector = query._qp_idlefish_table_qp_reserve_str_4rs_query_vector local q_str = qp_idlefish_table_qp_reserve_str_4rs_query_vector .. "&n=" .. param.embedding_recall_search_hit
que.q = "sim_vec:" .. url_encode(q_str) end
-- 改写attribues if not switch.full_phase then que.attribute = "item_id,bizType" end
-- 改写rank que.rank = { -- rank 子句 rank_profile = 'RecallScorer' }
return queend
复制代码


3、如何并发执行多引擎召回、合并流程,预防搜索 RT 恶化 

借助于 SPL 异步协程能力,通过一条 lambda 函数,实现并行调用,以解决搜索 RT 恶化问题。如:value = search_http("/qp?xxx"):value(),即表示一次异步并发调用操作。


-- 解析入口function multi_parse(res_vec)    local items_search_lua_tables = {}
for _, p in ipairs(res_vec) do -- p.res:value()执行异步并发请求 local res = p and p.res and p.res:value() local biz = p and p.biz
... ...
local ret_table = res:getTable()
table.insert(items_search_lua_tables, {biz=biz, tbl = ret_table}) endend
复制代码


通过多引擎隔离、query 改写手段,各业务间能有效地解耦,满足各业务定制化诉求,并且对闲鱼主搜服务能力干扰最小,将问题集中于单业务内;

通过集成 dianmond 配置中心的流控模块,达到实时干预 query 搜索能力,并有效孵化创新业务,如当前闲鱼无忧购创新业务;

通过多引擎并发召回及合并能力,一是解决搜索 RT 恶化问题,二是结合 URF Rank,有效控制不同业务间混排效果,并最终呈现给用户,以达到更好的用户体验。


以一个精简版用户 query 为例,传递给 sp 的 query 如下(查询关键字为“Zimmermann 裙”,从三个引擎(0、1、2)中召回结果):


/bin/sp?outfmt=json2&src=idlefishwireless&app=spl_secondHand_new&q=Zimmermann%20%E8%A3%99&isForbiden=0&wlsort=35&s=0&n=10&enable_rank=true&enable_advsort=true&new_rs=true&ha3_version=v2&sellStatus=0&auctionType=a,b&se=tis2&bizType=0,1,2
复制代码


对应可视化调用拓扑如下:


multi_searcher 是多路召回入口,包括并发多引擎召回、多引擎结果解析、合并等逻辑。


红框中每一个 ha3_searcher 表示一条独立引擎召回链路,图中三条链路可得出当前召回是多路并发召回(56ms < 54ms + 10ms +20ms)。

效果

目前多引擎多路召回能力已经在闲鱼无忧购引擎、向量引擎场景下落地,整体取得了不错的效果: 

1、构建快 

主引擎中闲鱼无忧购商品引擎 dump 大全量单次构建平均时长约为 14h,迁移独立引擎后平均构建时长约为 5h 

2、接入快 

主搜场景接入向量召回能力,采用当前多路召回能力,召回链路打通原先 1 周缩短到 2 天

3、体验好 

主搜场景开启主搜引擎、闲鱼无忧购引擎、向量引擎三路并发召回能力,整体 RT 上涨约 15ms

4、资源省 

主引擎占用内存(21.6T),无忧购引擎(210M),向量引擎(20G) 

5、维护成本低 

无忧购引擎整体 12 个 schema 字段,向量引擎目前只需要 2 个字段

闲鱼无忧购迁移独立引擎,整体曝光 PV 提升近 50%,支付订单整体提升近 33%

闲鱼无忧购商品与主引擎 C2C 商品混排效果如下:

展望

前期主要集中于搜索架构升级快速落地,快速支撑业务发展诉求,当前实现上存在不优雅、用户使用门槛高等问题,后续我们将围绕多路召回能力及用户体验继续优化:


1、隔离 query 改写及多引擎合并能力,将职责从平台侧迁移到业务侧,提高主链路稳定性,以及业务迭代效率 

当前各个引擎 query 改写能力及合并能力,和主链路流程揉合在一起,未做到垂直业务间、垂直业务与主链路间隔离,对主链路的稳定性存在一定的影响 


2、精细化流控及干预能力,并提供可视化配置能力 

当前流控及干预手段,统一使用集团 diamond 配置,缺少一个便于操作使用的可视化能力


3、SP 功能核心代码逻辑从 lua 迁移 java,降低用户学习、开发成本 

新的一年,闲鱼侧将有更多的垂直业务接入搜索,多路召回能力将极大的帮助更多垂直业务快速接入,并提供更优雅、灵活、高效的业务混排能力,为各业务的持续发展提供强有力的支撑。


本文转载自:闲鱼技术(ID:XYtech_Alibaba)

原文链接:闲鱼如何高效打造一个多业务、低侵入的搜索链路

2021-03-27 07:003669

评论

发布
暂无评论
发现更多内容

第二周

等燕归

国内首个区块链村正式落地:数字经济的裂变之路

CECBC

区块链 数字经济

架构师训练营 1 期 - 第二周总结(vaik)

行之

架构师训练营—第二周学习总结

Geek_shu1988

第二周课后练习

薛凯

案例分析--反应式编程框架Flower的设计

张荣召

第二周总结

等燕归

架构师训练营 - 第 2 周课后作业(1 期)

Pudding

#第二周作业

vitaminc

MySQL是如何实现可重复读的?

超超不会飞

MySQL

第二周学习框架设计总结

三板斧

极客大学架构师训练营

OOA-OOD:面向对象分析/设计练习

张荣召

[架构师训练营第1期]第二周学习总结

猫切切切切切

极客大学架构师训练营

华为:与全球180万云与计算开发者共成长,共创行业新价值

华为云开发者联盟

从四个问题透析Linux下C++编译&链接

华为云开发者联盟

c++ Linux 编程

第 2 周 作业

Pyr0man1ac

架构师训练营第二周作业

文智

极客大学架构师训练营

面向对象设计原则----接口分离原则(ISP)

张荣召

为什么区块链巨头企业一定会诞生在中国的原因分析

CECBC

区块链 科技

架构师训练营第二周心得

CmHuang

金融科技推进数字金融“新基建”,着力建设三种类型数字金融基础设施

CECBC

金融 科技 科技革命

架构师训练营第二周学习总结

薛凯

架构师训练营 Week2 作业 1

lucian

极客大学架构师训练营

面向对象设计原则----单一职责原则(SRP)

张荣召

作业-2020-09-27

芝麻酱

Java中的遍历(遍历集合或数组的几种方式)

keaper

Java List 遍历

graylog日志分析系统上手教程

MySQL从删库到跑路

Apache Linux 运维 日志分析 实时 Web 日志分析器

架构训练营 - 第 2周课后作业 - 学习总结

Pudding

深入理解JVM垃圾回收算法 - 标记整理算法

Skye

标记整理 双指针算法 Lisp2 引线整理算法

架构师训练营 Week2 作业 2【学习总结】

lucian

极客大学架构师训练营

架构师训练营—第二周作业

Geek_shu1988

闲鱼如何高效打造一个多业务、低侵入的搜索链路_架构_闲鱼技术_InfoQ精选文章