暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

演讲回顾|TuGraph-DB兼容Neo4j客户端:Bolt 协议设计与实现

TuGraph 2024-04-23
2812

本文为蚂蚁集团研发工程师王志勇在TuGraph Meetup北京站的演讲,详细的介绍了图数据库Neo4j client和server之间的通讯协议,以及TuGraph-DB对Bolt协议的设计与实现

作者介绍:王志勇,蚂蚁集团图计算团队研发工程师,当前主要负责开源图数据库TuGraph-DB的研发工作,包括技术演进规划。

今天要和大家分享的是关于TuGraph-DB近期新添加的一个功能——兼容Neo4j客户端。这个兼容性工作本质上是实现了Neo4j客户端与服务器之间的通讯协议,即Bolt协议。首先介绍下Bolt协议的基本情况,之后阐述下我们是如何实现这一协议的,然后展示下使用Bolt协议客户端连接TuGraph-DB的效果,最后会说明下目前还有哪些功能尚未实现。

背景

Bolt协议是在Neo4j 3.0版本中引入的,时间大约是2016年。在此之前,Neo4j使用的通讯方式主要是HTTP和WebSocket。自从引入Bolt协议后,已经过去了7到8年,目前它已经成为了Neo4j应用与服务器之间通信的首选方式。

Bolt协议在设计上具有以下几个特点:首先,它是二进制的,与之对应的HTTP协议是基于文本的;其次,Bolt协议支持事务、会话以及集群模式,并且提供了用户身份验证和数据加密功能;最后,Bolt协议中所有的数据传输都是基于流式传输的。

TuGraph-DB的客户端也经历了不断的演进和发展。最初我们仅支持HTTP方式,后来开发了自己的RPC客户端,现在我们开始支持兼容Neo4j的客户端。

兼容Neo4j客户端的最大好处在于生态系统的兼容。单就客户端而言,Neo4j官方开发了五种语言的客户端,加上社区开发的两种,总共有七种语言的客户端可供使用。这些客户端可以直接拿来使用,同时还有一些对接上下游生态系统的组件,比如访问Apache Spark或Apache Kafka,这些都有现成的代码。此外,还有一些开源项目,比如DataX,对Neo4j的适配的代码也是现成的。另外,还有一些编程框架相关的,特别是在Java领域,例如OGM、Spring等开发框架,这些相关的代码也无需重写,可以直接使用。这样就节约了大量的研发资源,周围组件直接用生态内的,我们可以将更多精力投入到数据库自身能力的提升上去。

Bolt协议介绍

Bolt协议主要由三大部分构成。首先是数据结构的编码和解码,它作为一种通信协议,核心功能就是数据传输。传输过程中,数据首先需要被编码,然后在网络上进行传输,接收端再对数据进行解码。其次是消息类型,这个比较常规,每个通讯协议都会定义自己的消息类型集合,主要用于控制和管理通讯过程。最后是消息处理机制,即以状态机的运作方式来处理Bolt协议中的消息,它不是简单的Ping-Pong模式。

数据编解码

在Bolt协议的数据编解码部分,有一个关键词是PackStream。它是Bolt协议中的一种数据序列化方式,源自于MessagePack。MessagePack也是一种数据序列化方案,类似于我们常用的Protobuf。

MessagePack在Redis里面有被使用到,大家都知道Redis是一种内存数据库,它对性能的要求非常高,所以MessagePack有一个特点是序列化和反序列化速度很快。但Bolt协议并没有直接使用MessagePack,而是根据其需求在MessagePack基础上定制了一套名为PackStream的序列化方式。在PackStream序列化方式中,包含几种核心的数据类型,如常用的布尔、整型、浮点型、二进制数组,和类似于字典(dictionary或map structure)的结构体。

由于Bolt协议主要用于传输Cypher查询语句及其响应结果的,所以Cypher的数据类型必然是构建在上述基础数据类型之上的。例如,Cypher数据类型中的节点(Node)、关系(Relationship)和路径(Path),都属于PackStream中的结构体,空间类型和时间类型也是。

左下角的小图解释了PackStream的序列化方式:数据序列化完成后,它的大致结构是。第一个字节表示数据类型,用以标记当前数据是哪种类型。紧接着是size字段,表示数据大小,然后是数据内容本身。这只是一个通用的表示方法,大部分数据序列化后形态都类似,但许多场景下是可以进行优化的。例如,某些数据类型在编码后仅为一个字节,因为它与前面的标识字节合并在一起了。对于一些数值小的整数,如只需一个字节就可以表示的数字,虽然数据类型本身占8个字节,但最终编码完成同样只有一个字节大小,它们的类型和数据本身均被压缩在一个字节中。

消息类型

Bolt协议中的消息主要分为三大类。第一类是Request消息,这类消息是由客户端发送给服务端的;第二类是Summary消息,这是服务端发给客户端的消息;第三类是Detail消息,目前只有一个Record消息,Record消息就是服务端返回给客户端的一行行数据,接下来简要介绍下每种消息。

