概述

Nebula Clients 给用户提供了多种编程语言的 API 用于和 Nebula Graph 交互,并且对服务端返回的数据结构进行了重新封装,便于用户使用。

目前 Nebula Clients 支持的语言有 C++、Java、Python、Golang 和 Rust。

通信框架

Nebula Clients 使用了 fbthrift https://github.com/facebook/fbthrift 作为服务端和客户端之间的 RPC 通信框架,实现了跨语言的交互。

fbthrift 提供了三方面的功能:

  1. 生成代码:fbthrift 可将不同语言序列化成数据结构
  2. 序列化:将生成的数据结构序列化
  3. 通信交互:在客户端、服务端之间传输消息,收到不同语言的客户端的请求时,调用相应的服务端函数

例子

这里以 Golang 客户端为例,展示 fbthrift 在 Nebula Graph 中的应用。

  1. Vertex 结构在服务端的定义:
struct Vertex {
Value vid;
std::vector<Tag> tags; Vertex() = default;
};
  1. 首先, 在 src/interface/common.thrift 中定义一些数据结构:
struct Tag {
1: binary name,
// List of <prop_name, prop_value>
2: map<binary, Value> (cpp.template = "std::unordered_map") props,
} (cpp.type = "nebula::Tag") struct Vertex {
1: Value vid,
2: list<Tag> tags,
} (cpp.type = "nebula::Vertex")

在这里我们定义了一个 Vertex 的结构,其中 (cpp.type = "nebula::Vertex") 标注出了这个结构对应了服务端的 nebula::Vertex

  1. fbthrift 会自动为我们生成 Golang 的数据结构:
// Attributes:
// - Vid
// - Tags
type Vertex struct {
Vid *Value `thrift:"vid,1" db:"vid" json:"vid"`
Tags []*Tag `thrift:"tags,2" db:"tags" json:"tags"`
} func NewVertex() *Vertex {
return &Vertex{}
} ... func (p *Vertex) Read(iprot thrift.Protocol) error { // 反序列化
...
} func (p *Vertex) Write(oprot thrift.Protocol) error { // 序列化
...
}
  1. MATCH (v:Person) WHERE id(v) == "ABC" RETURN v 这条语句中:客户端向服务端请求了一个顶点(nebula::Vertex),服务端找到这个顶点后会进行序列化,通过 RPC 通信框架的 transport 发送到客户端,在客户端收到这份数据时,会进行反序列化,生成对应客户端中定义的数据结构(type Vertex struct)。

客户端模块

在这个章节会以 nebula-go 为例,介绍客户端的各个模块和其主要接口。

  1. 配置类 Configs,提供全局的配置选项。
type PoolConfig struct {
// 设置超时时间,0 代表不超时,单位 ms。默认是 0
TimeOut time.Duration
// 每个连接最大空闲时间,当连接超过该时间没有被使用将会被断开和删除,0 表示永久 idle,连接不会关闭。默认是 0
IdleTime time.Duration
// max_connection_pool_size: 设置最大连接池连接数量,默认 10
MaxConnPoolSize int
// 最小空闲连接数,默认 0
MinConnPoolSize int
}
  1. 客户端会话 Session,提供用户直接调用的接口。
//管理 Session 特有的信息
type Session struct {
// 用于执行命令的时候的身份校验或者消息重试
sessionID int64
// 当前持有的连接
connection *connection
// 当前使用的连接池
connPool *ConnectionPool
// 日志工具
log Logger
// 用于保存当前 Session 所用的时区
timezoneInfo
}
  • 接口定义有以下
	// 执行 nGQL,返回的数据类型为 ResultSet,该接口是非线程安全的。
