用 nebula_dart_gdbc 在移动设备玩图数据库,泰酷辣!

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
简介: 当 Dart 遇上数据库,如何实现二者的相结合,让用户在移动端操作数据库呢?本文给出了它的答案,先搞定数据库数据访问接口,再搞定数据传输,最后是封装连接,一切就那么简单。

nebula_dart_gdbc,是访问 NebulaGraph 的 Dart 语言客户端,在 dart_gdbc 的规范下进行开发。

dart_gdbc 是一套使用 Dart 语言定义的图数据库标准数据接口,整体思路参考了 JDBC 的规范。目前已经实现了对 NebulaGraph 的支持。

在先前写 GraphDesk 的过程中,曾使用过 nebula-java(Socket)+ Spring Boot(HTTP)的方式实现了 Flutter 应用对 NebulaGraph 的访问。这无异于一辆可以高速行驶的汽车(Socket),进出家门的时候非要用人推(HTTP)的方式,这一点很难让人接受。而使用 Dart Socket 访问图数据库无疑是 Flutter 应用一个不错的选择。不过,因为 Dart 相比其他编程语言来说,目前相对小众,需求量小,并且是个偏前端的语言,纵观现有的图数据库生态,它们都不支持 Dart。

于是,dart_gdbc 与 nebula_dart_gdbc 应运而生。

随着 Dart 客户端的打通,对于拥有图数据库的朋友来说,通过 Dart 在手机上,甚至嵌入式平台上进行 NebulaGraph 的图探索,已经成为可能。而对于 Flutter 开发者来说,可以试试创建自己的图数据库,使用 nebula_dart_gdbc 直连数据库来实现自己的应用,也是一种奇妙体验。虽然,数据库的常规操作还是经过服务端做数据转发合理一点,但不影响大家尝试这种新玩法。

如果一切顺利的话,Dart 这一环节补上之后,之前我做的 GraphDesk,将会有以下变化:

  • 从程序启动来看,将挣脱 Java 环境的束缚,不再需要等待本地 Java 服务的启动过程,运行体验上会有一个质的变化;
  • 从查询性能的角度上来看,也会更加流畅;
  • 条件允许的情况下,未来将推出 App 版本。

意义层面的内容就说到这,接下来聊聊 nebula_dart_gdbc 的诞生历程,下文带有很强的技术色彩,非业内人士可以选择性跳过。

nebula_dart_gdbc 的设计实现

nebula_dart_gdbc 的大概实现思路同其他语言的客户端一致,按照了下面这个步骤:

  1. 通过 thrift 文件,生成 NebulaGraph 数据访问接口;
  2. 将生成的数据访问接口与 Thrift 结合,完成对 NebulaGraph 的访问;
  3. 封装数据库连接。

显然,真正的开发并没有这么简单。在实际的过程中,我遇到了很多问题,也做了很多尝试,最终才有了现在的 nebula_dart_gdbc。下面来记录下开发一个 Dart 客户端,有哪些操作步骤以及相关问题的解决方案。

生成 NebulaGraph 数据访问接口

第一步,先下载数据接口的 thrift 文件,这是 NebulaGraph 给出的链接:https://github.com/vesoft-inc/nebula/tree/master/src/interface,包含下面这些文件:

  • common.thrift
  • graph.thrift
  • meta.thrift
  • storage.thrift

注意:下载完之后,需要往文件添加对应的包名,如:namespace dart nebula.graph

第二步,下载完接口所需的 NebulaGraph thrift 文件之后,我们再来下载 Thrift,这是 Thrift 的官方下载地址:https://thrift.apache.org/download,这里使用的是最新版本 0.18.1(如果你阅读本文的时候,0.18.1 不是最新版本,下载最新版本即可)。

第三步,逐个文件生成 Dart 代码。使用下面的命令就行:

thrift --gen dart common.thrift
thrift --gen dart graph.thrift
thrift --gen dart meta.thrift
thrift --gen dart storage.thrift

这个过程中,我们会遇到一些问题,这里记录了它们的解决方法。

  • 问题 1:文件会遇到“非语言中立性的标记编译”问题;
    • 解决方案:按报错将文件中的语言标记删除即可。
  • 问题 2:这里使用的 Dart 版本是 2.19.0,由 thrift-0.18.1 生成的 Dart 代码与现今的版本语法并不兼容,而且版本差异很大。报错如下图所示,当然这只是当中的一部分,实际报错的文件有 300 个左右。
  • (这是一张散户欢呼,码农落泪的图)
    • 解决方案:根据编辑器的报错,逐个文件改。可能是最笨的办法,花了好几个小时。更好的方式,应该是让 thrift 的 compiler 支持最新的 Dart 版本,但这个办法可能需要花费更多的时间,而且不一定能成功。(退堂鼓震天响,退散...)

