图数据结构,能够很自然地表征现实世界。比如用户、门店、骑手这些实体可以用图中的点来表示,用户到门店的消费行为、骑手给用户的送餐行为可以用图中的边来表示。使用图的方式对场景建模,便于描述复杂关系。在美团,也有比较多的图数据存储及多跳查询需求,概括起来主要包括以下4个方面:
总体来说,美团需要一种组件来管理千亿级别的图数据,解决图数据存储以及多跳查询问题。海量图数据的高效存储和查询是图数据库研究的核心课题,如何在大规模分布式场景中进行工程落地是我们面临的痛点问题。传统的关系型数据库、NoSQL数据库可以用来存储图数据,但是不能很好处理图上多跳查询这一高频的操作。
Neo4j公司在社交场景(见图1)里做了传统关系型数据库MySQL跟图数据库Neo4j的查询性能对比[1],在一个包含100万人、每人约有50个朋友的社交网络里找最大深度为5的朋友的朋友,实验结果表明多跳查询中图数据库优势明显(见图2)。然而选取或者自主研发一款高吞吐、低查询延时、能存储海量数据且易用的图数据库非常困难。下面将介绍美团在图数据库选型及平台建设方面的一些工作。
在图数据库的选型上我们主要考虑了以下5点:(A)项目开源,暂不考虑需付费的图数据库;(B)分布式架构设计,具备良好的可扩展性;?毫秒级的多跳查询延迟;(D)支持千亿量级点边存储;(E)具备批量从数仓导入数据的能力。
分析DB-Engines[2]上排名前30的图数据库,剔除不开源的项目,我们将剩余的图数据库分为三类:
DGraph是由前Google员工ManishRaiJain离职创业后,在2016年推出的图数据库产品,底层数据模型是RDF[12],基于Go语言编写,存储引擎基于BadgerDB[13]改造,使用RAFT保证数据读写的强一致性。
NebulaGraph是由前Facebook员工叶小萌离职创业后,在2019年推出的图数据库产品,底层数据模型是属性图,基于C++语言编写,存储引擎基于RocksDB[14]改造,使用RAFT保证数据读写的强一致性。
一个完整的NebulaGraph集群包含三类服务,即QueryService、StorageService和MetaService。每类服务都有其各自的可执行二进制文件,既可以部署在同一节点上,也可以部署在不同的节点上。下面是NebulaGraph架构设计(见图3)的几个核心点[16][17]。
NebulaGraph基于C++实现,架构设计支持存储千亿顶点、万亿边,并提供毫秒级别的查询延时。我们在3台48U192G物理机搭建的集群上灌入10亿美食图谱数据对NebulaGraph的功能进行了验证。
为了统一管理图数据,减少工程同学在图数据库集群上的运维压力,我们基于开源分布式图数据库NebulaGraph,搭建了一套一站式图数据库自助管理平台(见图4),该平台包含以下4层:
与业界方案相比,团队主导设计的图数据库平台除了支持存储千亿顶点、万亿边,具备毫秒级别查询能力外,还提供了如下四项能力:应用可用性SLA达99.99%;支持每小时百亿量级数据导入;实时写入数据时保证多集群数据最终一致性;易用的图谱可视化能力。下面将介绍具体的设计思路。
另外,公司要求中间件要有跨区域容灾能力,即要具备在多个地域部署多集群的能力。我们分析了平台接入方的业务需求,大约80%的场景是T+1全量导入数据、线上只读。在这种场景下,对图数据的读写强一致性要求并不高,因此我们设计了单应用多集群这种部署方案。
AP方案部署方式可以参考图5,一个业务方在图数据库平台上创建了1个应用并部署了4个集群,其中北京2个、上海2个,平时这4个集群同时对外提供服务。假如现在北京集群1挂了,那么北京集群2可以提供服务。如果说真那么不巧,北京集群都挂了,或者北京侧对外的网络不可用,那么上海的集群也可以提供服务。在这种部署方式下,平台会尽可能地通过一些方式来保证整个应用的可用性。然后每个集群内部尽量部署同机房的机器,因为图数据库集群内部RPC非常多,如果有跨机房或者跨区域的频繁调用,整个集群对外的性能会比较低。
高可用模块主要包含下面4个部分,如上图6所示:
第一部分是右侧的图数据库Agent,它是部署在图数据库集群的一个进程,用来收集机器和图数据库三个核心模块的信息,并上报到图数据库平台。Agent能够接收图数据库平台的命令并对图数据库进行操作。
第二部分是图数据库平台,它主要是对集群进行管理,并同步图数据库集群的状态到配置中心。
第三部分是图数据库SDK,主要负责管理连接到图数据库集群的连接。如果业务方发送了某个查询请求,SDK会进行集群的路由和负载均衡,选择出一条高质量的连接来发送请求。此外,SDK还会处理图数据库集群中问题机器的自动降级以及恢复,并且支持平滑切换集群的数据版本。
第四部分是配置中心,类似ZooKeeper,存储集群的当前状态。
第二个模块是每小时百亿量级数据导入模块,平台在2019年底-2020年初全量导入数据的方式是调用NebulaGraph对外提供的批量数据导入接口,这种方式的数据写入速率大概是每小时10亿级别,导入百亿数据大概要耗费10个小时,耗时较长。此外,在以几十万每秒的速度导数据的过程中,会长期占用机器的CPU、IO资源,一方面会对机器造成损耗,另一方面数据导入过程中集群对外提供的读性能会变弱。
为了解决上面两个问题,平台进行了如下优化:在Spark集群中直接生成图数据库底层文件SSTFile,再借助RocksDB的Bulkload功能直接ingest文件到图数据库。
为兼顾各个业务方的不同需求,平台统一了应用导入、集群导入、离线导入、在线导入,以及全量导入、增量导入这些场景,然后细分成下面九个阶段,从流程上保证在导数据过程中应用整体的可用性:SSTFile生成、SSTFile下载、ingest、compact、数据校验、增量回溯、数据版本切换、集群重启、数据预热。
第三个模块是实时写入多集群数据同步模块,平台约有15%的需求场景是在实时读取数据时,还要把新产生的业务数据实时写入集群,并且对数据的读写强一致性要求不高。就是说,业务方写到图数据库里的数据,不需要立马能读到。针对上述场景,业务方在使用单应用多集群这种部署方案时,多集群里的数据需要保证最终一致性。针对这一需求,我们做了以下设计。
第一部分是引入Kafka组件,业务方在服务中通过SDK对图数据库进行写操作时,SDK并不直接写图数据库,而是把写操作写到Kafka队列里,之后由该应用下的多个集群异步消费这个Kafka队列。
第二部分是集群在应用级别可配置消费并发度,来控制数据写入集群的速度。具体流程如下:
第三部分是在实时写入数据过程中,平台可以同步生成一个全量数据版本,并做平滑切换(见图9),确保数据的不重、不漏、不延迟。
第四个模块是图可视化模块(见图10),主要是用于解决子图探索问题。当用户在图数据库平台通过可视化组件查看图数据时,能尽量通过恰当的交互设计来避免因为节点过多而引发爆屏。主要包括以下几个功能:
典型的应用场景包括商场找店,比如,某个用户想知道望京新荟城这个商场有没有海底捞,系统可以快速查出结果告诉用户;另一个场景是标签找店,用户想知道望京SOHO附近有没有适合情侣约会的餐厅,或者可以多加几个场景标签,系统都可以帮忙查找出来。
该项目数据来自用户的画像信息、商户的特征信息、用户半年内收藏/购买行为,数据量级是10亿级别,T+1全量更新。现在美团App和点评App上默认的商户推荐列表是由深度学习模型生成的,但模型并不会给出生成这个列表的理由,缺少可解释性。
而在图谱里用户跟商户之间天然存在多条连通路径,项目考虑选出一条合适路径来生成推荐理由,在App界面上展示给用户推荐某家店的原因。该项目基于用户的协同过滤算法来生成推荐理由,在家乡、消费水平、偏好类目、偏好菜系等多个组合维度中找出多条路径,然后给这些路径打分,选出一条分值较高的路径,之后按照特定Pattern产出推荐理由。通过上述方式,就可以获得“在北京喜欢北京菜的山东老乡都说这家店很赞”,或者“广州老乡都中意他家的正宗北京炸酱面”这类理由。
该项目把代码库中代码依赖关系写入到图数据库。代码库中存在很多服务代码,这些服务会包括对外提供的接口,这些接口的实现依赖于该服务中某些类的成员函数,这些类的成员函数又依赖了本类的成员变量、成员函数或者其它类的成员函数,那么它们之间的依赖关系就形成了一张图,可以把这个图写到图数据库里做代码依赖分析。
典型应用场景是精准测试:当开发同学完成需求并向公司的代码仓库提交了PR后,可以把更改实时地写到图数据库中。这样的话,开发同学就能查到他所写的代码影响了哪些外部接口,并且借助图可视化组件查看调用路径。如果开发同学本来是要改接口A的行为,改了很多代码,但是他可能并不知道他改的代码也会影响到对外接口B、C、D,这时候就可以用代码依赖分析来做个Check,增加测试的完备性。
目前,图数据库平台基本具备了对图数据的一站式自助管理功能。如果某个业务方要使用这种图数据库能力,那么业务方可以在平台上自助地创建图数据库集群、创建图的Schema、导入图数据、配置导入数据的执行计划、引入平台提供的SDK对数据进行操作等等。平台侧主要负责各业务方图数据库集群的稳定性。目前,美团有三四十个业务已经在平台上落地,基本满足了各个业务方的需求。
未来规划主要有两个方向,第一,根据业务场景优化图数据库内核,提升平台稳定性,开发的通用Feature持续反哺NebulaGraph社区。第二,挖掘更多的图数据价值。现在平台仅支持图数据存储及多跳查询这种基本能力,后续将基于NebulaGraph去探索图学习、图计算的能力,为平台用户提供更多挖掘图数据价值的功能。
登昌、梁帅、高辰、杨鑫、尊远、王超等,均为美团搜索与NLP部工程师。