共计 6127 个字符,预计需要花费 16 分钟才能阅读完成。
本文首发于 Nebula Graph Community 公众号
本文整顿自智联招聘资深工程师李世明在「智联招聘举荐场景利用」的实际分享
搜寻举荐架构
在讲具体的利用场景之前,咱们先看下智联招聘搜寻和举荐页面的截图。
这是一个简略的智联搜寻页面,登录到智联招聘 App 的用户都能看到,然而这个页面背地波及到的举荐、召回逻辑以及排序概念,是本文的重点。
性能矩阵
从性能上来说,从矩阵图咱们能够理解到做搜寻和举荐时,零碎分为 Online 和 Offline 两个局部。
在 Online 局部,次要波及到实时操作,例如:搜寻某个关键词、实时展现集体举荐。而这些功能性操作须要其余性能反对,比方:热词联想,以及依据特定的输出进行实体辨认、用意了解,或是个人用户画像的绘制。再下一步操作便是召回,利用倒排索引,依据文本、类似度匹配,以及引入 Nebula Graph 实现图索引、向量索引,都是为了解决召回问题。最初,便是搜寻后果的展现——如何排序。这里会有个粗排,比方常见的排序模型 TF/IDF,BM25,向量的余弦类似 等召回引擎排序。粗排前面是精排,即:机器学习的排序,常见的有线性模型、树型模型、深度模型。
上述为在线 Online 流程,绝对应的还有一套 Offline,离线流程。离线局部次要是整个业务的数据加工解决工作,把用户的相干行为,例如:数据采集、数据加工,再把数据最终写到召回引擎,像是上文提及过的倒排索引的 Solr 和 ES、图索引的 Nebula Graph 以及向量索引的 Milvus,以提供线上的召回能力。
线上架构
当一个用户点击了智联招聘的搜寻按钮,会产生什么呢?如上图所示,通过一个 API 调用,再通过 Query DSL 的对立封装加工,再进入三路(之前提过的倒排索引、图索引和向量索引)召回,机器学习排序,最终将后果返回到前端进行展现。
离线架构
如同下面性能矩阵方面介绍的那般,离线局部次要是数据的加工解决,将诸如 HBase、关系型数据库 PostgreSQL、KV 数据库 TiDB 之类的数据平台通过数据链路进行加工,最终写入到数据存储层。
整体业务流程
将在线和离线架构进行整合,下图细化了 API 申请的解决、缓存、分页、A/B Test、用户画像、Query Understanding、多路召回等流程。
平台架构
介绍了线上和离线的性能架构,当初来讲下智联招聘是如何撑持整个性能矩阵的。
从底层来说,智联技术团队是通过构建了这三个平台来撑持整个性能矩阵的。
首先最上方就是咱们整个的搜寻举荐架构平台,分为数据处理、聚合层、机器学习三个模块。在数据处理模块,次要用来实现数据加工、数据同步、数据合并、格局转换等数据层事项;聚合层则解决用意辨认、AB 测试、在线召回、排序模型;而机器学习模块,次要用来做特色加工、特色抽取、模型更新之类的事件。
在搜寻举荐架构平台下方,便是搜寻召回引擎,由 Solr、Elasticsearch、Nebula Graph、Milvus 组成,别离负责倒排索引、图索引和向量索引。
最上层,是大数据平台,对接 Pulsar、Flink、HBase、HIVE、Redis、TiDB 等数据源。
Nebula Graph 在举荐场景下的利用
智联的数据规模
智联这边线上环境部署了 9 台高配物理机,机器配置的话 CPU 核数大略在 64~72,256G 左右的内存。每台机器部署 2 个 storaged 节点,一共有 18 个 storaged 节点,查问 graphd 和元数据 metad 节点别离部署了 3-5 个。线上环境目前有 2 个 namespace,一共 15 个分片,三正本模式。
而测试环境,采纳了 K8s 部署,后续线上的部署也会缓缓变成 K8s 形式。
说完部署状况,再来讲下智联招聘这块的应用状况,目前是千万级别的点和十亿级别的边。线上运行的话,最高 QPS 是 1,000 以上;耗时 P99 在 50 ms 以下。
下图为智联自研的监控零碎,用来看 Prometheus 的监控数据,查看节点状态、以后查问的 QPS 和耗时,还有更具体的 CPU 内存耗损等监控指标。
业务场景介绍
上面来简略介绍下业务场景
举荐场景下的协同过滤
举荐场景下有个比拟常见的业务是协同过滤,次要用来解决上图左下角的 4 个业务:
- U2U:user1 和 user2 为类似用户;
- I2I:itemA 和 itemB 为类似物品;
- U2I2I:基于物品的协同,举荐类似物品;
- U2U2I:基于用户的协同,举荐类似用户的偏好物品;
下面 U2U 是在创立 user to user 的某种关系,可能是矩阵(向量级别)类似,也可能是行为级别的类似。1. 和 2. 是根本的协同(相似性),把用户和用户、物品和物品建设好关系,基于这种根本协同再延长出更简单的关系,比方:通过物品的协同给用户举荐类似物品,或是依据用户的协同,举荐类似用户的偏好物品。简略来说,这个场景次要是实现用户通过某种关系,可失去相干物品的类似举荐或者是类似用户的关联物品举荐。
上面来剖析一波这个场景
协同过滤的需要剖析
具体来说,招聘畛域来说,CV(简历)和 JD(职位)之间存在关联关系,聚焦到上图的两头局部,CV 和 JD 之间存在用户行为和矩阵类似关系,像用户查看了某个职位、用户投递了某个职位,或者是企业端的 HR 浏览了某个简历这些用户行为,或者是基于某种算法,都会给 CV 和 JD 建设起某种关联。同时,还要创立 CV 和 CV 之间的分割,也就是上文说到的 U2U;JD 和 JD 之间的关系,就是下面的 I2I。关联创立之后,能够整点有意思的事件——通过用户 A 查看过 CV1(简历)举荐类似的 CV2(简历),用户 B 浏览过职位,也能够依据职位的相似性,给他举荐另外的 JD…这里再提下这个需要的“暗藏”重点,就是须要进行属性过滤。什么是属性过滤呢?零碎会依据 CV 的类似度来举荐 CV,这里就要做相干的属性匹配了:基于冀望城市、冀望薪资、冀望行业进行属性过滤。召回的实现肯定要思考上述因素,不能 CV1 的冀望城市是北京,你举荐的类似 CV 冀望城市却是厦门。
技术实现
原先的技术实现——Redis
智联这边最开始实现协同过滤的形式是用 Redis 将关系通过 kv 形式存储起来,不便进行查问。不言而喻的是,这样操作是能带来肯定的益处:
- 架构简略,能疾速上线;
- Redis 应用门槛低;
- 技术绝对成熟;
- Redis 因为是内存数据库的起因,很快,耗时低;
但,与此同时,也带来一些问题:
属性过滤实现不了,像下面说到的基于城市、薪资之类的属性过滤,应用 Redis 这套解决方案是实现不了的。举个例子,当初要给用户推 10 个相干职位,通过离线咱们失去了 10 个相干职位,而后咱们创立好了这个关联关系,但如果这时候用户批改了他的求职意向,或者是减少了更多的筛选条件,就须要在线来实时举荐,这种场景下是无奈满足的。更不必提下面说到过的简单的图关系,实际上这种查问用图来做的话,1 跳查问就能满足。
再尝试倒排索引实现——ES 和 Solr
因为智联在倒排索引这块有肯定的积攒,所以前面尝试了倒排索引的形式。基于 Lucene 角度,它有一个索引的概念。能够将关系保留为子索引 nested,而后过滤这块的话,子索引中存关系 ID,再通过 JOIN query 实现跨索引 JOIN,这样属性就能够通过 JOIN 形式进行过滤。这种模式相比拟 Redis 实现的话,关系也能存上了,属性过滤也能实现。但理论开发过程中咱们发现了一些问题:
- 不能反对大数据量存储,当关系很大时,绝对应的单个倒排会特地大。对于 Lucene 来说它是标记删除,先将标记的删除了再插入新的,每次子索引都要反复该操作。
- 关系较多时 JOIN 性能不好,尽管实现了跨索引 JOIN 查问,然而它的性能并不好。
- 关系只能全量更新,其实设计跨索引时,咱们设计的计划是单机跨索引 JOIN,都在一个分片里进行 JOIN 操作,但这种计划须要每个分片寄存全量的 JOIN 索引数据。
- 应用资源较多,如果跨索引波及到跨服务器的话,性能不会很好,想要调好性能就比拟耗资源。
上图右侧是一个具体的实现实录,数据格式那边是关系的存储形式,再通过 JOIN JD 的数据进行属性过滤,这个计划最终尽管实现了性能然而没在线上运行。
图索引——Nebula Graph
通过咱们调研,业界对 Nebula Graph 评估挺高,智联这边用了 Nebula Graph 来实现图索引。像刚在 U2U 和 U2I 的场景,通过图的形式把 CV 和 JD 存储成点,边则存储关系。至于属性过滤,如上图所示将 JD 诸如所在城市、学历要求、薪资要求、教训要求等属性存储为点的属性;而相关性的话,则在关系边上存了一个“分”,最终通过分进行相关性排序。
新技术计划惟一的毛病便是新畛域的学习老本,不过在相熟图数据库之后就不便很多了。
基于 Nebula Graph 的举荐
具体的 CV 推 CV、CV 推 JD、JD 推 JD、JD 推 CV 场景,都能满足,像上面这条语句:
match (cv:CV_TAG)-[p]-(jd:JD_TAG)
where id(cv)==1 AND p.SALARY>2000
return jd.ID, jd.TITLE, p.score
ORDER BY p.score DESC
SKIP 0 LIMIT 1000;
便是一个 CV 推 JD 的具体 nGQL 语句:通过简历(CV)开始进行查问,通过一些属性过滤条件,比方:薪资,依据边上的类似分进行 ORDER BY 排序,最终返回一个举荐 JD 信息。
整个业务这块,因为关系绝对简略,所以这里一共波及了 5 种 Tag 和 20+ 种边关系,以及创立 100 多种索引,整个数据量在千万级点和十亿级别的边。
Nebula Graph 应用过程中问题总结
数据写入
数据写入这里次要分为了 3 个方面:
首先 T+1 数据刷新。开展来说,因为数据是提前加工的,要给在线业务应用的话,波及了 T+1 数据刷新问题。刷数据的话,一开始可能是个冷数据,或者是没有数据,刷新的时候是间接写入关系数据,这个边数据可能连起始点都没有。整个边数据刷新之后,就须要将不存在的点插入。所以这里有个改良点,咱们先插入点数据之后再写入边数据,这样关系能更好地创立起来。数据刷新这块还有个问题,就是边数据是 T+1 跑进去的,所以前一天的数据曾经生效了,这里就须要把曾经存在的关系删掉,再将新的关系写入。
再来讲下数据格式转换,之前咱们应用了倒排索引或者是 KV 来存储关系,在数据结构这块,图构造同之前略有不同。像方才提到的关系,两个点之间须要创立什么关系边,边上存储何种数据,都是须要从新设定的。智联这边过后开发了个外部工具,用来自定义 Schema,能够不便地将数据存储为点,局部数据存储为边,能够灵便操作配置。即使有别的业务接入,有了这个小工具也无需通过 Coding 形式来解决 Schema 设定。
最初一个问题是数据继续减少带来的数据生效。像常见的累积线上沉闷用户,通过一段时间,像是三个月之前的沉闷用户当初可能是个寂静用户了,但依照累积机制的话,沉闷用户的数据是会始终减少的,这无疑会给服务器带来数据压力。因而,咱们给具备时效性的个性减少 TTL 属性,定期删除曾经生效的沉闷用户。
数据查问
数据查问这块次要也是有 3 个方面的问题:
- 属性多值问题
- Java 客户端 Session 问题
- 语法更新问题
具体来说,Nebula 自身不反对属性多值,咱们想到给点连贯多条边,然而这样操作的话,会带来额定的一跳查问的老本。但,咱们想了另外个易操作的办法来实现属性多值问题,举个例子,咱们当初要存储 3 个城市,其中城市 A 的 ID 是城市 B 的 ID 前缀,这里如果用简略的文本存储,会存在检索后果不精准问题。像下面查问 5 时便会把 530 这个城市也查问进去,于是咱们写入数据时,给数据前后退出了标识符,这样进行前缀匹配时不会误返回其余数据。
第二个是 Session 治理问题,智联这边在一个集群中创立了多个 Space,一般来说多 Space 的话是须要切换 Space 再进行查问的。然而这样会存在性能损耗,于是智联这边实现了 Session 共享性能。每个 Session 保护一个 Space 的连贯,雷同的 Session 池是不须要切 Space 的。
最初一个是语法更新问题,因为咱们是从 v2.0.1 开始应用的 Nebula Graph,起初降级到了 v2.6,经验了语法迭代——从最开始的 GO FROM 切换到了 MATCH。自身来说,写业务的同学并不关怀底层应用了何种查问语法。于是,这里智联实现了一个 DSL,在查询语言下层形象一层进行语法转换,将业务的语法转换成对应的 nGQL 查问语法。退出 DSL 的益处还在于场景的查问语句不再拘泥于繁多的语法,如果用 MATCH 实现成果好就用 MATCH,用 GO 实现好就采纳 GO。
对立 DSL 的实现
上图便是对立 DSL 的大略想法,首先从一个点(CV)登程(上图上方蓝色块),去 join 某条边(上图两头蓝色块),再落到某个点上(上图下方蓝色块),最终通过 select 来输入字段,以及 sort 来进行排序,以及 limit 分页。
实现来说,图索引这块次要用到 match、range 和 join 函数。match 用来进行相等匹配,range 是用来进行区间查问,比如说工夫区间或者是数值范畴。而 join 次要实现一个点如何关联另外一个点。除了这 3 个根本函数之外,还搭配了布尔运算。
通过下面这种形式,咱们对立了 DSL,无论是 Nebula 还是 Solr、还是 Milvus 都能够对立成一套用法,一个 DSL 便能调用不同的索引。
智联 Nebula Graph 的后续布局
更有意思更简单的场景
下面讲的业务实现是基于离线加工的数据,前面智联这边将解决在线实时关系。像上图所示,对于一个用户和一个职位而言,二者存在的关系能够很简单。比方它们都同某个公司有关系,或者是职位所属的公司是某个用户之前任职过的,用户更偏向于求职某一个畛域或者行业,职位要求用户熟练掌握某种技能等等,这些构建成一个简单的关系网络。
而智联的下一步尝试便是构建起这种简单的关系网络,再做些比拟有意思的事件。例如,某个用户在公司 A 就任过,于是通过这个关系查问出他的共事,再进行相关性举荐;或者是用户同校的上一届学生 / 下届学生偏向投递某个职位或者公司,这种都能够进行相干举荐;像用户投递的这个职位已经和谁聊过天这种行为数据,这个用户的求职意向要求:薪资程度、城市、畛域行业等信息,便能够通过在线的形式进行关联举荐。
更不便的数据展现
目前数据查问都是通过特定的查问语法,然而下一步操作便是让更多的人低门槛的查问数据。
更高的资源利用率
目前来说,咱们的机器部署资源利用率不高,当初是一个机器部署两个服务节点,每台物理机配置要求较高,这种状况下 CPU、内存使用率不会很高,如果咱们将它退出 K8s 中,能够将所有的服务节点打散,能够更不便地利用资源,一个物理节点挂了,能够借助 K8s 疾速拉起另外一个服务,这样容灾能力也会有所晋升。还有一点是当初咱们是多图空间,采纳 K8s 的话,能够将不同 Space 进行隔离,防止图空间之间的数据烦扰。
更好用的 Nebula Graph
最初一点是,进行 Nebula Graph 的版本升级。目前,智联用的是 v2.6 版本,其实社区公布的 v3.0 中提到了对 MATCH 进行了性能优化,这块咱们后续将会尝试进行版本升级。
交换图数据库技术?退出 Nebula 交换群请先填写下你的 Nebula 名片,Nebula 小助手会拉你进群~~