在使用过程中对Dgraph和NebulaGraph两款数据库的对比

​ 本文仅限当前生产环境下的两款数据库对比,对于数据库整体性能并没有达到最佳。两款数据库均基于SSD进行优化。目前生产环境存储均为HDD,因此数据库的写入、查询均存在一定影响。

​ 同时,两款数据库之间的性能深度对比。可基于以下链接查看,内容为美团对Dgraph与Nebula做的详细的性能测试对比。最终结论为Nebula的批量导入可用性、导入速度、实时数据写入性能、数据多跳查询性能均优于竞品(Dgraph、HugeGraph)。

https://discuss.nebula-graph.com.cn/t/topic/1377
  • 基础数据为网络的拓扑结构类似
    在使用过程中对Dgraph和NebulaGraph两款数据库的对比_第1张图片

安装

​ Dgraph(v20.03.1):下载源码后解压在生产环境下即可启动图数据库

​ Nebula(v2.0.1):下载deb包后安装,默认安装路径为 /user/local/nebula/。

​ 启动数据库服务为 /usr/local/nebula/scripts/nebula.service start all

​ 连接数据库需要使用nebula-console,进入后按照sql的基本逻辑使用

配置

Dgraph:

依次启动zero、alpha
--badger.vlog disk             //设置存储为磁盘存储
--abort_older_than 1m         
--pending_proposals 1000 
--lru_mb 3072                 //查询的内存限制目前设置为3G,但实际使用会超出
--query_edge_limit 500000000    //设置查询边的上限


set_schema结构
task_id: [string] @index(term) .
ip_str: string @index(term) .
scan_to: [uid] @reverse .        //reverse为可进行逆向查询
other_info: string @index(term,fulltext) @lang .
has_pid: string @index(term) .   //设置后用来判断是否存在父节点
virtual_node: [string] @index(term) . //存在中间为跳跃节点,利用virtual_node属性判断

Nebula

在默认安装路径  /usr/local/nebula/scripts内启动
/user/local/nebula/scripts start all 即可启动数据库、存储服务

设置数据库建联执行一系列的excute,完成数据库建立
1、CREATE SPACE my_space_2(partition_num=5); 设置库名、同时设置分片数量
# 此处对比DGraph有一点说明
Dgraph不支持多个数据库的建立,即所有的数据存储在同一表中,当同一项目中存在需要两个不同的数据结构时,DGraph无法满足该场景

2、CREATE TAG node(ip_str string, has_pid bool default true, device_type string);
    node为节点数据类名
3、CREATE EDGE next(virtual bool default false);
    next为边数据类名,类似DGraph中的scan_to

3、两个图数据库数据结构的不同对数据库设计的影响

​ 首先,两个图数据库的数据结构存在较大的区别。两者数据库数据结构对后续的CURD操作各有利弊。

DGraph的数据结构大致为(之前的生产环境中的数据结构)

{
    uid: "0x1231",  //数据库生成的唯一主键,不可自定义
     scan_to: [uid1, uid2, uid3], //边关系的存储字段,将uid之间建联,uid的list只增不减
    virtual_node: [uid1, uid2], //跳跃节点的存储字段,存储子节点uid,用来判断连接线是否为虚线
    "" : "" 其他节点属性参数
}

存储后的数据结构内容类似单个的json
{
    uid:"uid1",
    scan_to: [
        {
            uid: 'uid2',
            scan_to: [...],
            virtual_node: [...]
        },
        {
             uid: "uid3"
            scan_to: [...],
            virtual_node: [...]
        }
    ]
}

Nebula的数据结构大致为

节点类型:
[("100000" :node{'其他信息': "xxxxxx", has_pid: false, ip_str: "127.0.0.1", task_id: "1"})]
边类型:
[:next "100000"->"100001" @0 {virtual: true}]

其中 1000000、100001均为节点的vid数据,类似于DGraph中的uid,为当前节点的唯一主键。但是vid可以为自定义的string。node内的数据为当前的所有节点属性。
边类型中: vid1 -> vid2 为一条边的关系建联。 virtual为当前边的属性信息。
根据数据库存储的数据结构。两个数据库在存储数据时所遇到的问题

所有的插入都建立在已经完成了图数据库结构设计的前提下

即Dgraph完成了schema的创建。nebula完成了space、tag、edge的创建

Dgraph

​ 1、由于Dgraph不支持自定义的uid(官方文档中有提到xid的概念,但是在使用中没有深入去看),因此在存储时需要做到一下几点,才能将一条数据完整的存储在数据库中

1.检查即将入库的ip是否在图数据库中已存在(图数据库指挥根据uid判断是否存在相同数据,即使存储的内同完全一致)
2.对未入库的数据进行入库操作(同时增加IP、device_type等一系列不包含关系的数据)
3.对节点间的关系数据进行入库操作
3.1 查询start和end的ip在数据库中对应的uid信息
3.2 获取当前的scan_to字段,判断end_uid是否存在于start对应的scan_to字段内。不存在对当前的start的scan_to增加end_uid
3.3 判断start节点的 virtual_node 数据是否为0。不为0时则表示为跳跃节点,需要更改start节点信息的virtual_node字段。

