前言:因为是纯小白入门,我暂时不可能做分布式部署,没那么优秀性能的机器,身为学生买服务器也只能赶在双11,目前就只有手上一台服务器。所以所有操作都在这小小一个上面完成的
学习目标
- 单机配置启动一个nebula图数据库
- 安装并配置好nebula-importer,nebula-console,nebula-graph-studio,nebula-spark-connector
- 处理生成一个适于导入的数据集,并导入图空间
- 使用nebula-spark-connector连接到nebula数据库并读取出导入的节点数据
项目背景
1.已经在本地配置好了java、spark、scala、maven,以及docker(本次全部采用docker部署)
2.准备好了一个图数据集:cora数据集
cora数据集(以及一些其他的数据集)下载链接可以在这里找到:
安装Nebula Graph
接下来我会介绍一下nebula官方文档里写的整个安装流程及需要注意的点(坑)
准备资源 - Nebula Graph Database 手册 (nebula-graph.com.cn)
安装部署-准备资源
基础硬件设施
由于只是学习,不是生产环境,所以我需要满足nebula graph的编译级别配置:
软件环境要求
不建议centOS 8傻傻去执行yum update,会把原来没毛病的镜像源覆盖成国内用不了的源。。。如果手贱点了,建议参考一下阿里的这个解决方案,用他们的yum源,就可以重新用yum了 centos镜像-centos下载地址-centos安装教程-阿里巴巴开源镜像站 (aliyun.com)
Docker Compose部署Nebula Graph
使用 Docker Compose 部署 - Nebula Graph Database 手册 (nebula-graph.com.cn)
在这里,前三步按官方顺序执行即可,启动后执行docker ps -a
也可以看到分别有三个graphd,storaged,metad服务已经启动了
需要提及的是第4点:连接Nebula Graph这里。
如果不知道这个nebula console是什么,去Releases · vesoft-inc/nebula-console (github.com)下载个。在先前的文章中,因为我的目标就是3.0.0版本的nebula-algorithm,所以这里也是下载3.0.0版本的nebula console。下载完了执行:
rename nebula-console-linux-amd64-v3.0.0 nebula-console nebula-console-linux-amd64-v3.0.0
chmod 111 nebula-console
# 根据你的服务器ip和端口来定,默认端口9669,用户名root,密码nebula
./nebula-console -addr <ip> -port <port> -u <username> -p <password>
这个Nebula Console相当于命令行版控制台,所有原生NGQL语句在这里就可以直接提交给图数据库执行。
Docker部署Nebula Graph Studio (可选)
这里官方提了一嘴该镜像仓库。因为docker默认都是从Docker Hub那拿的,有的镜像文件可能拉取的巨慢。
改镜像仓库的方法就是:
cd /etc/docker
vi daemon.json
##### 以下均为输入 #####
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
]
}
启动后进在浏览器访问你的ip:7001
,主机号是ip:9669,用户名root,密码nebula,就可以进nebula自己的前端了
本地部署Nebula importer并导入数据集
Cora数据集介绍及预处理
- Cora数据集共2708个样本点,每个样本点都是一篇科学论文,所有样本点被分为8个类别,类别分别是
- 基于案例
- 遗传算法
- 神经网络
- 概率方法
- 强化学习
- 规则学习
- 理论
- 每篇论文都由一个1433维的词向量表示,即每个样本点具有1433个特征。词向量的每个元素都对应一个词,且该元素只有0或1两个取值。取0表示该元素对应的词不在论文中,取1表示在论文中。所有的词来源于一个具有1433个词的字典。
- 每篇论文都至少引用了一篇其他论文,或者被其他论文引用,也就是样本点之间存在联系,没有任何一个样本点与其他样本点完全没联系。如果将样本点看做图中的点,则这是一个连通的图,不存在孤立点。
- Cora数据集有两个文件:
cora.cites
,cora.content
,都是文本文件,其中,cites中包含了各论文的引用关系,content中包含了各论文的编号id以及其词向量特征
处理过程其实就是把cites直接以文本形式转csv,把content中间的词向量全去掉只保留id和类型,最终两个csv大概长这样:
nebula importer YAML配置编写
下载方面的话,我是通过直接把git项目扒拉下来然后本地编译出二进制可执行文件nebula-importer
的
创建一个yaml文件,编写如下代码
# 3.0版本的
version: v3.0
removeTempFiles: false
clientSettings:
retry: 3
concurrency: 10
channelBufferSize: 128
# 使用的图空间:cora
space: cora
connection:
user: root
password: nebula
address: 你的ip:9669
logPath: ./err/1.log
files:
- path: ./contents.csv
failDataPath: ./err/contents.csv
batchSize: 128
inOrder: false
type: csv
csv:
# 无表头csv:上文中的contents
withHeader: false
withLabel: false
delimiter: ","
schema:
# vid为int类型的节点article
type: vertex
vertex:
vid:
index: 0
tags:
- name: article
props:
- name: type
type: string
index: 1
- path: ./cites.csv
failDataPath: ./err/cites.csv
batchSize: 128
inOrder: false
type: csv
csv:
# 无表头csv:上文中的cites
withHeader: false
withLabel: false
delimiter: ","
schema:
# 边名为cite
type: edge
edge:
name: cite
srcVID:
type: int
index: 0
dstVID:
type: int
index: 1
这个yaml是以默认存在一个cora图空间为基础的。我最初是直接在nebula graph studio前端操作了。
如果不方便手动在nebula graph studio创建的话,就需要在yaml文件中添加NGQL语句。添加位置在上面的clientSettings中,如果还弄不清可以看官方文档那个示例确认代码位置
postStart: # 配置连接 Nebula Graph 服务器之后,在插入数据之前执行的一些操作。
commands: |
DROP SPACE IF EXISTS cora;
CREATE SPACE IF NOT EXISTS cora(partition_num=5, replica_factor=1, vid_type=INT64);
USE cora;
CREATE TAG article(type string);
CREATE EDGE follow();
# 执行上述命令后到执行插入数据命令之间的间隔。
afterPeriod: 15s
nebula importer文件导入
现在我们把上面的yaml,csv文件放到一个文件夹里。目前我的想法是,为了更加规范的调用,我直接在nebula-importer这个文件夹下建立了一个nebula-csvs文件夹,内部按不同的图空间进行编号并创建文件夹,随后放入yaml和csv文件。
因此,我执行如下代码:
./nebula-importer --config <yaml_config_file_path>
执行完成后,进入nebula graph studio看看效果。随便进行几次查询,芜湖!
安装nebula-spark-connector
仍然是先按官方的来,从git拉取,并使用mvn编译生成jar包
官方之后的介绍似乎就开始各种模棱两可了,这也导致接下来我弄出来了一堆错误,花了整整一天才想明白咋回事。。。
使用方法???在哪使用啊
因为在之前的学习过程中我是在Windows操作系统上搞IDEA开发然后打包再去给服务器执行的。所以呗,我应该还是先在本地IDEA配个能编码spark-connector的环境才对啊!
官方只放了这么一句话。。。
那好吧,我本地确实是Maven项目。所以我先改pom呗
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>spark-connector</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.vesoft</groupId>
<artifactId>nebula-spark-connector</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.4.4</version>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
</project>
阿哲,可是我确实按照官方给的dependency写的啊
好吧,先把导包问题解决掉。右键项目,选择Maven,create/open settings.xml,进行如下修改,改一下Maven下载的镜像仓库。随后reload Maven即可
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>nexus</id>
<name>internal nexus repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
</settings>
这里这个spark并不是Maven本来有的那个org.apache.spark
,而应当是构建一个sparkSession。实际上还是应该去看官方在github上的示例才对。。。
nebula-spark-connector (github.com)
复制并修改了一下的代码
import com.facebook.thrift.protocol.TCompactProtocol
import com.vesoft.nebula.connector.connector.NebulaDataFrameReader
import com.vesoft.nebula.connector.{NebulaConnectionConfig, ReadNebulaConfig}
import org.apache.spark.SparkConf
import org.apache.spark.sql.SparkSession
object test {
def main(args: Array[String]): Unit = {
// 开启一个新的spark会话进程
val sparkConf = new SparkConf
sparkConf
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
.registerKryoClasses(Array[Class[_]](classOf[TCompactProtocol]))
// 指定本次spark submit任务的master等配置信息
val spark = SparkSession
.builder()
.master("local")
.config(sparkConf)
.getOrCreate()
println("read vertex")
readVertex(spark)
spark.close()
sys.exit()
}
def readVertex(spark: SparkSession): Unit = {
// 配置nebula连接。由于spark-connector是要从metad里面去获取storaged的地址之类的信息的,另外,根据官方文档,读取数据不需要配置graphd地址
val config =
NebulaConnectionConfig
.builder()
.withMetaAddress("127.0.0.1:9559")
.withConenctionRetry(2)
.build()
// 配置要读取的图空间及其标签
val nebulaReadVertexConfig: ReadNebulaConfig = ReadNebulaConfig
.builder()
.withSpace("cora")
.withLabel("article")
.withNoColumn(false)
.withReturnCols(List())
.build()
// 将图数据加载为RDD模式
val vertex = spark.read.nebula(config, nebulaReadVertexConfig).loadVerticesToDF()
vertex.printSchema()
vertex.show(20)
println("vertex count: " + vertex.count())
}
}
报错信息
随后打包这个程序,传到服务器去,执行spark-submit,结果报错了。截取一下开始报错的那几行日志,发现是java.lang.NoClassDefFoundError: com/facebook/thrift/protocol/TCompactProtocol
???
阿哲,所以我在本地编译好的jar包是没包含这些运行需要的库的,我人傻了(对maven打包理解不到位,我的)
把算法包导入spark中
在上文介绍nebula-spark-connector时,我一笔带过了,说按官方操作生成一个jar包就行,结果我可倒好,真把这个包给忘了。。。现在我放一下图,看下这个jar包的位置。图中可以清晰看到生成好了的包,那么事实上这个包里就已经包含了我们在Windows IDEA开发环境里用到的库啦(因为版本都是对应好的!)只需把这个jar包复制到spark文件夹下的/jars
目录里即可
报错信息
......
22/04/22 14:16:09 INFO NebulaDataSource: create reader
22/04/22 14:16:09 INFO NebulaDataSource: options {spacename=cora, nocolumn=false, enablestoragessl=false, metaaddress=127.0.0.1:9559, label=article, type=vertex, connectionretry=2, timeout=6000, executionretry=1, enablemetassl=false, paths=[], limit=1000, returncols=, partitionnumber=100}
Exception in thread "main" com.facebook.thrift.transport.TTransportException: java.net.ConnectException: Connection refused (Connection refused)
at com.facebook.thrift.transport.TSocket.open(TSocket.java:206)
at com.vesoft.nebula.client.meta.MetaClient.getClient(MetaClient.java:148)
at com.vesoft.nebula.client.meta.MetaClient.doConnect(MetaClient.java:127)
......
是否是端口有问题?
我查看了一下,执行docker ps -a
,emm,乍一看没什么,但是以普遍理性而言,这里显示的metad端口情况分别是
编号 | 端口 |
---|---|
metad0 | 0.0.0.0:49161->9559/tcp, :::49155->9559/tcp |
metad1 | 0.0.0.0:49155->9559/tcp, :::49161->9559/tcp |
metad2 | 0.0.0.0:49160->9559/tcp, :::49160->9559/tcp |
所以???我是应该去从外部访问,比如,49155端口才对???这明显是由于docker-compose的部署模式导致的,虽然在容器内部确实是在用9559端口部署metad服务,但在容器外部的主机上,没有任何程序占用9559,相反,此时是存在一个端口映射,把外部主机的49155等端口映射到了容器内部的9559端口。所以这就导致我使用的localhost:9559根本啥也获取不到,没进程用这个端口,自然会被拒绝访问
修改readVertex函数中metad的地址配置
val config =
NebulaConnectionConfig
.builder()
// 先试试能不能访问metad0吧
.withMetaAddress("127.0.0.1:49155")
.withConenctionRetry(2)
.build()
报错信息
结果还是报错了???截取报错那几行信息
......
22/04/22 22:38:00 INFO NebulaDataSource: create reader
22/04/22 22:38:00 INFO NebulaDataSource: options {spacename=cora, nocolumn=false, enablestoragessl=false, metaaddress=127.0.0.1:49155, label=article, type=vertex, connectionretry=2, timeout=6000, executionretry=1, enablemetassl=false, paths=[], limit=1000, returncols=, partitionnumber=100}
22/04/22 22:38:00 ERROR MetaClient: Get Space Error: java.net.UnknownHostException: metad2
Exception in thread "main" com.facebook.thrift.transport.TTransportException: java.net.UnknownHostException: metad2
at com.facebook.thrift.transport.TSocket.open(TSocket.java:206)
at com.vesoft.nebula.client.meta.MetaClient.getClient(MetaClient.java:148)
at com.vesoft.nebula.client.meta.MetaClient.freshClient(MetaClient.java:168)
at com.vesoft.nebula.client.meta.MetaClient.getSpace(MetaClient.java:230)
......
哦,所以你甚至已经可以访问这个地址却无法解析主机名了是么???
容器外部真的能被允许去容器里访问东西吗
使用exchange-2.6.1从clickhouse导入数据至nebula报错 UnknownHostException以及中文显示乱码
逛评论区,官方大佬给了一个很好的参考思路。首先,提问者使用nabula-exchange写数据时出现了同样的java.net.UnknownHostException
错误;另一个问题是使用spark-connector的,出现了java.net.ConnectException: Can't assign requested address (connect failed)
的报错。下面是大佬的几个回答
- spark-connector去获取nebula的数据时也是要从存储区拿,即访问storaged。但又不是直接访问storaged,而是需要通过metad去拿到storaged的地址
- spark事实上是要去扫storage去获取信息,如1所述,这需要能够访问metaclient,然而在默认的nebula graph部署中,由于是docker-compose模式,压根就拒绝了外部访问,meta,storage,graph开出来的9个服务里只有一个graphd是对外开放的
- 但是,如果spark和以上这些服务都运行在同一容器网络中,就可以正常访问数据了
- nebula graph studio就是个前端组件,外部可以用7001端口访问,访问后它是docker内部做容器间通信的
最终解决方案
\large\bold\color{red}\ {核心:运行Spark在与meta所属同一容器网络中}
创建一个新容器,直接挂载Spark,加入容器网络
查看容器网络情况
容器名 | 容器内网ip |
---|---|
nebula-docker-compose_storaged0_1 | 172.21.0.5/16 |
nebula-docker-compose_storaged1_1 | 172.21.0.6/16 |
nebula-docker-compose_storaged2_1 | 172.21.0.7/16 |
nebula-docker-compose_metad0_1 | 172.21.0.2/16 |
nebula-docker-compose_metad1_1 | 172.21.0.3/16 |
nebula-docker-compose_metad2_1 | 172.21.0.4/16 |
nebula-docker-compose_graphd_1 | 172.21.0.10/16 |
nebula-docker-compose_graphd1_1 | 172.21.0.9/16 |
nebula-docker-compose_graphd2_1 | 172.21.0.8/16 |
nebula-docker-compose_console_1 | 172.21.0.11/16 |
创建容器
docker run -itd --name spark-master -v /usr/local/java:/usr/local/java -v /usr/local/spark:/usr/local/spark --network nebula-docker-compose_nebula-net kdvolder/jdk8:latest
随后即可查看到容器内网中这一新成员:
容器名 | 容器内网ip |
---|---|
spark-master | 172.21.0.12/16 |
修改spark配置信息以迎合容器主机名
由于是映射的spark,而且临时弄了个jdk8的环境,为了java和spark都可以运行,所以同时挂载了主机的java和spark安装目录。
进入spark-master这个容器
docker exec -it spark-master /bin/bash
所以现在需要修改的只有两样东西:
- java和spark的环境变量(之前文章里讲过~/.bash_profile,一样的操作流程)
- spark-env.sh
修改scala程序中的meta地址
val config =
NebulaConnectionConfig
.builder()
.withMetaAddress("172.21.0.3:9559")
.withConenctionRetry(2)
.build()