第四步,将生成的 Dart 代码,放到项目中。

至此,完成“通过 thrift 文件,生成 NebulaGraph 数据访问接口”。

数据访问接口与 Thrift 结合

这里,先插播一条广告(请耐心读下去)。

给大家介绍一家的烧烤摊。这家店的特点是,一个服务员有一个托盘,并且这个服务员只服务一桌。服务员很庄重地用托盘,端着一张空白的纸来到顾客面前,由顾客们轮流写上自己喜欢的牛肉串、烤面筋、烤花菜...,再选择调料黑胡椒、孜然...等信息。将纸放到托盘中后,由服务员送到烧烤师傅手中,烧烤师傅按照纸上的要求,烤好后将菜品装到碟子里,放在原来的托盘上。由服务员再端到顾客面前,顾客们分别拿走自己点的菜品。
这家烧烤摊名字叫:Thrift。

这可能是一段迄今为止对 Thrift 最奇怪的比喻。Thrift 是一个 RPC 框架,可将接口的参数转换成二进制数据。下面,介绍 Thrift 两个重要的接口:TProtocol 跟 TTransport。以刚刚烧烤摊为例子,大致可以有以下对应关系(可能有些许不恰当):

  • TProtocol:可以有很多不同的顾客,负责写内容
  • TTransport:托盘 + 服务员,负责传递内容
  • TSocketTransport:服务员
  • THeaderTransport:托盘
  • Socket 连接的 IP 跟端口:桌号
  • 传递给服务器的输出流:纸
  • 调用接口的入参:纸上的内容
  • 传递给客户端的输入流:碟子
  • 调用接口的返回值:牛肉串、烤面筋、烤花菜...黑胡椒、孜然...
  • 服务器:烧烤师傅

好了,广告结束。相信机智的你看到这里,对 Thrift 有了一定的了解。下面继续我们的 Dart 之旅:

首先要参考 nebula-java 源码,对 GraphServiceClient 进行调用。

这个环节我遇到了两个问题:

  • 问题 1:nebula-java 使用的是 fbthrift,而 fbthrift 并没有 Dart 的实现。
    • 解决方案:使用 Apache Thrift 的 Dart 实现。
  • 问题 2:两个实现方案有着比较大的差异,比如说 Apache Thrift 没有 THeaderTransport,共同之处是,都有 Socket 的方式。
    • 解决方案:这里先埋个雷,建立用 Socket 连接再说。

搞定上面两个问题后,我们可以通过 Dart Socket 连接,实现对 NebulaGraph 的访问。

这个步骤怎么说呢?就好像又来了一桌,现在两桌,一桌顾客和服务员都只会闽南语,一桌顾客和服务员都只会普通话。而烧烤师傅啥也不管,只看菜单。新来的这桌只有一个要求,跟隔壁桌点的要一模一样...在两桌语言不通的情况下,没有意外的话,这里要出意外。

好的,我们现在遇到了几个问题:

  • 问题 1:返回了空数据,报错信息无从得知
    • 解决思路:先使用 nebula-java,并对 Socket 发送数据的位置进行断点,将数据拷贝下来,通过 Dart Socket 直接发送,卒。到这个地方,我意识到不得不建立一个可以调试的环境。根据我朴素的直觉,客户端跟服务端应该是一个类似湖面倒影的结构。于是,参考过 nebula-java 客户端的代码后,我决定开了个小灶,建立了一个服务端 Mock,并使用返回伪造数据的方式,实现了 GraphService 接口。此时,服务端就具备了调试的条件。
  • 问题 2:怎么确定 Mock 的结构是正确的?
    • 解决思路:使用 nebula-java 客户端,连接 Mock 服务端,看看是否能够正常返回数据。如果能够正常返回数据,Mock 的结构上的准确性也就可以确定。这边调试使用的是不需要太多条件的 verifyClientVersion 接口。

至此,建立如图所示的结构:

  • 对应关系如下:
    • C1:nebula-java
    • S1:NebulaGraph 数据库
    • C2:nebula_dart_gdbc
    • S2:Mock 测试
  • 整体思路如下:
    • L1 是可靠的,可以发送准确数据,也能接收准确数据;
    • L2 可以发送准确数据,用 L2 的连通性来确保 S2 的准确性;
    • 通过 L3 对 C2 进行调试,当 L2 跟 L3 返回的数据一样时,说明此时的 C2 发送的数据是正确;
    • 执行 L4,当 L4 跟 L1 返回的数据一样时,说明 C2 通过 Socket,连接到了数据库。

