kavin

Nebula Graph概念介绍

kavin 运维技术 2023-01-29 721浏览 0

Nebula Graph 是一款开源的、分布式的、易扩展的原生图数据库,能够承载数千亿个点和数万亿条边的超大规模数据集,并且提供毫秒级查询。

Nebula Graph概念介绍
Nebula Graph

1.1 Nebula 数据模型

  • 图空间(Space):图空间是 Nebula Graph 中彼此隔离的图数据集合,与 MySQL 中的 database 概念类似。
  • 点(Vertex):点用来保存实体对象,特点如下:
    • 点是用点标识符(VID
      或称为 Vertex ID
      )标识的。VID
      在同一图空间中唯一。VID 是一个 int64,或者 fixed_string(N)。
    • 点必须有至少一个 Tag,也可以有多个 Tag,但不能没有 Tag。
  • 边(Edge):边是用来连接点的,表示两个点之间的关系或行为,特点如下:
    • 两点之间可以有多条边。
    • 边是有方向的,不存在无向边。
    • 四元组 <起点VID、Edge type、边排序值(Rank)、终点VID>
      用于唯一标识一条边。边没有 EID。
    • 一条边有且仅有一个 Edge type。
    • 一条边有且仅有一个 rank。其为 int64,默认为 0。
  • 标签(Tag):点的类型,定义了一组描述点类型的属性。
  • 边类型(Edge type):边的类型,定义了一组描述边的类型的属性。Tag 和 Edge type 的作用,类似于关系型数据库中“点表”和“边表”的表结构。
  • 属性(Properties):属性是指以键值对(Key-value pair)形式存储的信息。

1.2 Nebula 架构总览

Nebula Graph 由三种服务构成:Graph 服务、Meta 服务和 Storage 服务,是一种存储与计算分离的架构。

  • Graph 服务主要负责处理查询请求,包括解析查询语句、校验语句、生成执行计划以及按照执行计划执行四个大步骤。
  • Meta 服务负责管理元数据信息,包括用户账号和权限信息、分片位置信息、图空间、Schema 信息、作业信息等等。
  • Storage 服务负责数据的存储,通过 Raft 协议保证数据多副本之间的一致性。
Nebula Graph概念介绍
Nebula 架构

1.3 Nebula 快速入门

本文将介绍在 Centos7 操作系统上通过 RPM 安装 Nebula。

1.3.1 安装 Nebula Graph

下载 RPM 安装包。

wget https://oss-cdn.nebula-graph.com.cn/package/2.6.1/nebula-graph-2.6.1.el7.x86_64.rpm

安装 RPM 包。

sudo rpm -ivh nebula-graph-2.6.1.el7.x86_64.rpm

1.3.2 启动 Nebula Graph 服务

Nebula Graph 使用脚本 nebula.service
管理服务,包括启动、停止、重启、中止和查看。

nebula.service
的默认路径是 /usr/local/nebula/scripts
,如果修改过安装路径,请使用实际路径。

nebula.service 脚本的语法如下。

sudo /usr/local/nebula/scripts/nebula.service 
[-v] [-c <config_file_path>]
<start|stop|restart|kill|status>
<metad|graphd|storaged|all>

参数说明如下。

Nebula Graph概念介绍
参数说明

我们使用以下命令启用 Nebula Graph 的所有服务,包括 Meta 服务、Graph 服务和 Storage 服务。

sudo /usr/local/nebula/scripts/nebula.service start all

查看所有服务的状态,可以看到此时 Nebula Graph 的服务都已经正常启动。

sudo /usr/local/nebula/scripts/nebula.service status all

# 返回结果
[WARN] The maximum files allowed to open might be too few: 1024
[INFO] nebula-metad(de03025): Running as 62568, Listening on 9559
[INFO] nebula-graphd(de03025): Running as 62658, Listening on 9669
[INFO] nebula-storaged(de03025): Running as 62673, Listening on 9779

1.3.3 连接 Nebula Graph

