“车货匹配”平台-搜索排序系统的工程设计与思考(1)

96
Geoffrey_fx
0.9 2019.02.03 11:00 字数 2951

前言

有句话说的好:“如果能把问题写清楚,那么问题就解决了一半”。这里先对车货匹配业务的系统工程与架构中的问题做一些小结,之后再写一些技术实现上的内容。

有关业务模式的‘小白型’介绍,在这两篇文章《车货匹配业务模式》里 :)

背景

工程架构的设计还是需要围绕业务需求及目标来建设,并随着业务发展不断演进。

产品角度,车货匹配从产品形态上大体上有三类(但本质上,最重要的那几步 - 查询分析、召回与排序):

     -  货源列表主动搜索 - 司机主动通过选择出发地、到达地、车长、车型、运输距离等条件进行匹配筛选、服务端返回货源列表。而列表也会根据搜索的条件类别与返回的货源结果集大小(比如,不满足一页大小时,补充其他类别的货源)合并其他列表,如’周边’货源(比如,出发地条件选择的如果是’区’,而’区’出发的货源列表大小不足一页时,则选择同城其他区的货源),’相似’货源等等

    - 司机被动触发系统搜索匹配推送 - 司机可以选择自己经常跑的几个路线作为’订阅路线’(一般是城市间)。添加’订阅’后,如果在订阅的路线上,有新发布的货源消息,系统匹配司机的订阅路线进行推送。除此之外,系统会根据司机的历史行为进行’偏好’计算作为匹配条件并推送货源

    - 详情推荐 - 司机点击某一个货源进入详情时,系统根据当前点击货源的出发地、到达地等条件进行与当前货源相似的货源进行匹配推荐

而从运营角度,对于车货匹配来说,更多的是需要提供搜索结果集上的运营配置能力。 比如,针对某类货主发布的货源(或特定类别货源),需要做特定司机流量的’倾斜’(在司机搜索和推送的结果上,进行优先展示或推送)。那么就需要针对这些情况,在车货匹配运营后台设计上,进行’接口’预留,即可以提供规则,让运营(或产品)人员对搜索排序结果集进行干预配置(当然也是在满足司机搜索条件的前提下);还有就是需要根据车货匹配运力供需系统实时计算出的各个地理区域内的货源与运力供需情况,基于预定义的规则或’供需调度’提权模型,来动态调整货源的召回权重,以达到供需调节的目的。

从用户角度,在App上一个司机进行货源列表搜索时,往往满足其搜索条件的货源会很多,一般几千条,甚至上万条。那么,就需要对这些货源进行排序,并将最有可能匹配司机真实需求(偏好)且最有可能成交的货源排在前面;而对货主而言,他期望的是在货源发布后,平台能够帮助在最短时间内帮助他调到最’靠谱’的司机来承运,那么司机那么多,该给谁派单与推送。 上述召回与排序的过程,都需要算法模型或者机器学习模型的能力(搜索排序中,更多是排序学习模型,基于多维特征来计算相关度),即在工程实现上,需要将算法模型嵌入进去,并提供灰度与实验分组能力,以实现不同模型算法的迭代与验证。同时,不同的搜索排序算法在线上运行需要有稳定性保障,即最终都要有降级兜底方案。

问题分析

架构决策 一、 需要对’问题域’(领域模型)进行统一,统一一套搜索与排序平台

存在不同的产品形态,其道理是显而易见的,即抓住’机会’使得未被成交/未被反馈的货源能够尽快且更多的匹配到没有找到货的司机。就如同上面背景中所述,货源的主动搜索,本身存在着多种不同的形态,也意味着搜索的逻辑有很多是不同的,条件模式不一、召回过滤条件不一、排序函数不一、结果集返回大小不一,还有推送与推荐等不同的形式。且功能的上线都不是一个节点,加上时间紧,那么结果往往就是不同的逻辑全部混在一起,没有区分没有隔离。即使有了隔离(也有可能是由不同的开发同学实现的功能,由于代码不熟或不影响已有服务逻辑及稳定性,索性新起一个服务来实现:)),如果不考虑整体框架的扩展以及可维护性,依然会使得逻辑越来越复杂,甚至功能复用困难。 因此,需要将搜索排序等逻辑进行领域划分,各个’算子’(一段功能内聚的,只对应一个输入数据结构和输出结构)的实现遵循统一接口,可以被框架在一个搜索、排序请求(抽象成一个’查询计划’)中分阶段进行统一调用。