Hello消息是TCP连接建立后需要发送的第一条消息,它会携带客户端自身的信息,比如客户端是什么语言的,版本号是多少,以及运行平台是什么,还有进行身份验证所需的用户名和密码。Reset消息用于重置会话状态,因为Bolt协议是有会话的,会话就有会话状态,有时候会话会处于失败状态,这时候就需要用Reset消息来复位一下。

Run消息用于执行Cypher查询语句,它携带了查询语句的具体内容。Pull消息是客户端请求服务端拉取数据,一般后面会跟个数字,例如Pull 1000,代表客户端希望拉取1000条数据。服务端接收到这个请求后,会返回不多于1000条数据。如果服务端在返回数据后发现还有更多数据可供返回,它会发送个Success消息,Success消息里面的has_more字段会置1,提示客户端还有更多的数据待发送。客户端收到后会继续发送Pull消息,如此循环,直到服务端返回的Success消息中has_more字段不为1,表示所有数据都拉取完了。Discard消息用于告诉服务端忽略后续特定数量的数据。

Pull和Discard消息可以交替使用,例如客户端可以轮流提取和忽略1000条数据。Begin、Commit和Rollback消息用于显示事务,而Route消息则用于获取集群的路由信息。Goodbye消息用于断开当前的连接。Ignore消息在服务端无法处理任何请求时使用,这时候服务端将回复一个Ignore消息,表示上一个请求没处理,直接丢掉了。Failed消息表示标准的失败响应,而Record消息则用于传输真实数据,例如传输1000行数据时,就会连续发送1000个Record消息给客户端。

服务端状态

Bolt协议消息是按照状态机的方式进行处理的,状态机就涉及到有哪些状态以及这些状态是怎么转换的。右侧的图表是从官方协议文档中摘录的,描述了状态转换的过程。由于图表较长,只截取了一部分,这里仅简要介绍几个最常用的状态。

首先是Connect状态,这是当TCP连接刚建立后的初始状态。随后,客户端发送Hello消息,一旦服务端处理成功,会话进入Ready状态。如果客户端发送Run消息,即执行Cypher语句,服务端随后会进入Streaming状态,此时会话涉及到数据传输。如果服务端的数据尚未发送完毕,它将持续保持在Streaming状态,直至数据完全发送完成后回到Ready状态。

Tx_Ready和Tx_Streaming状态是处理显式事务时使用的。其中,Tx Ready是在客户端发送Begin请求开启显式事务后达到的状态;Tx Streaming与上面的Streaming类似,只不过它发生在显式事务中。Failed状态则意味着当前会话处于失败状态,这种情况很容易发生,例如客户端不小心发送了一个有语法错误的Cypher语句,服务端报错后,会话将进入Failed状态。根据协议的状态机规则,在Failed状态下,服务端无法处理任何消息,如果客户端继续发送语句,服务端会直接忽略并回复Ignore消息。为了继续使用当前会话,客户端需要发送Reset消息,以重置失败状态并让会话回到Ready状态,继续提供服务。

交互举例

以执行一条Cypher语句为例,下面列出了相关的Bolt消息。首先,TCP连接建立后,客户端会发送固定四个字节的数据,这是Bolt协议的标识。紧随其后是发送16个字节的版本号数据,总共4个版本,这是因为Bolt协议存在多个版本。客户端发送支持的版本号给服务端,服务端选择一个共同支持的版本后回复给客户端,这个过程称为版本协商。成功后,客户端和服务端间的通信将在选定的协议版本下进行。

接着客户端发送Hello消息,包含其身份信息以及用户名和密码。服务端验证成功后返回Success消息,并且会提供服务器的信息以及分配的连接ID。然后客户端发送Begin消息来请求开启一个显式事务,其中包括多个参数,如事务模式和数据库名称等。服务端确认后发送Success消息,客户端随后开始发送Run消息执行Cypher查询,并接收由服务端返回的结果。

这里,客户端请求只读取两条数据,那么服务端接着连续发送两个Record消息。随后服务端发送附带has_more字段的Success消息提醒客户端还有未发送的数据,但是客户端选择发送Discard消息来告诉服务端忽略余下数据并继续处理。

最后,客户端提交事务,如果成功,服务端会返回Success消息。

另一个例子说明了Bolt协议的另一个特性,即pipeline流水式处理。客户端可以连续发送多条Cypher查询语句,而不需要等待前一条语句执行完成。例如,客户端连续发送两条“Run”消息,然后使用Pull消息来指定拉取某个特定查询语句的结果,这需要在消息中明确指出语句的ID,以便服务端知道客户端希望获取哪一条查询的结果,但这也要求服务端也必须具备相应的能力,即在一个会话中可以同时执行多条语句。

Bolt协议实现