基本思路确定之后,开始了漫长的翻译(Java 代码变 Dart 代码)、调试,再翻译、再调试...,在 L2 跟 L3 中反复横跳。每个环节的数据表现形式都是纯数字数组,数组长度、下标对应的值错一个都可能功亏一篑。这里需要细心跟耐心。特别是数字操作上涉及到反码、补码、移位的操作,不同的参数有不同的字节长度,按位读写。以下,再列几个 L2、L3 反复横跳的过程中几个让人比较头大的问题:

  • 问题 1:Apache 的 Thrift 版本,相比 fbthrift 版本缺了很多类,这里就不一一列举。
    • 解决方案:将缺失的类,从 nebula-java 中的 fbthrift 中翻译,连同所依赖的第三方类也要跟着翻译(所幸依赖的并不多,只有 IO 相关的几个类。所幸做数据压缩的 zip 包有依赖,但实际没用到)。
  • 问题 2:结构一样了,但发送的数据还是不一样,Apache 版本中,包括 Socket,用来写数据的容器是 Uint8List,所有放进去的数字都会被强行转成正数,但 fbthrift 中的数据是带正负号的。
    • 解决方案:对所有类型进行修改,全部使用 Int8List 声明跟传输,包括 thrift 生成的类。
  • 问题 3:在 Dart 中并没有 byte、short、int、long 的区分,所有的整型都是 int。
    • 解决方案:同样是整数型,需要特别注意,根据实际的情况手动降低整型精度,从而达到与 Java 中声明类型后赋值时自动舍弃精度的效果。

到这里,L3 已经走通,C1 跟 C2 访问 S2,返回的数据一致,拿下模拟考!继续迎接挑战:

  • 问题 4:调用 L4,通过 C2 连接到 S1,返回的数据还是空数据,明明 L2 跟 L3 从 S2 拿到的数据都是一样的,这让人很费解。
    • 解决方法 1:改写 Apache 版本下 Socket 的机制,放弃使用创建时就指定消息回调,直接使用 async/await 的方式,使得调试更符合顺序思维。然后,对返回结果进行等待读取。虽然读到的数据依然是空的,不过在 Socket 使用方式上还是保留了这个写法。
    • 解决方法 2:后来,我发现这个问题的原因是,Java Socket 跟 Dart Socket 之间写入的方式不同。在 nebula-java 中,接口间的数据传输使用了 THeaderTransport,数据会分为两段发送的,不确定是不是断点的位置不对,服务端拿到的都是一份两段合并后的数据。而 Dart 中的做法是将两个 List<int> 拼接成一段合并发送,在 S2 的表现完全一致,这个问题就很隐蔽,过了相当长一段一行代码不写还不得不保持干活状态的时间。到最后改成在 Dart Socket 的一次传输中,强行将两个 List<int> 剥离,使用两个 Stream 进行传输。至此,L4 成功,连通性问题解决,说实话,最后这点带有一丝运气成分。

封装数据库连接

在这个环节,实现思路中断的情况比较少碰到,整体算流畅,比较花时间的是结果集的处理。

nebula_dart_gdbc,并没有沿用 NebulaGraph 其他客户端的写法。而是整体上参考了 JDBC 的思路,将数据库连接、执行、结果集等进行了封装,形成 dart_gdbc。在这套接口标准下,对 nebula_dart_gdbc 进行实现。

熟悉 JDBC 的朋友,对以下流程就不陌生了,dart_gdbc 的调用流程如下:

  • 注册驱动
  • 获取连接
  • 创建语句
  • 执行语句
  • 处理结果集
  • 关闭连接

以下是 nebula_dart_gdbc 在 dart_gdbc 的规范下的代码示例:

import 'package:nebula_dart_gdbc/nebula_dart_gdbc.dart';

void main() async {
   
   
  // 注册驱动
  DriverManager.registerDriver(NgDriver());

  // 获取连接
  var conn = await DriverManager.getConnection(
    'gdbc.nebula://127.0.0.1:9669/?space=test',
    username: 'root', // username is optional
    password: 'nebula', // password is optional
  );

  // 创建语句
  var stmt = await conn.createStatement();
  // 执行语句
  var rs = await stmt.executeQuery(gql: 'SHOW SPACES;');
  // 打印结果
  print(rs);
  // 关闭连接
  conn.close();
}