2、在存储virtual_node数据时。存在节点连接状态一直变化的状态,在当前的virtual_node中就必须不断的进行增加、删除操作。但是uid的list无法进行删除操作。即当连接线为虚线后无法更改状态

3、DGraph使用事务进行图数据库的mutation,但是事务不支持锁。当不同的线程同时操作一个节点时就会造成事务冲突问题。具体可查看以下链接内容

https://discuss.dgraph.io/t/transaction-locks-and-conflicts/1982

4、DGraph存在较大概率出现OOM情况。

当开始写入数据时,内存信息最大会维持在8G(单进程写入),但当同时查询并写入数据时,整体的内存消耗会超过10G,在测试过程中存在内存占用范围为15~20G的情况。查询较多数据量的全部数据时大概率会产生OOM。目前推测的整体占用内存较大为入库内容过多的查询导致的。在每次的入库中基本过程为(查询->入库->查询->入库),因此需要大量内存。并且在DGraph存储数据时,需要先将数据存储在内存中

def insert_dgraph(client, datas):
    logger.info("insert dgraph data is %s" % datas)
    insert_list = []
    try:
        for i in datas:
            txn = client.txn()
            insert_list.append(i)
            if len(insert_list) == 500:
                txn.mutate(set_obj=insert_list, commit_now=True)
                insert_list = []
                time.sleep(0.01)
        client.txn().mutate(set_obj=insert_list, commit_now=True)
        time.sleep(0.01) 
    # 每次需要sleep来将内存内的数据写入到磁盘内
    except Exception as e:
        logger.error("insert dgraph error, which err is %s" % e)
        raise e
    logger.info("insert list is %s" % insert_list)
    time.sleep(0.2)  
Nebula-Graph

1、NebulaGraph的一整条插入为

1. 生成一条节点插入语句  INSERT VERTEX node(ip_str, has_pid) VALUES 
2. 生成一条边插入语句 INSERT EDGE next(virtual) VALUES 
3. 将将所有的插入语句一次性的插入到space内

// 由于可以自定义vid的内容,在插入边与插入节点时均不用提前查询唯一主键的值,后续的节点信息,在相同主键的前提下会做覆盖操作

2、有关virtual_node的添加问题

​ 在nebula数据库中由于不支持list的数据类型,仅支持基本的数据类型。即,使用相同的task_id存储方式无法满足当前的数据库。但是同时由于task_id类别多样化的问题,可以将任务信息存储为节点并对数据进行关联,即存储的结构为。

整个数据库存在三个数据集
边数据集合、节点数据集合、任务信息数据集合

任务信息数据集             边信息数据集        任务信息数据集
    ||                    ||                ||
节点信息数据集            ==========》        节点信息数据集

3、数据库的插入语法

​ 相比较DGraph的json格式数据插入。Nebula将数据写入到数据库语法构建过于复杂。Nebula数据库的数据插入必须依托excute()函数才能实现,即函数内为完整的NGsql语句 需要依托字符拼接完成。

数据库的查询问题

两款数据库对于查询的覆盖面基本相同,但是目前Nebula对于图数据库查询的算法覆盖不及DGraph,即便如此Nebula的查询场景基本可以涵盖项目需求。以后也可以对数据库版本进行更新。

​ Dgraph查询结果依托特有的数据结构,本身就为标准的JSON格式,对数据的解析友好度较高。

​ Nebula查询的结果为一个ResultSet类型数据

ResultSet(
    keys: ['_vertices', '_edges'],
    values: [
        [("100000": node {
            task_id: "12",
            ip_str: "127.0.0.1",
            has_pid: False
        })],
        [("100000") - [: next @0 { virtual: True}] - > ("100001")]
    ], [
        [("100001": node {
            task_id: "12",
            ip_str: "127.0.0.2",
            has_pid: False
        })],
        []
    ])

目前需要循环遍历解析为Node类型,并单独获取单个node内的数据信息,相比Dgraph解析复杂度较大。具体流程为

1、查询一条链路消息
resp = client.execute("""GET SUBGRAPH 5 STEPS FROM '100000'""")
2、分别获取边、节点信息
node = resp.column_values('_vertices')
edge = resp.column_values('_edges')
3、遍历节点,获取节点内的全部信息
for info in node:
    new_info = info.as_list()
    if new_info:
        new_data = new_info[0].as_node()
        node_obj = new_data.propertys('node')
        node_obj['vid'] = new_data.get_id().as_string()
        print(node_obj)
4、遍历边,获取边上的全部信息
for info in edge:
    new_info = info.as_list()
    if new_info:
        new_data = new_info[0].as_relationship()
        start_id = new_data.start_vertex_id()
        end_id = new_data.end_vertex_id()

        edge_obj = new_data.propertys()
        edge_obj['source'] = start_id
        edge_obj['target'] = end_id
"""
    打印信息为:{'virtual': True, 'source': "100000", 'target': "100001"}
"""

目前对两款数据库的使用都仅为入门级水平,如有不对还望指正

以上

你可能感兴趣的:(图数据库)