Nebula 架构剖析系列(一)图数据库的存储设计

  • 时间:
  • 浏览:0

为了方便对于 WAL 进行定制,Nebula KVStore 实现了当事人的 WAL 模块,每个 partition 都不 当事人的 WAL,从前在追数据时,不需要需要 进行 wal split 操作, 更加高效。 另外,为了实现一点特殊的操作,专门定义了 Command Log 一点类别,那些 log 只为了使用 Raft 来通知所有 replica 执行某有有一一三个多多特定操作,并比较慢 真正的数据。除了 Command Log 外,Nebula 还提供了一类日志来实现针对某个 Partition 的 atomic operation,类似于 CAS,read-modify-write,  它充分利用了Raft 串行的形状。



图二 Vertex Key Format

图存储的主要数据是点和边,但 Nebula 存储的数据是一张属性图,也要是我说除了点和边以外,Nebula 还存储了它们对应的属性,以便更高效地使用属性过滤。

有有一一三个多多点之间将会所处多种类型的边,Nebula 用 Edge Type 来表示边类型。而同一类型的边将会所处多条,比如,定义有有一一三个多多 edge type "转账",用户 A 将会多次转账给 B, 不要 不要 Nebula 又增加了有有一一三个多多 Rank 字段来做区分,表示 A 到 B 之间多次转账记录。 Edge key 的 format 如图3 所示:

一点层会将图语义的接口转化成 kv 操作。为了提高遍历的性能,不需要 做并发操作。

举个例子,Nebula 利用 WAL 实现了无锁的 CAS 操作,而每个 CAS 操作不需要 时候的 WAL 删改 commit 时候不需要 执行,不要 不要 对于有有一一三个多多 batch,将会后边夹杂了好多个 CAS 类型的 WAL, 亲戚亲戚朋友还不需要 把一点 batch 分成粒度更小的好多个 group,group 之间保证串行。还有,command 类型的 WAL 不需要 它后边的 WAL 在其 commit 时候不需要 执行,不要 不要 整个 batch 划分 group 的操作工程实现上比较有特色。

在讨论某个数据库时,存储 ( Storage ) 和计算 ( Query Engine ) 通常是讨论的热点,也是爱好者们了解某个数据库不可或缺的偏离 。每个数据库都不 其独有的存储、计算依据 ,今天就和图图来学习下图数据库 Nebula Graph 的存储偏离 。

这篇文章给亲戚亲戚朋友大致介绍了 Nebula Storage 层的整体设计, 将会篇幅意味着着, 不要 不要 细节比较慢 展开讲, 欢迎亲戚亲戚朋友到亲戚亲戚朋友的微信群里提问,加入 Nebula Graph 交流群,请联系 Nebula Graph 官方小助手微信号:NebulaGraphbot。

在 local store engine 之上,便是亲戚亲戚朋友的 Consensus 层,实现了 Multi Group Raft,每有有一一三个多多 Partition 都对应了一组 Raft Group,这里的 Partition 便是亲戚亲戚朋友的数据分片。目前 Nebula 的分片策略采用了 静态 Hash 的依据 ,具体按照那些依据 进行 Hash,在下有有一一三个多多章节 schema 里会提及。用户在创建 SPACE 不需要 指定 Partition 数,Partition 数量一旦设置便不可更改,一般来讲,Partition 数目不需要 满足业务将来的扩容需求。

作为有有一一三个多多分布式系统,KVStore 的 replication,scale out 等功能需 Raft 的支持。当前,市面上讲 Raft 的文章非常多,具体原理性的内容,这里不再赘述,本文主要说一点 Nebula Raft 的一点特点以及工程实现。

将会 Raft 的日志不允许空洞,几乎所有的实现都会采用 Multi Raft Group 来缓解一点问提,后该 partition 的数目几乎决定了整个 Raft Group 的性能。但这也并都不 说 Partition 的数目不要 越好:每有有一一三个多多 Raft Group 组织组织结构都不 存储一系列的情况信息,后该每有有一一三个多多 Raft Group 有当事人的 WAL 文件,后该 Partition 数目不要 会增加开销。此外,当 Partition 不要 时, 将会负载比较慢 足够高,batch 操作是比较慢 意义的。比如,有有一一三个多多有 1w tps 的线上系统单机,它的单机 partition 的数目超过 1w,将会每个 Partition 每秒的 tps 不需要 否 1,从前 batch 操作就背叛了意义,还增加了 CPU 开销。

实现 Multi Raft Group 的最关键之处有两点, 第一是共享 Transport 层,将会每有有一一三个多多 Raft Group 组织组织结构都不需要 向对应的 peer  发送消息,将会不需要 否共享 Transport 层,连接的开销巨大;第二是应用线程池池模型,Mutli Raft Group 一定要共享一组应用线程池池池,后该该造成系统的应用线程池池数目不要 ,意味着着少量的 context switch 开销。