就目前而言,拿到结果集之后,还需要自行按数组下标位进行数据组装。结果集的结构采用“头部数组”+“数据数组”的形式。在实际的开发中,处理上可能会麻烦一些。目前,我正在考虑在 dart_gdbc 未来的版本中,加入对象图映射的埋点回调。或者再开一个 OGM 的项目。关于这一点,目前只是一个还不太成熟的想法。

结尾

在文章结束部分,跟大家分享下写这个项目的心路历程。

起初,我对工作量大有一定的心理准备,铁了心想试一试。上手之后,才意识到并不仅仅是语法翻译一下这么简单,特别是不同语言的生态不同,引用的第三方也就随之不同,即使是相同的功能,运行机制也有所不同,这一部分的工作量会比想象中的要大。

再一个就是没有办法对数据库直接断点调试,算是一种黑盒调用,如同凭借着微弱的光线在黑夜中前行,对毫无头绪的问题有时候会产生一种无力感,只能凭借着经验做一些猜测,然后一点点去验证。但当连上实际的数据库,并拿到数据的那一刻,即使多年来已经习惯了被 bug 吊打再消灭 bug 的流程,但还是激动了一下。当开始着手建立 dart_gdbc 规范的时候,在《序》里写到的“责无旁贷”在脑海里回荡。直到今天发布第一个版本,这份热情算多了一份实实在在的兑现。

