亚马逊AWS官方博客

在 AWS EC2 上快速部署 NebulaGraph:图数据分析实战

1. 引言

随着数据关联性的日益增强,传统关系型数据库在处理复杂关系和大规模连接查询时表现出瓶颈。图数据库以其强大的关系表达能力和高效的遍历性能,成为社交网络、推荐系统、知识图谱等领域的首选技术。NebulaGraph 作为一款开源分布式图数据库,具备高性能、可扩展的特点,适合处理海量图数据。

本文将带你一步步在 AWS EC2 上快速部署 NebulaGraph,并通过一个示例场景完成图数据的生成与分析,帮助你快速上手 NebulaGraph 图数据库的实战应用。

2. 准备工作

  1. 首先,确保你拥有一个有效的 AWS 账户,并且具备创建 EC2 实例、配置 VPC、EBS 等资源的权限。登录 AWS 管理控制台,检查你的资源配额,确保有足够的弹性 IP、EC2 实例配额等。
  2. 你可以选择在新的 VPC 中启动 EC2 部署 NebulaGraph,或使用已有的 VPC。部署前请确保在目标区域有可用的 EC2 密钥对,用于安全登录 EC2 实例。
  3. 本次测试环境使用的 EC2 机型是 c5a.2xlarge,可以提前创建一台。

3. 在 AWS EC2 上部署 NebulaGraph

NebulaGraph 采用计算与存储分离架构,部署包含 graphdstoragedmetad 节点。以下是在同一台 EC2 上搭建 NebulaGraph graphdstoragedmetad 3 个节点的过程。

登陆 EC2,下载启动 NebulaGraph:

sudo su ubuntu
cd ~
export NEBULA_VERSION=3.4.0
wget https://oss-cdn.nebula-graph.com.cn/package/${NEBULA_VERSION}/nebula-graph-${NEBULA_VERSION}.ubuntu2004.amd64.deb
sudo dpkg -i nebula-graph-${NEBULA_VERSION}.ubuntu2004.amd64.deb
sudo /usr/local/nebula/scripts/nebula.service start all
sudo /usr/local/nebula/scripts/nebula.service status all

下载 Nebula console ,以便通过命令行查询操作

wget https://github.com/vesoft-inc/nebula-console/releases/download/v${NEBULA_VERSION}/nebula-console-linux-amd64-v${NEBULA_VERSION}
chmod 111 ./nebula-console-linux-amd64-v${NEBULA_VERSION}

下载 Nebula importer,可以更方便、快速地导入数据

wget https://github.com/vesoft-inc/nebula-importer/releases/download/v${NEBULA_VERSION}/nebula-importer-linux-amd64-v${NEBULA_VERSION}
chmod 111 nebula-importer-linux-amd64-v${NEBULA_VERSION}

4. 图数据生成:创建图空间与导入数据

本次我们通过处理 MySQL 数据库中的几张表,来生成实体之间的依赖关系,这个实体对应图中的一个类型的点,如下图所示的 V1, 由于 V1 之间有依赖关系,如果从 MySQL 解析依赖关系需要多表关联且不确定依赖关系的层数,因此构建图来查找出最长的路径以及依赖和被依赖最多的点。我们将 V1 这个类型的点之间的边定义为 E1,由于相同的 V1 之间可能有不同周期的依赖关系,用 rank 来区分它们。

如上图所示,在点和边上,我们设置了 property,用来做更进一步的 filter,在边上,除了 property 之外还使用了 rank,用来区分两个点之间不同的边,关于 property 和 rank 的详细说明如下:

1. Rank 的作用

  • Rank 是用来区分同一对起点(src_vid)和终点(dst_vid)之间多条边的唯一标识。
  • 在 NebulaGraph 中,一条边由四元组唯一标识:(src_vid, edge_type, rank, dst_vid)
  • 如果两个边的起点、终点和边类型相同,但它们代表不同的关系或不同时刻的关系,就可以通过不同的 rank 值区分。
  • 默认情况下,rank 值为 0,如果需要插入多条相同起点和终点的边,必须指定不同的 rank。
  • 例如,A 和 B 之间有多次转账记录,每次转账可以用不同的时间戳作为 rank 区分。

2. Property(属性)的作用

  • 属性是边或点携带的具体数据字段,用来描述边或点的详细信息。
  • 属性是用户定义的键值对,比如时间、金额、状态等。
  • 属性是边或点的内容,描述边边或点特征和业务含义。
  • 例如,转账边或点属性可能包括转账金额、转账时间、备注等。

数据的生成使用 python 生成 csv 即可,一种点对应一个 csv 文件,一种边对应一个 csv 文件,点 csv 文件包括点的唯一标识 ID,后面的字段为 property 的具体值;边 csv 文件第一列是起点,第二列是终点的 ID,后面的字段为 property 以及 rank 的具体值。准备好 csv 文件后,可以按照格式准备以下 importer 的 yaml 配置文件,这里需要注意的是不同 NebulaGraph 的版本的 yaml 文件格式可能会有些许的差异,因此需要严格按照 Nebula Graph 的版本去官方文档获取 yaml 的格式。

config.yaml:

version: v2
description: data import
removeTempFiles: null
clientSettings:
  retry: 3
  concurrency: 10
  channelBufferSize: 128
  space: workflow_analysis
  connection:
    user: "root"
    password: "pwd"
    address: "127.0.0.1:9669"
  postStart: null
  preStop: null