func (session *Session) Execute(stmt string) (*ResultSet, error) {...}
// 重新为当前 Session 从连接池中获取连接
func (session *Session) reConnect() error {...}
// 做 signout,释放 Session ID,归还 connection 到 pool
func (session *Session) Release() {
  1. 连接池 ConnectionPool,管理所有的连接,主要接口有以下
// 创建新的连接池, 并用输入的服务地址完成初始化
func NewConnectionPool(addresses []HostAddress, conf PoolConfig, log Logger) (*ConnectionPool, error) {...}
// 验证并获取 Session 实例
func (pool *ConnectionPool) GetSession(username, password string) (*Session, error) {...}
  1. 连接 Connection,封装 thrift 的网络,提供以下接口
// 和指定的 ip 和端口的建立连接
func (cn *connection) open(hostAddress HostAddress, timeout time.Duration) error {...}
// 验证用户名和密码
func (cn *connection) authenticate(username, password string) (*graph.AuthResponse, error) {
// 执行 query
func (cn *connection) execute(sessionID int64, stmt string) (*graph.ExecutionResponse, error) {...}
// 通过 SessionId 为 0 发送 "YIELD 1" 来判断连接是否是可用的
func (cn *connection) ping() bool {...}
// 向 graphd 释放 sessionId
func (cn *connection) signOut(sessionID int64) error {...}
// 断开连接
func (cn *connection) close() {...}
  1. 负载均衡 LoadBalance,在连接池里面使用该模块

    • 策略:轮询策略

模块交互解析

  1. 连接池

    • 初始化:

      • 在使用时用户需要先创建并初始化一个连接池 ConnectionPool,连接池会在初始化时会对用户指定的 Nebula 服务所在地址建立连接 Connection,如果在用集群部署方式部署了多个 Graph 服务,连接池会采用轮询的策略来平衡负载,对每个地址建立近乎等量的连接。
    • 管理连接:
      • 连接池内维护了两个队列,空闲连接队列 idleConnectionQueue 和使用中的连接队列 idleConnectionQueue,连接池会定期检测过期空闲的连接并将其关闭。这两个队列在增删元素的时候会通过读写锁来确保多线程执行的正确性。
      • 当 Session 向连接池请求连接时,会检查空闲连接队列中是否有可用的连接,如果有则直接返回给 Session 供用户使用;如果没有可用连接并且当前的总连接数没有超过配置中限定的最大连接数,则新建一个连接给 Session;如果已经到达了最大连接数的限制,返回错误。
    • 一般只有在程序退出时才需要关闭连接池, 在关闭时池中所有的连接都会被断开。
  2. 客户端会话
    • 客户端会话 Session 通过连接池生成,用户需要提供用户密码进行校验,在校验成功后用户会获得一个 Session 实例,并通过 Session 中的连接与服务端进行通信。最常用的接口是 execute(),如果在执行时发生错误,客户端会检查错误的类型,如果是网络原因则会自动重连并尝试再次执行语句。
    • 需要注意,一个 Session 不支持被多个线程同时使用,正确的方式是用多个线程申请多个 Session,每个线程使用一个 Session。
    • Session 被释放时,其持有的连接会被放回到连接池的空闲连接队列中,以便于之后被其他 Session 复用。
  3. 连接
    • 每个连接实例都是等价的,可以被任意 Session 持有,这样设计的目的是这些连接可以被不同的 Session 复用,减少反复开关 Transport 的开销。
    • 连接会将客户端的请求发送到服务端并将其结果返回给 Session。
  4. 用户使用示例
// Initialize connection pool
pool, err := nebula.NewConnectionPool(hostList, testPoolConfig, log)
if err != nil {
log.Fatal(fmt.Sprintf("Fail to initialize the connection pool, host: %s, port: %d, %s", address, port, err.Error()))
}
// Close all connections in the pool when program exits
defer pool.Close() // Create session
session, err := pool.GetSession(username, password)
if err != nil {
log.Fatal(fmt.Sprintf("Fail to create a new session from connection pool, username: %s, password: %s, %s",
username, password, err.Error()))
}
// Release session and return connection back to connection pool when program exits
defer session.Release() // Excute a query
resultSet, err := session.Execute(query)
if err != nil {
fmt.Print(err.Error())
}

返回数据结构

客户端对部分复杂的服务端返回的查询结果进行了封装并添加了接口,以便于用户使用。

查询结果基本类型 封装后的类型
Null
Bool
Int64
Double
String
Time TimeWrapper
Date
DateTime DateTimeWrapper
List
Set
Map
Vertex Node
Edge Relationship
Path PathWrraper
DateSet ResultSet
- Record(用于ResultSet 的行操作)

对于 nebula::Value,在客户端会被包装成 ValueWrapper,并通过接口转换成其他结构。(i.g. node = ValueWrapper.asNode())

数据结构的解析

对于语句 MATCH p= (v:player{name:"Tim Duncan"})-[]->(v2) RETURN p,返回结果为:

+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| p |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:teammate@0 {end_year: 2016, start_year: 2002}]-("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Got 1 rows (time spent 11550/12009 us)

我们可以看到返回的结果包含了一行,类型是一条路径. 此时如果需要取得路径终点(v2)的属性,可以通过如下操作实现:

// Excute a query
resultSet, _ := session.Execute("MATCH p= (v:player{name:"\"Tim Duncan"\"})-[]->(v2) RETURN p") // 获取结果的第一行, 第一行的 index 为0
record, err := resultSet.GetRowValuesByIndex(0)
if err != nil {
t.Fatalf(err.Error())
} // 从第一行中取第一列那个 cell 的值
// 此时 valInCol0 的类型为 ValueWrapper
valInCol0, err := record.GetValueByIndex(0) // 将 ValueWrapper 转化成 PathWrapper 对象
pathWrap, err = valInCol0.AsPath() // 通过 PathWrapper 的 GetEndNode() 接口直接得到终点
node, err = pathWrap.GetEndNode() // 通过 node 的 Properties() 得到所有属性
// props 的类型为 map[string]*ValueWrapper
props, err = node.Properties()

客户端地址

各语言客户端 GitHub 地址:

推荐阅读

《开源分布式图数据库Nebula Graph完全指南》,又名:Nebula 小书,里面详细记录了图数据库以及图数据库 Nebula Graph 的知识点以及具体的用法,阅读传送门:https://docs.nebula-graph.com.cn/site/pdf/NebulaGraph-book.pdf

交流图数据库技术?加入 Nebula 交流群请先填写下你的 Nebula 名片,Nebula 小助手会拉你进群~~

Nebula Graph 源码解读系列|客户端的通信秘密——fbthrift的更多相关文章

  1. 新手阅读 Nebula Graph 源码的姿势

    摘要:在本文中,我们将通过数据流快速学习 Nebula Graph,以用户在客户端输入一条 nGQL 语句 SHOW SPACES 为例,使用 GDB 追踪语句输入时 Nebula Graph 是怎么 ...

  2. Alamofire源码解读系列(四)之参数编码(ParameterEncoding)

    本篇讲解参数编码的内容 前言 我们在开发中发的每一个请求都是通过URLRequest来进行封装的,可以通过一个URL生成URLRequest.那么如果我有一个参数字典,这个参数字典又是如何从客户端传递 ...

  3. Alamofire源码解读系列(六)之Task代理(TaskDelegate)

    本篇介绍Task代理(TaskDelegate.swift) 前言 我相信可能有80%的同学使用AFNetworking或者Alamofire处理网络事件,并且这两个框架都提供了丰富的功能,我也相信很 ...

  4. Alamofire源码解读系列(八)之安全策略(ServerTrustPolicy)

    本篇主要讲解Alamofire中安全验证代码 前言 作为开发人员,理解HTTPS的原理和应用算是一项基本技能.HTTPS目前来说是非常安全的,但仍然有大量的公司还在使用HTTP.其实HTTPS也并不是 ...

  5. Alamofire源码解读系列(十一)之多表单(MultipartFormData)

    本篇讲解跟上传数据相关的多表单 前言 我相信应该有不少的开发者不明白多表单是怎么一回事,然而事实上,多表单确实很简单.试想一下,如果有多个不同类型的文件(png/txt/mp3/pdf等等)需要上传给 ...

  6. Alamofire源码解读系列(二)之错误处理(AFError)

    本篇主要讲解Alamofire中错误的处理机制 前言 在开发中,往往最容易被忽略的内容就是对错误的处理.有经验的开发者,能够对自己写的每行代码负责,而且非常清楚自己写的代码在什么时候会出现异常,这样就 ...

  7. Alamofire源码解读系列(三)之通知处理(Notification)

    本篇讲解swift中通知的用法 前言 通知作为传递事件和数据的载体,在使用中是不受限制的.由于忘记移除某个通知的监听,会造成很多潜在的问题,这些问题在测试中是很难被发现的.但这不是我们这篇文章探讨的主 ...

  8. Alamofire源码解读系列(五)之结果封装(Result)

    本篇讲解Result的封装 前言 有时候,我们会根据现实中的事物来对程序中的某个业务关系进行抽象,这句话很难理解.在Alamofire中,使用Response来描述请求后的结果.我们都知道Alamof ...

  9. Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager)

    Alamofire源码解读系列(七)之网络监控(NetworkReachabilityManager) 本篇主要讲解iOS开发中的网络监控 前言 在开发中,有时候我们需要获取这些信息: 手机是否联网 ...

  10. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

随机推荐

  1. 书写自动智慧文本分类器的开发与应用:支持多分类、多标签分类、多层级分类和Kmeans聚类

    书写自动智慧文本分类器的开发与应用:支持多分类.多标签分类.多层级分类和Kmeans聚类 文本分类器,提供多种文本分类和聚类算法,支持句子和文档级的文本分类任务,支持二分类.多分类.多标签分类.多层级 ...

  2. 强化学习基础篇【1】:基础知识点、马尔科夫决策过程、蒙特卡洛策略梯度定理、REINFORCE 算法

    强化学习基础篇[1]:基础知识点.马尔科夫决策过程.蒙特卡洛策略梯度定理.REINFORCE 算法 1.强化学习基础知识点 智能体(agent):智能体是强化学习算法的主体,它能够根据经验做出主观判断 ...

  3. 4.6 C++ Boost 函数绑定回调库

    Boost 库是一个由C/C++语言的开发者创建并更新维护的开源类库,其提供了许多功能强大的程序库和工具,用于开发高质量.可移植.高效的C应用程序.Boost库可以作为标准C库的后备,通常被称为准标准 ...

  4. C/C++ Qt 常用数据结构

    Qt 是一个跨平台的图形化类库,常用数据结构就是对C++ STL的二次封装,使其更加易用,如下是经常会用到的一些数据结构和算法,其中包括了QString,QList,QLinkedList,QVect ...

  5. PLSQL Developer汉语设置

    PLSQLQ Developer是由Oracle公司推出的数据库开发工具,具有很好的移植性和适应性.但是当我们安装完成Oracle11g PLSQL Developer工具后发现状态栏的显示是英文,对 ...

  6. MD5算法:高效安全的数据完整性保障

    摘要:在数字世界中,确保数据完整性和安全性至关重要.消息摘要算法就是一种用于实现这一目标的常用技术.其中,Message Digest Algorithm 5(MD5)算法因其高效性和安全性而受到广泛 ...

  7. STM32CubeMX教程31 USB_DEVICE - HID外设_模拟键盘或鼠标

    1.准备材料 正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) ST-LINK/V2驱动 ...

  8. 小知识:MAC上添加小米喷墨打印机

    最近新购一个小米喷墨打印机,价格不贵,可彩打资料,也能打印照片,非常提升家庭幸福感的一件物品: 如果使用手机打印,下载米家打印就非常方便了. 但是有时候需要电脑打印,使用自己电脑添加打印机时遇到一些小 ...

  9. MySQL主主+Keepalived架构安装部署

    需求:根据当前客户的生产环境,模拟安装部署一套MySQL主主+Keepalived架构的测试环境,方便后续自己做一些功能性的测试. 1.准备工作 2.MySQL安装部署 3.MySQL主主配置 4.K ...

  10. 《ASP.NET Core 与 RESTful API 开发实战》-- (第10章)-- 读书笔记

    第 10 章 部署 10.1 部署到 IIS ASP.NET Core 应用程序支持部署到 IIS 中,之后它将作为应用程序的反向代理服务器和负载均衡器,向应用程序中转传入的 HTTP 请求 默认情况 ...