Transfer leadership 一点操作对于 balance 来讲至关重要,当亲戚亲戚朋友把某个 Paritition 从一台机器挪到另一台机器时,首先便会检查 source 是都不 leader,将会是的话,不需要 先把他挪到另外的 peer 后边;在搬迁数据完毕时候,通常不需要 把 leader 进行一次 balance,从前每台机器承担的负载不需要 保证均衡。



图4 出边的 Key Format



图5 入边的 Key Format



图一  storage service 架构图

在 KVStore 的接口之上,Nebula 封装有图语义接口,主要的接口如下:

GitHub:https://github.com/vesoft-inc/nebula

关于多图空间(space)的支持:有有一一三个多多 Nebula KVStore 集群可不可不还能不能 支持多个 space,每个 space 可设置当事人的 partition 数和 replica 数。不同 space 在物理上是删改隔离的,后该在同有有一一三个多多集群上的不同 space 可支持不同的 store engine 及分片策略。

对于点来说,亲戚亲戚朋友使用不同的 Tag 表示不类似于型的点,同有有一一三个多多 VertexID 可不可不还能不能 关联多个 Tag,而每有有一一三个多多 Tag 都不 当事人对应的属性。对应到 kv 存储后边,亲戚亲戚朋友使用 vertexID + TagID 来表示 key,  亲戚亲戚朋友把相关的属性编码后放进去 value 后边,具体 key 的 format 如图2 所示:

对于点或边的属性信息,有对应的一组 kv pairs,Nebula 将它们编码后所处对应的 value 里。将会 Nebula 使用强类型 schema,不要 不要 在解码时候,不需要 先去 Meta Service 中取具体的 schema 信息。另外,为了支持在线变更 schema,在编码属性时,会加入对应的 schema 版本信息,具体的编解码细节在这里不作展开,后续会有专门的文章讲解这块内容。

微博:https://weibo.com/nebulagraph

OK,到这里亲戚亲戚朋友基本上了解了 Nebula 是怎样存储数据的,那数据是怎样进行分片呢?很简单,对 Vertex ID 取模 即可。通过对 Vertex ID 取模,同有有一一三个多多点的所有_出边_,_入边_以及一点点上所有关联的 _Tag 信息_都会被分到同有有一一三个多多 Partition,一点依据 大大地提升了查询效率。对于在线图查询来讲,最常见的操作便是从有有一一三个多多点开始英语 了了向外 BFS(广度优先)拓展,于是拿有有一一三个多多点的出边将会入边是最基本的操作,而一点操作的性能也决定了整个遍历的性能。BFS 中将会会出現按照一点属性进行剪枝的情况,Nebula 通过将属性与点边所处一同,来保证整个操作的高效。当前一点的图数据库通过 Graph 50 将会 Twitter 的数据集试来验证当事人的高效性,这并比较慢 代表性,将会那些数据集比较慢 属性,而实际的场景中大偏离 情况都不 属性图,后该实际中的 BFS 要是我需要 进行少量的剪枝操作。

Nebula KVStore 主要采用 RocksDB 作为本地的存储引擎,对于多硬盘机器,为了充分利用多硬盘的并发能力,Nebula 支持当事人管理多块盘,用户只需配置多个不同的数据目录即可。分布式 KVStore 的管理由 Meta Service 来统一调度,它记录了所有 Partition 的分布情况,以及当前机器的情况,当用户增减机器时,只不需要 通过 console 输入相应的指令,Meta Service 便不需要 生成整个 balance plan 并执行。(五种比较慢 采用删改自动 balance 的依据 ,主要是我为了减少数据搬迁对于线上服务的影响,balance 的时机由用户当事人控制。)

为那些要当事人做 KVStore,这是亲戚亲戚朋友无数次被问起的问提。理由很简单,当前开源的 KVStore 都比较慢满足亲戚亲戚朋友的要求:

在有有一一三个多多图中,每两根逻辑意义上的边,在 Nebula Graph 中会建模成有有一一三个多多独立的 key-value,分别称为 out-key 和in-key。out-key 与这条边所对应的起点存储在同有有一一三个多多 partition 上,in-key 与这条边所对应的终点存储在同有有一一三个多多partition 上。通常来说,out-key 和 in-key 会分布在有有一一三个多多不同的 Partition 中。

Snapshot 怎样与 Raft 流程结合起来,论文中并比较慢 细讲,后该一点偏离 我认为是有有一一三个多多 Raft 实现里最容易出错的地方,将会这里会产生少量的 corner case。

在 Consensus 层后边也要是我 Storage Service 的最上层,便是亲戚亲戚朋友的 Storage interfaces,一点层定义了一系列和图相关的 API。 那些 API 请求会在一点层被翻译成一组针对相应 Partition 的 kv 操作。正是一点层的所处,使得亲戚亲戚朋友的存储服务变成了真正的图存储,后该,Storage Service 要是我有有一一三个多多 kv 存储罢了。而 Nebula 没把 kv 作为有有一一三个多多服务单独提出,其最主要的意味着着便是图查询过程中会涉及到少量计算,那些计算往往不需要 使用图的 schema,而 kv 层是比较慢 数据 schema 概念,从前设计会比较容易实现计算下推。

