浅谈业内常见图数据库以及适用的业务场景

1,445 阅读11分钟

前言

目前市面上存在多款开源图数据库,以及各家厂商也进行了自研图数据库(大多闭源),以符合自己的定制化要求,如何选择呢? hugegraph,janusgraph,nebula,bytegraph,easygraph进行对比

一、 看落地应用场景

以下场景均为从各次大会分享/ppt中以及本人对接的业务中总结

  1. 反欺诈
    1. 诈骗电话的特征提取,比如不在三步社交邻居圈内,被大量拒接等特征。使用图数据库筛选出此类特征可迅速识别。
    2. 再比如银行卡/支付宝转账,目前据个人体验,支付宝在我们转账陌生人支付宝时,回有提示风险,比如我们不在一个k步圈子中,会及时拦截转账。
    3. 羊毛党的识别,某app放出很多优惠,但是总有羊毛党组织薅羊毛,利用图数据库就可以将个人账号和手机ip关系筛选出,如果一台设备有多个账号登陆,就会将其判断为羊毛党,及时减少企业损失。 image.png
  2. 表示公司与法人/股东关系图

一个人可以有很多公司,一个公司可以被很多人持股,以及某几个人共同持股等关系,当我们要查看某个人和他的相关公司情况,风险,持股情况,即可基于图进行查询,国内的天眼查就是基于此应用场景。

目前银行对于影子集团、集团客户多层交叉持股、股权层层嵌套复杂关系的识别手段相对较为落后。随着企业集团化、家族化、多元化发展,单一企业通过资本运作组建成商业帝国;各类资本系内部股权不透明,隐形股东和股权代持现象频繁发生,主要股东、控股股东、实际控制人、一致行动人、最终受益人不明晰;企业与股东个人、企业与企业之间的关系与交互影响愈加复杂,单个客户信用风险的爆发有可能引发整个关联客群的风险。

企业、高管及关联公司构成一个复杂的关系网络,利用图计算引擎搜索国家企业信用信息公示系统,遍历集团成员及关联企业之间的股东及股权结构情况,判断是否存在交叉持股、受同一股东控制和高管任职关系,识别出隐蔽关联关系,有助于发现关联交易非关联化、关联交易利益输送等违法、违规线索。

image.png

image.png 3. 表示汽车供应链关系

背景:这是tigergraph的一场分享中披露的应用案例,汽车在生产中会使用成千上万的零件,且不同型号的汽车有使用的零件有的相同,有的不同,且有些零件供应商只有一家,且不同零件库存不同

那么问题来了,如果这家汽车生产商接到一个订单,所需要的型号的汽车使用的上千个零件是否全部能够满足订单呢?即使满足了此订单,其他型号汽车使用此零件的生产需求是否仍然满足?如何找到被掐脖子的零件供应(没有此零件整个车间无法运作)?有的零件供应商是否存在风险,是否需要增加储备供应商?

这是从原材料角度出发的图应用场景,我可以将汽车和零件当作一个实体,所需要的零件都是实体之间的关系,汽车-->需要-->零件,建立这样的一条边来表示汽车和零件的关系,汽车有型号等等属性,零件有存量,等属性那么我们可以直观的表示某个零件被多少型号的汽车所需要,需求量多少,如果在传统的数据库中,我们需要连接大量的零件表和车型表来统计这些数据。

除此之外,还有价格计算需求,比如某些大宗产品价格上涨,导致某些零件价格上涨,汽车厂商也需要相应的计算利润率,得出最终的一个价格,那么有多少汽车受到此批零件价格波动的影响呢?在传统的统计方式中,我们需要挨个计算每个汽车型号的依赖和零件价格,需要大量的人力计算,在图模型中,我们只需要输入零件价格,就可以计算出其依赖的汽车最终符合利润率的价格来。大大提高了效率

image.png

  1. 替代大量表连接/转换 在数据仓库开发过程中, 会因为数据跨表关联产生大量的中间无用表,使用图数据库则避免了大量无用表,直接根据关系模型表示出目标数据 image.png

  2. 知识图谱,构建行业知识图谱

比如查询某个演员和某个导演合作过的影视。作品信息,类似的有关两个实体之间关系的查询

image.png

  1. 贷款流向监测/贷后实时监控预警

信贷资金流向始终是监管关注重点,其中,信贷资金违规进入股市、楼市等领域成为监管严查领域,监管部门要求银行监控信贷资金的真实流向。建立起银行账号、银行转账金额、企业名称等数据的实体关系,就是 “资金流向知识图谱”。

如图是以任意放款账号获得银行贷款为起点,图计算贷款资金流向的最终账号为终点。贷款资金经过43层账号转账

image.png

  1. 房源推荐、客源维护、影响力分级

节点: 经纪人、房源、客户

关系: 浏览、关注、带看等

例: 当某个用户经常浏览关注或者咨 询某个房源时,该房源的维护人A1会 邀请该用户的维护人A2带客户来看房,目前贝壳找房即应用图数据库来处理此场景需求,此外还可据此识别虚假房源、虚假客源、虚假带看、私单飞单......

8.商城商品品类细分

image.png

9.精准营销

对客户分群,进行客户标签记录,处于同一标签下的客户具有多种共同特征,(家人朋友,共用wifi)

如来一条客户数据我会将其进行打标签分类,同类客户会在一个群体内。

10. 安全

ip关系等黑客攻击场景。

11.游戏皮肤推荐

游戏搭配推荐图关系,详情见网易游戏应用www.bilibili.com/video/BV1bP…

12.数据血缘 表示 表 字段 任务之间的关系

