如何给图数据库 NebulaGraph 新增一种数据类型,以 Binary 为例
NebulaGraph 内核所自带的数据结构其实已经很丰富了,比如 List、Set、Map、Duration、DataSet 等等,但是我们平时在建表和数据写入的时候,可以用到的数据结构其实比较有限,复杂结构目前仅支持以下几种:
enum PropertyType {
UNKNOWN = 0,
... // 基础类型
TIMESTAMP = 21,
DURATION = 23,
DATE = 24,
DATETIME = 25,
TIME = 26,
GEOGRAPHY = 31,
} (cpp.enum_strict)
所以,有时候因为业务需求,我们需要能存入一些定制化的数据类型,比如机器学习中经常用到的 Embedding 的数据类型,工程上经常会有直接存储二进制数据 Binary 的需求,这就需要开发者自己去添加一个数据类型来满足自己的业务开发需求。
本文将手把手教你如何在 NebulaGraph 中增加一种数据类型,直到可以建表使用并插入对应数据以及查询。
下面我们以一个简单的二进制类型 Binary
的添加步骤来讲解整个流程。
1.命令设计
在实现新增 Binary 类型之前我们先想好要用怎么样的命令去使用这个类型,我们可以参考 NebulaGraph 已有的数据类型的使用
1.1 schema 创建命令
// 创建点表
create tag player(name string, image binary)
// 创建边表
create edge team(name string, logo binary)
上面我们设计新建 schema 时使用 binary
关键字来表示设置二进制类型的属性字段。
1.2 插入数据
这里有一个问题就是,命令只能以字符串形式传输,所以我们如果通过命令来插入二进制数据的话,就需要转码。这里我们以选用 Base64 编码为例。
insert vertex player values "p1":("jimmy", binary("0s=W9d...."))
我们在插入命令里面同样以一个 binary
关键字来表示插入的是二进制数据的字符串而不是普通的字符串。
1.3 查询数据
其实正常的设计,或者现有的 NebulaGraph 代码上面来看,查询语句并不需要做改变,直接按照像读其他数据一样读取 Binary
字段就可以了,只是这里我们需要考虑一个问题,客户端没适配的话怎么办?像 nebula-console、nebula-java、nebula-cpp 这些客户端,我们暂时没法一一去适配新增的类型,所以为了测试的时候使用 nebula-console 能够正常读取到数据,我们需要提供转换函数,将新增的 Binary
类型转换为现有客户端能读取的数据格式。
fetch prop on player "p1" yield base64(player.image) as img_base64
这里我们定义了一个 base64()
的转换函数,将存储的二进制数据再以 Base64的格式输出。(兜兜转转回到原点了(:≡)
定义好命令之后,我们来看看怎么实现这些内容,首先我们需要实现这个 Binary
的数据结构。
2. 定义数据结构
在服务端 C++ 代码中,我们可以以一个 Bytes 数组来表示二进制的数据结构
struct Binary {
std::vector<std::byte> values;
Binary() = default;
Binary(const Binary &) = default;
Binary(Binary &&) noexcept = default;
explicit Binary(std::vector<std::byte> &&vals);
explicit Binary(const std::vector<std::byte> &l);
// 用于直接从命令行的字符串中解析出二进制
explicit Binary(const std::string &str);
... // 其他接口
};
一个简单的数据结构定义好之后,我们需要将这个结构添加到 Value
的 union 中
Value 这个数据结构在 Value.cpp 中定义,它是 nebula 中所有数据结构的一个基类表示,每个新增的数据结构想要和之前其他数据结构一起混用的话,需要在 Value.cpp 里面对各个接口做适配。
这个 Value 的数据结构里面有很多的接口定义,像赋值构造、符号重载、toString、toJson、hash 等接口,都需要去适配。
好在这不是什么难事,参考其他类型的实现就行。唯一要注意的是要细心!
2.1 定义 thrift 的数据结构
因为我们的数据结构还需要进行网络传输,所以我们还需要定义 thrift
文件里面的结构类型并实现序列化能力。
// 新增的数据类型
struct Binary {
1: list<byte> values;
} (cpp.type = "nebula::Binary")
// 在Value union中增加Binary类型
union Value {
1: NullType nVal;
2: bool bVal;
3: i64 iVal;
4: double fVal;
5: binary sVal;
6: Date dVal;
7: Time tVal;
8: DateTime dtVal;
9: Vertex (cpp.type = "nebula::Vertex") vVal (cpp.ref_type = "unique");
10: Edge (cpp.type = "nebula::Edge") eVal (cpp.ref_type = "unique");
11: Path (cpp.type = "nebula::Path") pVal (cpp.ref_type = "unique");
12: NList (cpp.type = "nebula::List") lVal (cpp.ref_type = "unique");
13: NMap (cpp.type = "nebula::Map") mVal (cpp.ref_type = "unique");
14: NSet (cpp.type = "nebula::Set") uVal (cpp.ref_type = "unique");
15: DataSet (cpp.type = "nebula::DataSet") gVal (cpp.ref_type = "unique");
16: Geography (cpp.type = "nebula::Geography") ggVal (cpp.ref_type = "unique");
17: Duration (cpp.type = "nebula::Duration") duVal (cpp.ref_type = "unique");
18: Binary (cpp.type = "nebula::Binary") btVal (cpp.ref_type = "unique");
} (cpp.type = "nebula::Value")
另外我们还需要在 common.thrift
文件中的 PropertyType
该枚举中增加一个 BINARY
类型。
enum PropertyType {
UNKNOWN = 0,
... // 基础类型
TIMESTAMP = 21,
DURATION = 23,
DATE = 24,
DATETIME = 25,
TIME = 26,
GEOGRAPHY = 31,
BINARY = 32,
} (cpp.enum_strict)
2.2 实现 Binary 的 thrift rpc 格式的序列化
这里的代码就不展示了,同样可以参考其他类型的实现。最相近的可以参考 src/common/datatypes/ListOps-inl.h
的实现
3. 命令行实现
数据结构定义好之后,我们可以开始命令行的实现,首先打开 src/parser/scanner.lex
,我们需要新增一个关键字 Binary
:
"BINARY" { return TokenType::KW_BINARY; }
接着打开 src/parser/parser.yy
文件,将关键字声明一下:
$token KW_BINARY
为了尽量减少命令行的影响,我们将 Binary
关键字添加到非保留关键字的集合中:
unreserved_keyword
...
| KW_BINARY { $$ = new std::string("binary"); }
接下来我们要将Binary
关键字添加到建表命令的词法树中:
type_spec
...
| KW_BINARY {
$$ = new meta::cpp2::ColumnTypeDef();
$$->type_ref() = nebula::cpp2::PropertyType::BINARY;
}
最后我们实现插入命令:
constant_expression
...
| KW_BINARY L_PAREN STRING R_PAREN {
$$ = ConstantExpression::make(qctx->objPool(), Value(Binary(*$3)));
delete $3;
}
就这样,我们就简单实现了上面命令设计里面的创建 binary schema 和插入 binary 数据的命令。
4. storaged 服务的读写适配
上面我们搞定了数据结构定义和 rpc 序列化以及命令行适配,一个新增的数据结构通过命令创建后,由 grapd 服务接收到请求并传输给 storaged 服务端。然而 storaged 服务端存储实际的数据是经过编码之后的 string,我们需要为这个新增的数据结构写一个编解码的代码逻辑。
4.1 RowWriterV2 写适配
在代码文件 src/codec/RowWriterV2.cpp
中,有以下几个函数需要适配的。
RowWriterV2::RowWriterV2(RowReader& reader) // 构造函数中适配新增的类似
WriteResult RowWriterV2::write(ssize_t index, const Binary& v) // 新增一个Binary的编码写入函数
这里我直接将 Bytes 数组写入 String 中
WriteResult RowWriterV2::write(ssize_t index, const Binary& v) noexcept {
return write(index, folly::StringPiece(reinterpret_cast<const char*>(v.values.data()), v.values.size()));
}
4.2 RowReaderV2 读适配
在代码文件 src/codec/RowReaderV2.cpp
中,同样有以下函数需要适配
Value RowReaderV2::getValueByIndex(const int64_t index) const {
...
case PropertyType::VID: {
// This is to be compatible with V1, so we treat it as
// 8-byte long string
return std::string(&data_[offset], sizeof(int64_t));
}
case PropertyType::FLOAT: {
float val;
memcpy(reinterpret_cast<void*>(&val), &data_[offset], sizeof(float));
return val;
}
case PropertyType::DOUBLE: {
double val;
memcpy(reinterpret_cast<void*>(&val), &data_[offset], sizeof(double));
return val;
}
...
// code here
case PropertyType::BINARY: {
...
}
}
需要注意的是:读和写必须映射上,怎么写的就怎么读。
至此,在 NebulaGraph 里新增一个数据类型的流程就结束了。
看看效果
感谢你的阅读 (///▽///)
对图数据库 NebulaGraph 感兴趣?欢迎前往 GitHub ✨ 查看源码:https://github.com/vesoft-inc/nebula; 想和其他图技术爱好者一起交流心得?和 NebulaGraph 星云小姐姐 交个朋友再进个交流群;

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
基于 Flink 构建实时数据湖的实践
本文整理自火山引擎云原生计算研发工程师王正和闵中元在本次 CommunityOverCode Asia 2023 数据湖专场中的《基于 Flink 构建实时数据湖的实践》主题演讲。 实时数据湖是现代数据架构的核心组成部分,随着数据湖技术的发展,用户对其也有了更高的需求:需要从多种数据源中导入数据、数据湖与数据源保持实时与一致、在发生变更时能够及时同步,同时也需要高性能查询,秒级返回数据等。所以我们选择使用 Flink 进行出入湖以及 OLAP 查询。Flink 的 批流一体架构、 E xactly O nce 保证和完善的社区生态提供了 众多 C onnector 可以满足前面的需求。Flink 也同样适合 OLAP 查询,这一点将在本文进行详细介绍。 整体架构 在基于 Flink 构建实时数据湖的整体架构中,底层使用 K8s 作为容器编排和管理平台。存储层支持 HDFS 或 S3。由于 Iceberg 良好的文件组织架构和生态,所以选择了 Iceberg 作为 Table Format。计算层则使用 Flink 进行出入湖,其中 Flink SQL 是最常用的出入湖方式,同时也用 ...
-
下一篇
Apache Doris 在奇富科技的统一 OLAP 场景探索实践
导读:随着消费信贷规模快速增长,个人信贷市场呈现场景化、体验感强的特征,精准营销、精细化风险管理以及用户使用体验的优化愈发重要。作为中国卓越的由人工智能驱动的信贷科技服务平台,奇富科技选择将 Apache Doris 作为整体 OLAP 场景统一的分析引擎,并使用 Apache Doris 替换了 ClickHouse 和 MySQL ,使得报表分析场景 SLA 达标率提升至 99% 以上,平均查询耗时降低 50%。使用 Doris 替换了 Elasticsearch,离线标签场景数据导入时效从 4 小时缩短至 1 小时,为营销活动、广告投放等提供强有力的数据支持。此外,凭借 Doris Multi-Catalog 能力,实现湖仓查询加速,彻底解决配置外表繁琐、不支持元信息自动同步等问题,极大提升了数据处理与分析的速度。 作为中国卓越的人工智能驱动的信贷科技服务平台,奇富科技(原 360 数科)致力于帮助金融机构提升智能化水平。经过多年金融领域实践,奇富科技以自身强大安全生态为依托,完成了在人工智能、大数据、云计算等技术方面的专业积累。目前,已与银行、消费金融公司、信托公司等建立广泛合...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- MySQL数据库在高并发下的优化方案
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS8编译安装MySQL8.0.19
- SpringBoot2全家桶,快速入门学习开发网站教程