Skip to content

Commit

Permalink
[doc]improve storage design.md English version (vesoft-inc#1721)
Browse files Browse the repository at this point in the history
* improve storage desing

* minor

* rebase upstream

Co-authored-by: dutor <440396+dutor@users.noreply.github.com>
  • Loading branch information
whitewum and dutor authored Mar 12, 2020
1 parent 87b3edf commit 9ccc784
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 59 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
## 架构

![image](https://user-images.githubusercontent.com/42762957/71571213-2cd94c00-2b14-11ea-9b7f-a65067c61a63.png)

图一  storage service 架构图

如图1 所示,Storage Service 共有三层,最底层是 Store Engine,它是一个单机版 local store engine,提供了对本地数据的 `get` / `put` / `scan` / `delete` 操作,相关的接口放在 KVStore/KVEngine.h 文件里面,用户完全可以根据自己的需求定制开发相关 local store plugin,目前 **Nebula Graph** 提供了基于 RocksDB 实现的 Store Engine。
Expand All @@ -22,6 +23,7 @@
对于点来说,**Nebula Graph** 使用不同的 Tag 表示不同类型的点,同一个 VertexID 可以关联多个 Tag,而每一个 Tag 都有自己对应的属性。对应到 kv 存储里面,**Nebula Graph** 使用 vertexID + TagID 来表示 key,  把相关的属性编码后放在 value 里面,具体 key 的 format 如图2 所示:

![image](https://user-images.githubusercontent.com/42762957/71571002-1252a300-2b13-11ea-915f-b3fd16920b95.png)

图二 Vertex Key Format

- `Type` :  1 个字节,用来表示 key 类型,当前的类型有 data, index, system 等
Expand All @@ -35,6 +37,7 @@
两个点之间可能存在多种类型的边,**Nebula Graph** 用 Edge Type 来表示边类型。而同一类型的边可能存在多条,比如,定义一个 edge type "转账",用户 A 可能多次转账给 B, 所以 **Nebula Graph** 又增加了一个 Rank 字段来做区分,表示 A 到 B 之间多次转账记录。 Edge key 的 format 如图3 所示:

![1](https://user-images.githubusercontent.com/51590253/75966340-20eb7b00-5f05-11ea-9d8e-c3ee17a33038.png)

图三 Edge Key Format

- `Type` :  1 个字节,用来表示 key 的类型,当前的类型有 data, index, system 等。
Expand Down Expand Up @@ -72,11 +75,13 @@
- **数据强一致**:这是分布式系统决定的;
- **使用 C++实现**:这由团队的技术特点决定;

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

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

为了方便对于 WAL 进行定制,**Nebula Graph** KVStore 实现了自己的 WAL 模块,每个 partition 都有自己的 WAL,这样在追数据时,不需要进行 wal split 操作, 更加高效。 另外,为了实现一些特殊的操作,专门定义了 Command Log 这个类别,这些 log 只为了使用 Raft 来通知所有 replica 执行某一个特定操作,并没有真正的数据。除了 Command Log 外,Nebula Graph 还提供了一类日志来实现针对某个 Partition 的 atomic operation,例如 CAS,read-modify-write,  它充分利用了Raft 串行的特性。
分布式 KVStore 的管理由 Meta Service 来统一调度,它记录了所有 Partition 的分布情况,以及当前机器的状态,当用户增减机器时,只需要通过 console 输入相应的指令,Meta Service 便能够生成整个 balance plan 并执行。(之所以没有采用完全自动 balance 的方式,主要是为了减少数据搬迁对于线上服务的影响,balance 的时机由用户自己控制,通常会在业务低谷进行。)

为了方便对于 WAL 进行定制,**Nebula Graph** KVStore 实现了自己的 WAL 模块,每个 partition 都有自己的 WAL,这样在追数据时,不需要进行 wal split 操作, 更加高效。 另外,为了实现一些特殊的操作,专门定义了 Command Log 这个类别,这些 log 只为了使用 Raft 来通知所有 replica 执行某一个特定操作,并没有真正的数据。除了 Command Log 外,**Nebula Graph** 还提供了一类日志来实现针对某个 Partition 的 atomic operation,例如 CAS,read-modify-write,  它充分利用了Raft 串行的特性。

关于多图空间(space)的支持:一个 Nebula Graph KVStore 集群可以支持多个 space,每个 space 可设置自己的 partition 数和 replica 数。不同 space 在物理上是完全隔离的,而且在同一个集群上的不同 space 可支持不同的 store engine 及分片策略。

Expand All @@ -86,8 +91,9 @@

### Multi Raft Group

由于 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 层,连接的开销巨大;**第二是线程模型**,Multi Raft Group 一定要共享一组线程池,否则会造成系统的线程数目过多,导致大量的 context switch 开销。
由于 Raft 的日志不允许空洞,几乎所有的实现都会采用 Multi Raft Group 来缓解这个问题,因此 partition 的数目几乎决定了整个 Raft Group 的性能。但这也并不是说 Partition 的数目越多越好:每一个 Raft Group 内部都要存储一系列的状态信息,并且每一个 Raft Group 有自己的 WAL 文件,因此 Partition 数目太多会增加开销。此外,当 Partition 太多时, 如果负载没有足够高,batch 操作是没有意义的。比如,对于一个有 1万 TPS 的线上系统,即使它的每台机器上 partition 的数目超过 1万,但很有可能每个 partition TPS 只有 1,这样 batch 操作就失去了意义,还增加了 CPU 开销。

实现 Multi Raft Group 的最关键之处有两点,**第一是共享 Transport 层**,因为每一个 Raft Group 内部都需要向对应的 peer 发送消息,如果不能共享 Transport 层,连接的开销巨大;**第二是线程模型**,Multi Raft Group 一定要共享一组线程池,否则会造成系统的线程数目过多,导致大量的 context switch 开销。

### Batch

Expand All @@ -97,7 +103,7 @@

### Learner

Learner 这个角色的存在主要是为了 `应对扩容` 时,新机器需要"追"相当长一段时间的数据,而这段时间有可能会发生意外。如果直接以 follower 的身份开始追数据,就会使得整个集群的 HA 能力下降。 Nebula Graph 里面 learner 的实现就是采用了上面提到的 command wal,leader 在写 wal 时如果碰到 add learner 的 command, 就会将 learner 加入自己的 peers,并把它标记为 learner,这样在统计多数派的时候,就不会算上 learner,但是日志还是会照常发送给它们。当然 learner 也不会主动发起选举。
Learner 这个角色的存在主要是为了 `应对扩容` 时,新机器需要"追"相当长一段时间的数据,而这段时间有可能会发生意外。如果直接以 follower 的身份开始追数据,就会使得整个集群的 HA 能力下降。 Nebula Graph 里面 learner 的实现就是采用了上面提到的 command wal。 Leader 在写 wal 时如果碰到 add learner 的 command, 就会将 learner 加入自己的 peers,并把它标记为 learner,这样在统计多数派的时候,就不会算上 learner,但是日志还是会照常发送给它们。当然 learner 也不会主动发起选举。

### Transfer Leadership

Expand Down
Loading

0 comments on commit 9ccc784

Please sign in to comment.