接下来,我将简要描述TuGraph-DB是如何实现Bolt协议的。起初我们当然会进行搜索,看是否已经有现成的开源实现可以利用。是有一个C语言版本的,但这个C语言实现版本相当陈旧,已经很多年未更新,这个是社区贡献的,并且只支持到Bolt协议的3.0版本,而目前Bolt协议已经发展到了5.0版本,3.0版本可能很快就会被淘汰。因此,我们没有采用这个版本,Neo4j自己是没有没有开发C++版本的客户端的。

后来我们研究了官方的Bolt Golang语言的驱动代码。经过阅读后,我们发现Golang驱动里面Bolt协议这块代码写得比较清晰,于是决定以此为参考,使用C++语言来实现Bolt协议。

在网络框架方面,因为Neo4j是使用Java编写的,Neo4j自己在实现Bolt Server的时候用的是Java里面的Netty框架,Netty是一个NIO网络编程框架,在C++中则可以使用有类似功能的Boost.Asio网络框架来实现。至于消息处理部分,我们严格按照官方文档中描述的状态机规则来构建。

在线程模型的设计上,核心理念是每个连接对应一个线程。主要是因为会话是有状态的,将每个会话绑定在一个固定的线程上,处理起来比较方便。类似地,MySQL也是采用这种方式,为每个连接直接启动一个线程来处理。

关于协议的版本,我们目前采用的是Bolt的4.4版本。Bolt协议有多个主要版本,包括1.0、2.0、3.0、4.0和5.0,每个大版本下还有多个子版本号。而4.4版本是官方的长期支持版本,支持到2026年。

数据编解码

数据编解码这块的实现,主要方法是参考官方Bolt Golang驱动的代码,然后用C++进行翻译。这里举了个例子:Bolt协议里面int64数字的编码实现,左边是Go版本的,右边是C++版本的,可以看到几乎是逐行把Go的语法转换成了C++的语法。

数据编解码它是纯粹的计算逻辑,也可以选择参考官方的协议文档自己编写,但这样效率相对较低,而且枯燥,并且出错的概率也大,对着已有的版本翻译是最快的。但是相应的测试代码也得翻译过来,并且跑通,确保逻辑正确。

Bolt Server 网络模型

这张图是Bolt sever的网络模型,最左边是监听线程,中间是一堆的Bolt IO线程,每个线程处理一些Bolt连接的IO读写,每个连接固定对应右边的一个状态机线程。当消息从Bolt连接里面读出来后,会放到状态机线程的消息队列里面去,然后发个信号唤醒状态机去处理消息。消息的处理是按顺序的,如果没有消息。状态机线程处于休眠状态。

不兼容的点

我们也注意到了一些与官方Neo4j客户端的兼容性问题。Bolt协议是Neo4j自己设计的,服务与自家的产品,并不是一个通用的标准,所以Bolt协议里面不可避免的会有一些Neo4j独有的特性。比如在新版本Neo4j中点的结构体中有一个element_id的字段,是字符串类型类型,这个在TuGraph-DB中没有这个东西,如用Neo4j客户端连TuGraph-DB,在客户端代码中去获取这个字段,目前返回是空的,而上面的三个字段是有的。同理,右边RUN消息里面红框标出来的都是目前TuGraph-DB没有的。

使用效果

我们编写了一段简单的Java代码来展示使用Bolt客户端连TuGraph-DB的效果,与关系型数据库里面的JDBC非常类似,整体的使用体验很好。此外,我们还开发了一个Console 客户端,通过Bolt协议与服务端通信,也可以用这个CLI客户端实现在线流式导出数据。

在性能对比方面,我们发现新实现的Bolt客户端在性能上略优于我们之前自己编写的RPC客户端,原因可能有多方面,例如编码方式的不同或是PackStream的序列化速度更快。

暂未实现的功能

目前尚未实现的功能包括显式事务、集群模式、加密传输和高级身份认证。尽管官方Bolt协议支持这些功能,但由于支持它们需要一些改造,且成本与收益比并不高,因此我们当前只支持基本的事务和身份认证。不过,我们计划在今年适配集群模式。

🌟演讲PPT下载:关注 TuGraph 公众号,后台回复“meetup0330



 欢迎关注TuGraph代码仓库~

TuGraph-DB 图数据库 GitHub
https://github.com/tugraph-family/tugraph-db
TuGraph-Analytics 流式图计算引擎 GitHub
https://github.com/tugraph-family/tugraph-analytics
TuGraph-AGL 图学习引擎 GitHub
https://github.com/tugraph-family/tugraph-antgraphlearning



END


往期回顾
→ 权威报告:蚂蚁集团TuGraph跻身中国图数据库市场“领导者”象限
→ 蚂蚁图计算平台开源业内首个工业级流图计算引擎
→ 洪春涛:图数据库厂商的新机遇在深海中等待


文章转载自TuGraph,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论