Nebula Graph 支持多种类型客户端,包括 CLI 客户端、GUI 客户端和流行编程语言开发的客户端,详情可以查看 [Nebula Graph 生态工具概览] (https://docs.nebula-graph.com.cn/2.6.1/20.appendix/6.eco-tool-version/)。接下来将介绍如何使用原生 CLI 客户端 Nebula Console 来连接 Nebula Graph 数据库。

首先在 Github 的 [Nebula Console 下载页面] (https://github.com/vesoft-inc/nebula-console/releases) 根据机器的系统和 CPU 架构选择对应的二进制文件。我使用的机器的 CPU 架构是 x86_64 的,因此这里选择下载 amd64 的二进制文件。

Nebula Graph概念介绍
Nebula Graph

为了方便使用,将文件重命名为 nebula-console。

wget https://github.com/vesoft-inc/nebula-console/releases/download/v2.6.0/nebula-console-linux-amd64-v2.6.0
mv nebula-console-linux-amd64-v2.6.0 nebula-console

为 nebula-console 二进制文件赋予可执行权限。

chmod +x nebula-console

nebula-console 的语法如下。

./nebula-console -addr <ip> -port <port> -u <username> -p <password> [-t 120] [-e "nGQL_statement" | -f filename.nGQL]

参数说明如下。

Nebula Graph概念介绍
参数说明

使用以下命令连接 Nebula Graph。

./nebula-console -addr 192.168.1.12 -port 9669 -u root -p nebula

看到以下输出说明连接成功。

Nebula Graph概念介绍
连接成功

1.3.4 使用常用命令

接下来将使用下图的数据集演示 Nebula Graph 基础的操作语法,包括用于 Schema 创建和常用增删改查操作的语句。nGQL(Nebula Graph Query Language)是 Nebula Graph 使用的的声明式图查询语言,支持灵活高效的图模式,而且 nGQL 是为开发和运维人员设计的类 SQL 查询语言,易于学习。

下表为 basketballplayer 数据集的结构示例,包括两种类型的点(playerteam)和两种类型的边(servefollow)。

Nebula Graph概念介绍
常用命令

本文将使用下图的数据集演示基础操作的语法。

Nebula Graph概念介绍
语法

1.3.4.1 创建和选择图空间

执行如下语句创建名为basketballplayer
的图空间。

(root@nebula) [(none)]> CREATE SPACE basketballplayer(partition_num=15, replica_factor=1, vid_type=fixed_string(30));

选择图空间basketballplayer

(root@nebula) [(none)]> USE basketballplayer;

查看创建的图空间。

(root@nebula) [basketballplayer]>  SHOW SPACES;
+--------------------+
| Name               |
+--------------------+
| "basketballplayer" |
+--------------------+

1.3.4.2 创建 Tag 和 Edge type

Tag 和 Edge type 的作用,类似于关系型数据库中“点表”和“边表”的表结构。创建 Tag: player
和 team
,以及 Edge type: follow
和 serve

CREATE TAG player(name string, age int); 
CREATE TAG team(name string); 
CREATE EDGE follow(degree int); 
CREATE EDGE serve(start_year int, end_year int);

1.3.4.3 插入点和边

可以使用 INSERT
语句,基于现有的 Tag 插入点,或者基于现有的 Edge type 插入边。

插入代表球员和球队的点。

INSERT VERTEX player(name, age) VALUES "player100":("Tim Duncan", 42);
INSERT VERTEX player(name, age) VALUES "player101":("Tony Parker", 36);
INSERT VERTEX player(name, age) VALUES "player102":("LaMarcus Aldridge", 33);
INSERT VERTEX team(name) VALUES "team203":("Trail Blazers"), "team204":("Spurs");

插入代表球员和球队之间关系的边。

INSERT EDGE follow(degree) VALUES "player101" -> "player100":(95);
INSERT EDGE follow(degree) VALUES "player101" -> "player102":(90);
INSERT EDGE follow(degree) VALUES "player102" -> "player100":(75);
INSERT EDGE serve(start_year, end_year) VALUES "player101" -> "team204":(1999, 2018),"player102" -> "team203":(2006, 2015);

1.3.4.4 创建索引

MATCH
和 LOOKUP
语句的执行都依赖索引,但是索引会导致写性能大幅降低(降低 90% 甚至更多)。请不要随意在生产环境中使用索引,除非很清楚使用索引对业务的影响。

必须为“已写入但未构建索引”的数据重建索引,否则无法在 MATCH
和 LOOKUP
语句中返回这些数据,参见 [重建索引] (https://docs.nebula-graph.com.cn/2.6.1/3.ngql-guide/14.native-index-statements/4.rebuild-native-index/)。

原生索引可以基于指定的属性查询数据,创建原生索引分为以下 3 种情况:

  • 创建 Tag/Edge type 索引。Tag 索引和 Edge type 索引应用于和Tag、Edge type 自身相关的查询,例如用 LOOKUP
    查找有 Tag player
    的所有点。
  • 创建单属性索引。“属性索引”应用于基于属性的查询,例如基于属性 age
    找到 age == 19
    的所有的点。
  • 创建复合属性索引(遵循”最左匹配原则”)。

关于创建索引的详细内容可以查看 [CREATE INDEX] (https://docs.nebula-graph.com.cn/2.6.1/3.ngql-guide/14.native-index-statements/1.create-native-index/#tagedge_type)

1.3.4.4.1 为 TAG 创建索引

为 TAG team 的创建索引,需要重建索引确保对已存在数据生效,注意在重建索引之前我们等待 20s,因为新创建的索引并不会立刻生效,因为创建索引是异步实现的,Nebula Graph 需要在下一个心跳周期才能完成索引的创建。

# 为 Tag team 创建索引 team_index_1。 
CREATE TAG INDEX team_index_1 ON team(); 
# 重建索引确保能对已存在数据生效。
:sleep 20
REBUILD TAG INDEX team_index_1;

为 TAG player 的 name 属性创建单属性索引,为 name 和 age 属性创建复合属性索引。

# 为 Tag player 的 name 属性创建单属性索引 player_index_1。
# 索引长度为10。即只使用属性 name 的前 10 个字符来创建索引。
CREATE TAG INDEX player_index_1 ON player(name(20)); 
# 重建索引确保能对已存在数据生效。 
REBUILD TAG INDEX player_index_1;

# 为 Tag player 的 name 和 age 属性创建复合属性索引 player_index_2。
CREATE TAG INDEX player_index_2 ON player(name,age); 
# 重建索引确保能对已存在数据生效。 
:sleep 20
REBUILD TAG INDEX player_index_2;

新创建的索引并不会立刻生效,创建新的索引并尝试立刻使用(例如 LOOKUP
或者 REBUILD INDEX
)通常会失败(报错 can’t find xxx in the space
)。因为创建步骤是异步实现的,Nebula Graph 要在下一个心跳周期才能完成索引的创建。可以使用如下方法之一:

  • 1.在 SHOW TAG/EDGE INDEXES
    语句的结果中查找到新的索引。
  • 2.等待两个心跳周期,例如 20 秒。如果需要修改心跳间隔,请为所有配置文件修改参数 heartbeat_interval_secs

1.3.4.4.2 为 EDGE type 创建索引

为 EDGE type 创建索引的方式和点相同,只是把关键字改成 EDGE 即可。

# 为 EDGE follow 的 degree 属性创建索引,并重建索引。
CREATE EDGE INDEX follow_index_1 on follow(degree);
:sleep 20
REBUILD EDGE INDEX follow_index_1;

# 为 EDGE serve 创建索引,并重建索引。
CREATE EDGE INDEX serve_index_1 on serve();
:sleep 20
REBUILD EDGE INDEX serve_index_1;


# 为 EDGE serve 创建复合属性索引,并重建索引。
CREATE EDGE INDEX serve_index_2 on serve(start_year,end_year);
:sleep 20
REBUILD EDGE INDEX serve_index_2;

1.3.4.5 查看索引

查看为 TAG player 和 team 创建的索引。

(root@nebula) [basketballplayer]> SHOW TAG INDEXES;
+------------------+----------+-----------------+
| Index Name       | By Tag   | Columns         |
+------------------+----------+-----------------+
| "player_index_1" | "player" | ["name"]        | # 单属性索引
| "player_index_2" | "player" | ["name", "age"] | # 复合属性索引
| "team_index_1"   | "team"   | []              | # TAG 索引
+------------------+----------+-----------------+

查看为 EDGE follow 和 serve 创建的索引。

(root@nebula) [basketballplayer]> SHOW EDGE INDEXES;
+------------------+----------+----------------------------+
| Index Name       | By Edge  | Columns                    |
+------------------+----------+----------------------------+
| "follow_index_1" | "follow" | ["degree"]                 | # 单属性索引
| "serve_index_1"  | "serve"  | []                         | # EDGE 索引
| "serve_index_2"  | "serve"  | ["start_year", "end_year"] | # 复合属性索引
+------------------+----------+----------------------------+

1.3.4.6 删除索引

删除 TAG player 的索引 player_index_2。

(root@nebula) [basketballplayer]> DROP TAG INDEX player_index_2;

删除 EDGE serve 的索引 serve_index_2。

(root@nebula) [basketballplayer]> DROP EDGE INDEX serve_index_2;

1.3.4.7 查询数据

查询数据主要有以下 4 种语句:

  • GO 语句可以根据指定的条件遍历数据库。GO
    语句从一个或多个点开始,沿着一条或多条边遍历,可以使用 YIELD
    子句中指定的返回的信息。
  • FETCH 语句可以获得点或边的属性。
  • LOOKUP 语句是基于索引的,和 WHERE
    子句一起使用,查找符合特定条件的数据。
  • MATCH 语句是查询图数据最常用的,与 GO
    或 LOOKUP
    等其他查询语句相比,MATCH
    的语法更灵活。MATCH 语句可以描述各种图模式,它依赖索引去匹配 Nebula Graph 中的数据模型。

1.3.4.7.1 GO 语句示例

从 TAG player 中 VID 为 player101
的球员开始,沿着边 follow
找到连接的球员。

(root@nebula) [basketballplayer]> GO FROM "player101" OVER follow;
+-------------+
| follow._dst |
+-------------+
| "player100" |
| "player102" |
+-------------+

1.3.4.7.2 FETCH 语句示例

查询 TAG player 中 VID 为 player100
的球员的属性值。

(root@nebula) [basketballplayer]> FETCH PROP ON player "player100";
+----------------------------------------------------+
| vertices_                                          |
+----------------------------------------------------+
| ("player100" :player{age: 42, name: "Tim Duncan"}) |
+----------------------------------------------------+

获取连接 player102 和 team203 的边 serve 的所有属性值。

(root@nebula) [basketballplayer]> FETCH PROP ON serve "player102" -> "team203";
+-----------------------------------------------------------------------+
| edges_                                                                |
+-----------------------------------------------------------------------+
| [:serve "player102"->"team203" @0 {end_year: 2015, start_year: 2006}] |
+-----------------------------------------------------------------------+

1.3.4.7.3 LOOKUP 语句示例

列出 TAG player 的所有 VID。

(root@nebula) [basketballplayer]> LOOKUP ON player;
+-------------+
| VertexID    |
+-------------+
| "player100" |
| "player102" |
| "player103" |
+-------------+

列出 EDGE serve 所有边的起始点、目的点和 rank。

(root@nebula) [basketballplayer]> LOOKUP ON serve;
+-------------+-----------+---------+
| SrcVID      | DstVID    | Ranking |
+-------------+-----------+---------+
| "player101" | "team204" | 0       |
| "player102" | "team203" | 0       |
+-------------+-----------+---------+

LOOKUP 也可以基于 where 条件进行过滤,例如在 EDGE serve 中查询 start_year == 2006 的属性值。

(root@nebula) [basketballplayer]> LOOKUP ON serve where serve.start_year == 2006;
+-------------+-----------+---------+
| SrcVID      | DstVID    | Ranking |
+-------------+-----------+---------+
| "player102" | "team203" | 0       |
+-------------+-----------+---------+

1.3.4.7.4 MATCH 语句示例

通过 MATCH 语句分别查询 TAG player 和 team 的属性值。

# 查询 Tag 为 player 的点的属性值
(root@nebula) [basketballplayer]> MATCH (x:player) return x;
+-----------------------------------------------------------+
| x                                                         |
+-----------------------------------------------------------+
| ("player100" :player{age: 42, name: "Tim Duncan"})        |
| ("player101" :player{age: 36, name: "Tony Parker"})       |
| ("player102" :player{age: 33, name: "LaMarcus Aldridge"}) |
+-----------------------------------------------------------+

# 查询 Tag 为 team 的点的属性值
(root@nebula) [basketballplayer]> MATCH (x:team) return x;
+------------------------------------------+
| x                                        |
+------------------------------------------+
| ("team203" :team{name: "Trail Blazers"}) |
| ("team204" :team{name: "Spurs"})         |
+------------------------------------------+

也可以根据索引所在的属性进行查询,例如我们查询 TAG player 的 name 字段名为 Tony parker 的属性值。

(root@nebula) [basketballplayer]> MATCH (v:player{name:"Tony Parker"}) RETURN v;
+-----------------------------------------------------+
| v                                                   |
+-----------------------------------------------------+
| ("player101" :player{age: 36, name: "Tony Parker"}) |
+-----------------------------------------------------+

由于 TAG team 上的 name 属性并没有建立索引,因此无法根据 name 属性进行查询。

(root@nebula) [basketballplayer]> MATCH (v:team{name:'Spurs'}) RETURN v;
[ERROR (-1005)]: IndexNotFound: No valid index found

使用 MATCH 查询 EDGE serve 的属性值。

(root@nebula) [basketballplayer]> MATCH ()-[e:serve]-()  RETURN e;
+-----------------------------------------------------------------------+
| e                                                                     |
+-----------------------------------------------------------------------+
| [:serve "player101"->"team204" @0 {end_year: 2018, start_year: 1999}] |
| [:serve "player102"->"team203" @0 {end_year: 2015, start_year: 2006}] |
+-----------------------------------------------------------------------+

1.3.4.7.5 实际的查询例子

使用以下语句查询和 Tony Parker 有关的球员和球队。

(root@nebula) [basketballplayer]> MATCH p=(v:player{name:"Tony Parker"})-->(v2)   RETURN p
+-------------------------------------------------------------------------------------------------------------------------------------------+
| p                                                                                                                                         |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| <("player101" :player{age: 36, name: "Tony Parker"})-[:serve@0 {end_year: 2018, start_year: 1999}]->("team204" :team{name: "Spurs"})>     |
| <("player101" :player{age: 36, name: "Tony Parker"})-[:follow@0 {degree: 95}]->("player100" :player{age: 42, name: "Tim Duncan"})>        |
| <("player101" :player{age: 36, name: "Tony Parker"})-[:follow@0 {degree: 90}]->("player102" :player{age: 33, name: "LaMarcus Aldridge"})> |
+-------------------------------------------------------------------------------------------------------------------------------------------+

和 Tony Parker 有关系的球员和球队在下图中用绿色方框标识。

Nebula Graph概念介绍

1.3.4.8 修改点和边

用户可以使用 UPDATE
语句或 UPSERT
语句修改现有数据。UPSERT
是 UPDATE
和 INSERT
的结合体。当使用 UPSERT
更新一个点或边,如果它不存在,数据库会自动插入一个新的点或边。

首先查询 TAG player 现在的属性值。

(root@nebula) [basketballplayer]> match (n:player) return n;
+-----------------------------------------------------------+
| n                                                         |
+-----------------------------------------------------------+
| ("player100" :player{age: 42, name: "Tim"})               |
| ("player101" :player{age: 36, name: "Tony Parker"})       |
| ("player102" :player{age: 33, name: "LaMarcus Aldridge"}) |
+-----------------------------------------------------------+

用 UPDATE
修改 VID 为 player100
的球员的 name
属性,然后用 FETCH
语句检查结果。

(root@nebula) [basketballplayer]> UPDATE VERTEX "player100" SET player.name = "Tim";

(root@nebula) [basketballplayer]> FETCH PROP ON player "player100";
+---------------------------------------------+
| vertices_                                   |
+---------------------------------------------+
| ("player100" :player{age: 42, name: "Tim"}) |
+---------------------------------------------+

执行 UPSERT
语句,分别对已存在的 player101 和未存在的 player103 进行操作,通过 MATCH 查询可以看到在 UPSERT
修改了原本 player101 的值,新插入的 player103。

(root@nebula) [basketballplayer]> UPSERT VERTEX "player101" SET player.name = "CRIS", player.age = 18;
(root@nebula) [basketballplayer]> UPSERT VERTEX "player103" SET player.name = "THOMAS", player.age = 20;


(root@nebula) [basketballplayer]> match (n:player) return n;
+-----------------------------------------------------------+
| n                                                         |
+-----------------------------------------------------------+
| ("player101" :player{age: 18, name: "CRIS"})              |
| ("player100" :player{age: 42, name: "Tim"})               |
| ("player102" :player{age: 33, name: "LaMarcus Aldridge"}) |
| ("player103" :player{age: 20, name: "THOMAS"})            |
+-----------------------------------------------------------+

1.3.4.9 删除点和边

删除点。

nebula> DELETE VERTEX "player101";

删除边。

nebula> DELETE EDGE follow "player101" -> "team204";

1.3.4.10 删除 TAG 和 EDGE

删除 TAG/EDGE 前要确保 TAG/EDGE 不包含任何索引,否则 DROP TAG
时会报冲突错误 [ERROR (-8)]: Conflict!

删除 TAG。

# 删除 TAG 的索引
DROP TAG INDEX player_index_1;
DROP TAG INDEX team_index_1;

# 删除 TAG 
DROP TAG player;
DROP TAG team;

删除 EDGE。

# 删除 EDGE 的索引
DROP EDGE INDEX follow_index_1
DROP EDGE INDEX serve_index_1

# 删除 EDGE 
DROP EDGE follow;
DROP EDGE serve;

继续浏览有关 数据库 的文章
发表评论