架构决策 二、运营规则不断变化,那么需要的是一个统一的规范来描述和定义,并且可以’关联’到‘查询计划’以及包含的’算子’

运营规则通常影响着召回与排序。如召回(包含’粗排’的子过程)时,针对某类货源的强制曝光或者对货源(虚假、广告货源)的降权,规则可以直接发挥作用,即决定‘查询分析’过程的查询条件组成;如排序(包含’精排’的子过程)时,对’分组’了的货源(经过粗排后,货源被打上’标签’,分成不同的组),根据排序规则的优先级,进行重排序 (举例来说,对于列表中的第1 ~ 3位置,如果召回的’分组’中,有A类货源且货源列表Size大于等于3,则优先取A类货源其次取B类货源,如果取B类货源时,按50%概率来取),决定’列表的最终组成’;而一个规则的组成除了上述两个部分,还与应用场景、某类用户、某条路线相关,即不同的场景、用户、路线可以决定不同的规则配置。那么,这种情况下如何保障规则的可配置性、动态性(实时生效)这对于工程架构提出了比较大的挑战。因此,一个比较可行的办法,就是将’规则’模型与’查询计划’模型进行’关联’。

架构决策 三、搜索排序平台中的’算子’在功能实现时需要考虑通用性,同时平台框架也需要支持按具体场景进行特定化编程实现的能力 - 即可以写’Glue codes’将通用算子和特定算子连接起来

为了能’更好’的对结果集list 进行排序,引入’Learning To Rank’ 模型(即,对召回结果集Item list与Query Features进行相关度打分)并构建特征库(UserModel等)是一个比较重要的途径(这一般发生在当用户的Query 条件比较模糊 - 如到达地为一个’省’,或者’车长’、’车型’为不限的情况下,召回结果集结合更多的Feature来计算相关度得分。题外话:Feature的选择对于LTR是一个比较重要的步骤)。而这个模型需要在货源列表搜索和详情推荐等不同的场景下用到。不同的场景下,搜索策略却又不同、Query条件类别不同、运营规则也不同,因此平台需要提供框架将特定实现的’算子’和通用’算子’整合,即’查询计划’(SearchPlan)。’查询计划’的作用更像一个’算子’执行预编排好的’指令',不同的’查询计划’可以按用户、查询条件、场景来细分。

架构决策 四、搜索排序平台不能成为一个单体应用,不同场景下虽然需要遵循统一框架,但物理部署与逻辑上需要考虑熔断、降级兜底等稳定性保障相关策略

不同的产品形态决定了不同的流量特征与并发量。无论是’货源列表主动搜索’还是’司机被动触发系统搜索匹配推送’,本质上虽然都是这一个’查询分析->召回(含粗排)->打分精排 ->合并与重排序 ->截断 ->缓存’的过程,但执行的过程还是需要考虑稳定性影响。一个新的策略或算法上线,是一个不断调整参数与实验灰度的过程。因此,对平台的需求是可以将核心框架剥离并独立部署到其他服务,同时平台在上层接口调用处基于超时时间设置、错误率等做’查询计划’的降级、兜底(一般是替换另外一个’查询计划’,或者’查询计划’内部做容错处理)。再说说影响平台性能的几个重要因素(暂不说由于代码质量引起的问题 😞)。正如上面提到的搜索与排序过程,需要对大量Item 进行相关度计算,距离计算,偏好度计算。因此,这里有几个原则:

      - 尽量通过条件过滤,缩小待召回候选集(并利用到搜索引擎的缓存)

      - 将一部分相关度(基于少量关键特征)打分过程,尽量放在召回粗排阶段

      - 如有需要距离计算并过滤的场景,尽量先计算出地理索引值,然后写入搜索引擎。搜索时,可以根据地理索引值,快速计算出周边的索引,然后按Term过滤

后记

最近看的了另外一个文章,觉得不错也分享在这儿 - 教你用认知和人性来做最棒的程序员

日记本
Gupao