实现 transfer leadership, 不需要 注意的是 leader 放弃当事人的 leadership,和 follower 开始英语 了了进行 leader election 的时机。对于 leader 来讲,当 transfer leadership command 在 commit 的时候,它放弃 leadership;而对于 follower 来讲,当收到此 command 的时候就要开始英语 了了进行 leader election, 这套实现要和 Raft 五种的 leader election 走一套路径,后该很容易出現一点难以正确处理的 corner case。

为了正确处理脑裂,当有有一一三个多多 Raft Group 的成员所处变化时,不需要 有有一一三个多多多后边情况, 一点情况下 old group 的多数派与 new group 的多数派无缘无故有 overlap,从前就正确处理了 old group 将会新 group 单方面做出决定,这要是我论文中提到的 joint consensus 。为了更加复杂,Diego Ongaro 在当事人的博士论文中提出每次增减有有一一三个多多 peer 的依据 以保证 old group 的多数派无缘无故与 new group 的多数派有 overlap。 Nebula 的实现也采用了一点依据 ,只不过 add member 与 remove member 的实现有所区别,具体实现依据 本文不作讨论,有兴趣的同学可不可不还能不能 参考 Raft Part class 后边 addPeer  /  removePeer  的实现。

在 KVStore 的接口上,Nebula 也一同封装了一套 meta 相关的接口。Meta Service 不但提供了图 schema 的增删查改的功能,还提供了集群的管理功能以及用户鉴权相关的功能。Meta Service 支持单独部署,也支持使用多副从前保证数据的安全。

知乎:https://www.zhihu.com/org/nebulagraph/posts

如图1 所示,Storage Service 共有三层,最底层是 Store Engine,它是有有一一三个多多单机版 local store engine,提供了对本地数据的 get / put / scan / delete 操作,相关的接口放进去 KVStore / KVEngine.h 文件后边,用户删改可不可不还能不能 根据当事人的需求定制开发相关 local store plugin,目前 Nebula 提供了基于 RocksDB 实现的  Store Engine。

基于上述要求,Nebula 实现了当事人的 KVStore。当然,对于性能删改不敏感且不太希望搬迁数据的用户来说,Nebula 也提供了整个KVStore 层的 plugin,直接将 Storage Service 搭建在第三方的 KVStore 后边,目前官方提供的是 HBase 的 plugin。

举有有一一三个多多例子,当 leader 发送 snapshot 过程中,将会 leader 所处了变化,该为甚办? 一点时候,有将会 follower 只接到了一半的 snapshot 数据。 不要 不要 不需要 有有一一三个多多多 Partition 数据清理过程,将会多个 Partition 共享一份存储,后该怎样清理数据又是有有一一三个多多很麻烦的问提。另外,snapshot 过程中,会产生少量的 IO,为了性能考虑,亲戚亲戚朋友不希望一点过程与正常的 Raft 共用有有一一三个多多 IO threadPool,后该整个过程中,还不需要 使用少量的内存,怎样优化内存的使用,对于性能十分关键。将会篇幅意味着着,亲戚亲戚朋友何必 会在本文对那些问提展开讲述,有兴趣的同学可不可不还能不能 参考 SnapshotManager  的实现。

Learner 一点角色的所处主要是我为了 应对扩容 时,新机器不需要 "追"相当长一段时间的数据,而这段时间有将会会所处意外。将会直接以 follower 的身份开始英语 了了追数据,就会使得整个集群的 HA 能力下降。 Nebula 后边 learner 的实现要是我采用了后边提到的 command wal,leader 在写 wal 时将会碰到 add learner 的 command, 就会将 learner 加入当事人的 peers,并把它标记为 learner,从前在统计多数派的时候,就不需要算上 learner,后该日志还是会照常发送给它们。当然 learner 要是我会主动发起选举。



图三 Edge Key Format

Nebula Graph:有有一一三个多多开源的分布式图数据库。

Nebula 的 Storage 含高高有一一三个多多偏离 , 一是 meta 相关的存储, 亲戚亲戚朋友称之为 Meta Service ,从前是 data 相关的存储, 亲戚亲戚朋友称之为 Storage Service。 一点个多多多服务是有有一一三个多多独立的应用应用线程池池,数据也删改隔离,当然部署也是分别部署, 不过两者整体架构相差不大,本文最都会提到这点。 将会比较慢 特殊说明,本文中 Storage Service 代指 data 的存储服务。接下来,亲戚亲戚朋友就随我一同看一下 Storage Service 的整个架构。 Let's go~

针对 Edge Type 的值,若将会大于 0 表示出边,则对应的 edge key format 如图4 所示;若 Edge Type 的值小于 0,则对应的 edge key format 如图5 所示

对于每个 Partition来说,将会串行写 WAL,为了提高吞吐,做 batch 是十分必要的。一般来讲,batch 并比较慢 那些一阵一阵的地方,后该 Nebula 利用每个 part 串行的特点,做了一点特殊类型的 WAL,带来了一点工程上的挑战。