logPath: import.log
workingDir: null
files:
- path: ./v1_vertex.csv
  failDataPath: v1_vertex_FAIL_DATA
  batchSize: 60
  limit: null
  inOrder: null
  type: csv
  csv:
    withHeader: false
    withLabel: false
    delimiter: null
  schema:
    type: vertex
    edge: null
    vertex:
      vid:
        index: 0
        function: null
        type: string
        prefix: null
      tags:
      - name: V1
        props:
        - name: name
          type: int
          index: 0
        - name: project
          type: int
          index: 1
- path: ./E1_edge.csv
  failDataPath: E1_FAIL_DATA
  batchSize: 60
  limit: null
  inOrder: null
  type: csv
  csv:
    withHeader: false
    withLabel: false
    delimiter: null
  schema:
    type: edge
    edge:
      name: E1
      withRanking: null
      props:
      - name: duration_time
        type: int
        index: 2
      srcVID:
        index: 0
        function: null
        type: string
        prefix: null
      dstVID:
        index: 1
        function: null
        type: string
        prefix: null
      rank:
        index: 3
    vertex: null

之后,通过 Nebula Console 来创建图的 schema:

./nebula-console-linux-amd64-v${NEBULA_VERSION} -addr localhost -port 9669 -u root -p pwd

CREATE SPACE `workflow_analysis` (partition_num = 1, replica_factor = 1, charset = utf8, collate = utf8_bin, vid_type = FIXED_STRING(128));
USE `workflow_analysis`;
CREATE TAG `V1` ( `name` int64 NOT NULL, `project` string NOT NULL) ttl_duration = 0, ttl_col = "";
CREATE EDGE `E1` ( `duration_time` int64 NOT NULL) ttl_duration = 0, ttl_col = "";
CREATE EDGE INDEX IF NOT EXISTS e1_duration_index ON E1(duration_time);
CREATE TAG INDEX IF NOT EXISTS v1_name_index ON V1(name);
:sleep 10
REBUILD TAG INDEX v1_name_index;
REBUILD EDGE INDEX e1_duration_index;

最后,在 EC2 上,通过 Nebula impoter 导入数据:

./nebula-importer-linux-amd64-v${NEBULA_VERSION} --config config.yaml

图数据分析实战

# 随机查询3个V1点
MATCH (v:V1) \
        RETURN v \
        LIMIT 3;

# 随机查询3条E1边
MATCH ()-[e:E1]->() \
        RETURN e \
        limit 3;  
          
# 查询name为12345的点
MATCH (v:V1{ name: '12345' }) RETURN v; 

# 查询从点12345开始、0~1 跳、所有 Edge type 的子图。
GET SUBGRAPH 1 STEPS FROM "12345" YIELD VERTICES AS nodes, EDGES AS relationships;

# 找出两个V1之间的最短路径
FIND SHORTEST PATH FROM "12345" TO "45678" OVER * YIELD path AS p;                  

# 某种依赖周期下被依赖最多的点,rank(e)==2代表其中一种依赖周期
MATCH (v:V1) \
WITH v LIMIT 1000000\
OPTIONAL MATCH (v2:V1)-[e:E1]->(v) \
WITH v, e \
WHERE rank(e)==2 \
WITH v, count(e) AS in_degree \
RETURN id(v) AS V1_id, in_degree \
ORDER BY in_degree DESC \
LIMIT 10;

# 查询最长的路径前10条,1-100跳
# 用path_length排序, 注意在有环的情况下这条语句不能执行,会打满内存
MATCH p = (v1:V1)-[e:E1*1..100]->(v2:V1) \
WITH p, \
     relationships(p) AS rs, \
     REDUCE(total = 0, e1 IN relationships(p) | total + e1.d_time) AS total_duration, \
     length(p) AS path_length \
WHERE ALL(e2 IN relationships(p) WHERE rank(e2)==1) AND length(p) >= 1 \
RETURN p, total_duration, path_length \
ORDER BY path_length DESC \
LIMIT 10;

# 查找所有42条到12345的点,该语句执行不受环的影响,因为它只返回点,并不涉及路径
GO 42 STEPS FROM "12345" \
OVER E1 REVERSELY \
YIELD DISTINCT $$.V1.code AS code ;

# 查询"12345" TO "45678"之间的5跳及以下所有没有环的路径并limit其中一条
FIND NOLOOP PATH FROM "12345" TO "45678" \
OVER E1 \
UPTO 5 STEPS \
YIELD path AS p \
| ORDER BY $-.p DESC \
| LIMIT 1;

# 查询点有没有环,如果是多个点,则需要使用nebula python client 一一执行下面的语句
FIND ALL PATH FROM "v1_id1" TO "v1_id1" OVER E1 UPTO 10 STEPS YIELD path AS p

以上语句均扩展自 NebulaGraph官方文档,需要结合数据的实际情况及想要查询的逻辑和结果选择如何去使用。

性能与扩展建议

  • 本次点的数量在 10w 以内,边的数量在 20w 以内,使用的机型可以支持高效的查询,如果您的数据量非常大,建议更换机型及使用集群模式来提高查询性能。
  • 生成图数据时,如果是读数据库,建议读非生产环境的数据库生成。
  • 在生产环境使用时,需要结合多副本 + 多可用区 + 负载均衡的设计来提高可用性。

总结与展望

  • 通过本次在 NebulaGraph 上的分析,将原来在 MySQL 场景下多跳依赖关系的查询时间优化到了秒级,并且成功的找出了关系链中存在的环路,对它们进行拆解后可以提高业务测的运行效率。
  • 随着图数据库与大数据、AI 的深度融合,图数据库 在智能分析、业务洞察、自动化决策等方面的应用潜力将持续释放。
  • Nebula Graph 参考文档

本篇作者

贾婷

亚马逊云科技快速原型解决方案架构师,致力于帮助客户设计和开发大数据和 AI 方向的快速原型方案,有游戏、汽车等行业的大数据开发经验。