最后,当然是希望这个项目能够对基础软件生态有所帮助。个人也会不断修复 bug,完善功能,希望能够成为一个稳定可靠的项目。对项目感兴趣的话,欢迎参与开发。另外,dart_gdbc 与 nebula_dart_gdbc 目前收录在【图社区】(https://github.com/graph-cn),以后有新的关于图的开源项目,也会在这里发布,欢迎围观。

最后的最后,这篇文章及项目的背后有思为和想念小鱼干的清蒸两位老师的帮助,感谢。

传送门


谢谢你读完本文 (///▽///)

如果你想尝鲜图数据库 NebulaGraph,记得去 GitHub 下载、使用、(^з^)-☆ star 它 -> GitHub;和其他的 NebulaGraph 用户一起交流图数据库技术和应用技能,留下「你的名片」一起玩耍呀~

2023 年 NebulaGraph 技术社区年度征文活动正在进行中,来这里领取华为 Meta 60 Pro、Switch 游戏机、小米扫地机器人等等礼品哟~ 活动链接:https://discuss.nebula-graph.com.cn/t/topic/13970

相关实践学习
阿里云图数据库GDB入门与应用
图数据库(Graph Database,简称GDB)是一种支持Property Graph图模型、用于处理高度连接数据查询与存储的实时、可靠的在线数据库服务。它支持Apache TinkerPop Gremlin查询语言,可以帮您快速构建基于高度连接的数据集的应用程序。GDB非常适合社交网络、欺诈检测、推荐引擎、实时图谱、网络/IT运营这类高度互连数据集的场景。 GDB由阿里云自主研发,具备如下优势: 标准图查询语言:支持属性图,高度兼容Gremlin图查询语言。 高度优化的自研引擎:高度优化的自研图计算层和存储层,云盘多副本保障数据超高可靠,支持ACID事务。 服务高可用:支持高可用实例,节点故障迅速转移,保障业务连续性。 易运维:提供备份恢复、自动升级、监控告警、故障切换等丰富的运维功能,大幅降低运维成本。 产品主页:https://www.aliyun.com/product/gdb
目录
相关文章
|
缓存 移动开发 图形学
进击的 Vulkan 移动开发(二)之谈谈对渲染流程的理解
都说 OpenGL 、Vulkan 是用来绘制二维、三维图形的,那么这个绘制渲染的流程到底是怎么样的呢?这里,谈谈我自己对它的理解。
600 0
进击的 Vulkan 移动开发(二)之谈谈对渲染流程的理解
|
4月前
|
JSON 监控 中间件
多图详解万星 Restful 框架原理与实现
多图详解万星 Restful 框架原理与实现
|
4月前
|
存储 前端开发 JavaScript
"Angular与AWS Amplify的神奇之处:如何用云端连接技术让你的项目一鸣惊人?"
【8月更文挑战第31天】在现代软件开发中,云端连接的前端应用已成为主流。本文探讨了Angular与AWS Amplify的结合,展示了如何通过示例代码快速构建云端连接的前端应用。Angular是由Google支持的开源前端框架,而AWS Amplify是AWS提供的云服务,两者结合可以快速构建云端连接的前端应用。文中还分享了一些最佳实践,帮助开发者更高效地使用这两种技术构建高性能的云端连接的前端应用。随着Angular和AWS Amplify生态的不断成熟,它们将在未来的Web开发中扮演更加重要的角色。
54 0
|
4月前
|
开发框架 JavaScript 前端开发
Angular 与 Ionic 简直太牛啦!双剑合璧构建高性能移动应用,开启跨平台开发新征程!
【8月更文挑战第31天】Angular是由Google维护的前端开发框架,使用TypeScript提供组件化开发、依赖注入等功能,适合构建复杂Web应用。Ionic则是基于Angular和Cordova的开源移动应用框架,提供丰富的UI组件以实现跨平台移动应用的快速构建。结合使用Angular与Ionic不仅能够显著提升开发速度并简化流程,还能够保证应用在iOS、Android及Web等多个平台上的良好运行,同时两者都有成熟的社区支持与资源可供利用。为了开始使用这两款工具,开发者需先安装Node.js和npm,接着利用Angular CLI和Ionic CLI创建项目并进行开发工作。
55 0
|
4月前
|
前端开发 JavaScript API
强强联手打造桌面应用新标杆:Angular与Electron的完美融合——从环境搭建到通信机制,全面解析构建跨平台应用的最佳实践与技巧
【8月更文挑战第31天】随着Web技术的进步,开发者们越来越多地采用Web技术来构建桌面应用程序。通过结合使用开源框架Electron及前沿的前端框架Angular,开发者能充分利用JavaScript、HTML和CSS打造出高性能且易维护的跨平台桌面应用。本文将详细介绍如何搭建基于Angular与Electron的开发环境,包括创建Angular项目、安装Electron及相关依赖、配置Electron主进程以及实现Angular应用与Electron间的通信等关键步骤,并最终将应用打包成多平台可执行文件,为读者提供了一套完整的解决方案以快速入门并实践这一强大技术组合。
120 0
|
4月前
|
存储 设计模式 运维
Angular遇上Azure Functions:探索无服务器架构下的开发实践——从在线投票系统案例深入分析前端与后端的协同工作
【8月更文挑战第31天】在现代软件开发中,无服务器架构因可扩展性和成本效益而备受青睐。本文通过构建一个在线投票应用,介绍如何结合Angular前端框架与Azure Functions后端服务,快速搭建高效、可扩展的应用系统。Angular提供响应式编程和组件化能力,适合构建动态用户界面;Azure Functions则简化了后端逻辑处理与数据存储。通过具体示例代码,详细展示了从设置Azure Functions到整合Angular前端的全过程,帮助开发者轻松上手无服务器应用开发。
31 0
|
4月前
|
缓存 前端开发 JavaScript
Angular邂逅PWA:一场关于如何利用现代Web技术栈中的明星框架与渐进式理念,共同编织出具备原生应用般丝滑体验、离线访问及桌面集成能力的未来Web应用的探索之旅
【8月更文挑战第31天】本文详细介绍如何利用Angular将传统Web应用升级为渐进式Web应用(PWA),克服后者在网络依赖、设备集成及通知功能上的局限。通过具体命令行操作与代码示例,指导读者从新建Angular项目到配置`manifest.json`和服务工作进程,最终实现离线访问、主屏添加及推送通知等功能,显著提升用户体验。适合各水平开发者学习实践。
42 0
|
4月前
|
前端开发 JavaScript 开发者
Angular状态管理神器ngrx Store:从零开始的实践指南与进阶优化秘籍,让你的前端应用状态井井有条、高效运行的绝招大揭秘
【8月更文挑战第31天】状态管理在现代Web应用开发中至关重要,特别是在构建大型、复杂的Angular应用时。ngrx Store借鉴Redux的设计理念,提供集中式状态管理和可预测的数据流,有助于增强应用的可维护性和可测试性。
85 0
|
6月前
|
Python
Mandelbrot集的最新变化形态一览——MandelBox,Mandelbulb,Burning Ship,NebulaBrot
该文介绍了几种基于Mandelbrot集的衍生形态,包括Mandelbulb、MandelBox、Burning Ship和NebulaBrot。Mandelbulb是3D扩展,使用球坐标;MandelBox利用盒映射创造复杂形状;Burning Ship以复数模和实部迭代;NebulaBrot则结合多种分形特征。文中提供了简单的Python代码示例来生成这些图形,并提到了相关学习资源。
|
人工智能 分布式计算 算法
手把手教你用 NebulaGraph AI 全家桶跑图算法
ng_ai 的全名是:Nebulagraph AI Suite,顾名思义,它是在 NebulaGraph 之上跑算法的 Python 套件,希望能给 NebulaGraph 的用户一个自然、简洁的高级 API。简单来说,用很少的代码量就可以执行图上的算法相关的任务。
104 0
手把手教你用 NebulaGraph AI 全家桶跑图算法