二、 图存储结构

总的来说,市面上的图数据库存储基本分为这三种情况:

  1. 一个KV对一条边hugegraph/nebula
    • 实现简单
    • 写入放大较小,适合重写入场景
    • 使用KV Scan 实现一度邻居查询,某些场景下性能退化,比如全量读边
  2. 一个KV对保存一个起点所有边janusgraph/titan/easygraph
    • 实现较为简单
    • 写入放大情况严重,读取一次寻址,需要将所有顶点全部读取一次,适合重读(全量边读),其他读场景等于是读放大。写入/更新放也更明显, 因为新增/更新一条边, 也需要重新写一整行
  3. 多个KV对组成B树等结构保存起点的所有边bytegraph
    • 实现较为复杂
    • 可根据配置灵活平衡读写放大问题
    • 可以解决超级顶点问题

1.1 easygraph

  • 点边数据可单独存放
  • 边列表可快速查找邻居点和边信息
  • 索引对点/边按字段值快速查找
  • 支持snapshot isolation,保证实时写入数据一致性 我们据图来看看它的存储结构:
  1. 点数据 key 分为三部分,graphid+labelid+vertexid,这里多个图使用同一份存储,并用graphid进行区分多个图,推测这里多个图没有进行物理隔离,可能会相互影响。然后是点类型,最后是点id。
  2. 边数据 分为 graphid+labelid+direction+srcid,边也是用graphid作为前缀区分不同图数据,labelid区分不同种类的边,然后是边的方向in/out,最后是边的起始点id,value则是这以这个起始点id为起点或者终点的边集合。这里符合如上所述的情况2:一个KV对保存一个起点所有边。当然也就具有读/写边放大的性能问题。且对于超级顶点,这个edgelist又会非常大。不方便像其他图数据库一样遇到超级点将查询截断。(比如单独kv存储一条边,遇到超级点时查询到1000就返回,不再继续查询) image.png

1.2 janusgraph

janus也属于一个KV对保存一个起点所有边的存储结构。(如上情况2)(边切图)

1.2.1 点边存储

janus的点存储是按着邻接表的形式进行存储的,每个点将其property存储在前,其邻居边在后,按sortkey排序。但是 顶点的 [属性]列表 和 [邻接边]列表 两种数据存在一起,顶点和边读写性能相互影响严重。 property和edge各自占据列: image.png 详情看解析 边的存储这里:边起点id作为rowkey ,labelid+ rank key+终点id+edgeid在column,属性在value。 存疑:边的column为何需要edgeid,讲道理,labelid+sortkey+终点id足够将其唯一标识边了?这里还需要结合代码看一下。

1.3 hugegraph

见之前的文章

1.4 bytegraph

  • 字节图的存储结构类似于 janus,但是使用了邻接表/b树的存储方式,当节点是普通小点时,使用临街表的方式存储,此时读写放大不明显。当此点为超级顶点的时候, 这就比较特殊了, 有两类page , 一类存储元信息, 一类存储边数据, 组合起来是一颗BTree.

    • 超级顶点的所有边会被按大小均分, 切成多个Edge Page, 按part排序(看起来类似innodb的page的概念, 比如每个4~16K, 包含多个行?)
    • MetaPage中, key是vid + edgeLabel, value是边切分后的partId. (e.g vid1 + likes --> part1,)
    • EdgePage类似普通点的存储方式, 只不过这里key是partId, value应该是按字典序排序划分? 相当于拆了一层. 多一级映射. (所以可以动态调整/分裂)
  • B树结构细节

    • 一个起点的同一类型的所有边是一个存储单元(KV)
    • 一级存储(点的出度少于阈值):
      • 起点id+点类型+边类型作为key
      • 同一起点类型相同所有边聚合作为一个value存储
    • 二级存储(点出度超过阈值):
      • 所有边均匀分成多个Edge page,并分配对应的PartKey,所有PartKey组成Meta数据
      • Meta page整体作为value存储,(点,边类型)->(PartKey1,PartKey2,...)
      • Edge page的存储格式和一级存储类似,PartKey->(edge1,edge2...)
      • Meta pageEdge page整体是一颗B树,通过维护page版本号,实现无锁并发增删改查&子树分裂合并
    • 两级存储之间可动态转化
  • 分布式集群存储

    • 通过多种可选的图划分算法,将全局数据划分到多个shard中
      image.png

1.5 nebula graph

nebulahugegraph都为如上情况一:一个KV对一条边,边rowkey具有完整的起点终点信息。(点切图)

  • 点格式 image.png

  • 边格式 image.png

  • property 格式 image.png

Type : 1 个字节,用来表示 key 类型,当前的类型有 data, index, system 等

Part ID : 3 个字节,用来表示数据分片 Partition,此字段主要用于 Partition 重新分布(balance) 时方便根据前缀扫描整个 Partition 数据

Vertex ID : n 个字节, 用来表示点的 ID

Tag ID : 4 个字节, 用来表示关联的某个 tag

Edge Type : 4 个字节, 用来表示这条边的类型,如果大于 0 表示出边,小于 0 表示入边。

Rank : 8 个字节,用来处理同一种类型的边存在多条的情况。用户可以根据自己的需求进行设置,这个字段可_存放交易时间_、交易流水号、或_某个排序权重_

PlaceHolder : 1个字节占位符,要是留给 TOSS(transaction on storage side)使用。主要用于标识一条边的出边和入边是否完整插入了

(在物理空间点边type不同,不在像1.0版本一样点边放在一起存储,而 Type 分离之后,按 VertexType + VertexID 前缀扫描,可以快速获取所有 tag)