在用户使用 Nebula Graph 的过程中,经常会遇到各种问题,通常我们都会建议先通过 show hosts 查看集群状态。可以说,整个 Nebula Graph 的集群状态都是靠心跳机制来构建的。本文将从心跳说起,帮助你了解 Nebula Graph 集群各个节点之间通信的机制。

什么是心跳?有什么作用?

metad storaged graphd 通信

Nebula Graph 集群一般包含三种节点,graphd 作为查询节点,storaged 作为存储节点,metad 作为元信息节点。本文说的心跳,主要是指 graphd 和 storaged 定期向 metad 上报信息的这个心跳,借助心跳,整个集群完成了以下功能。(相关参数是 heartbeat_interval_secs

在 Nebula Graph 中经常提及的 raft 心跳则是用于拥有同一个 partition 的多个 storaged 之间的心跳,和本文提的心跳并不相同。

1. 服务发现

当我们启动一个 Nebula Graph 集群时,需要在对应的配置文件中填写 meta_server_addrs。graphd 和 storaged 在启动之后,就会通过这个 meta_server_addrs 地址,向对应的 metad 发送心跳。通常来说,graphd 和 storaged 在连接上 metad 前是无法对外进行服务的。当 metad 收到心跳后,会保存相关信息(见下文第 2 点),此时就能够通过 show hosts 看到对应的 storaged 节点,在 2.x 版本中,也能够通过 show hosts graph 看到 graphd 节点。

2. 上报节点信息

在 metad 收到心跳时,会将心跳中的 ip、port、节点类型、心跳时间等等信息保存,以供后续使用(见下文)。

除此以外 storaged 在自身 leader 数量变化的时候也会上报 leader 信息,在 show hosts 中看到的 Leader count 和 Leader distribution 就是通过心跳汇报的。

3. 更新元信息

当客户通过 console 或者各种客户端,对集群的元信息进行更改之后(例如 create/drop spacecreate/alter/drop tag/edgeupdate configs 等等),通常在几秒之内,整个集群就都会更新元数据。

每次 graphd 和 storaged 在心跳的响应中会包含一个 last_update_time,这个时间是由 metad 返回给各个节点的,用于告知 metad 自身最后一次更新元信息的时间。当 graphd 或者 storaged 发现 metad 的元信息有更新,就会向 metad 获取相应信息(例如 space 信息、schema 信息、配置更改等等)。

我们以创建一个 tag 为例,如果在 graphd/storaged 获取到新创建的这个 tag 信息之前,我们无法插入这个 tag 数据(会报类似 No schema found 这样的错误)。而当通过心跳获取到对应信息并保存至本地缓存后,就能够正常写入数据了。

心跳上报的信息有什么用?

  • how hostsshow parts 这类命令都是通过 metad 中保存的各个节点心跳信息,组合显示出来的。
  • balance databalance leader 等运维命令,需要通过获取当前集群内哪些 storaged 节点是在线状态,实际也是通过 metad 判断最近一次心跳时间是否在阈值之内。
  • create space,当用户创建一个 space 时,metad 也需要获取 storaged 的状态,将这个 space 的各个 partition 分配到在线的 storaged 中。

以用户容易遇到的问题为例:假如我们启动一个 storaged 后,关掉并修改端口号,然后再启动 storaged。如果这个过程足够快,那么通过 show hosts 能看到两个在线的 storaged。此时,如果新建一个 space,例如 CREATE space test(partition_num=10, replica_factor=1),这个 test space 就会分布在前后启动的两个 storage 上。但如果等到在 show hosts 中看到其中一个离线后,再执行 CREATE space test(partition_num=10, replica_factor=1),即便离线的 storaged 再启动,也只有一个 storaged 拥有这个 space(创建 test space 时 online 的那个 storaged)。

上线

掉线

心跳的演变历史

在 18-19 年的时候,当时的心跳机制没有这么完善。一方面,无论元信息是否更改,都会从 metad 获取最新的元信息。而通常来说,元信息改动不会很频繁,定期获取元信息有一定的资源浪费。另一方面,想要将一个 storaged 节点加入和移除都是通过类似 add/delete hosts 这样的命令,采取的是类似白名单的机制。对于其他没有认证过的节点,都无法对外服务,这样做固然也有一些优势,带来的最大问题就是不够友好。

因此,在 19 年底开始,我们对心跳做了一系列的改动,特别鸣谢社区用户 @zhanggguoqing。经过一段时间的验证踩坑后,基本就形成了现在的形式。

额外的补充

有关心跳还有一个涉及到的问题就是 cluster.id 这个文件。它实际是为了防止 storaged 与错误的 metad 通信,大致原理如下:

  • 首先,metad 在启动的时候会根据 meta_server_addrs 这个参数,生成一个 hash 值并保存在本地 kv 中。
  • storaged 在启动的时候会尝试从 cluster.id 这个文件中获取对应 metad 的 hash 值,并附送在心跳中发送(如果文件不存在,则第一次使用 0 代替。收到心跳响应时,将 metad 的 hash 值保存在 cluster.id 这个文件中,后续一直使用该值)。
  • 在心跳处理中,metad 会比较本地 hash 值和来自 storaged 心跳请求中的 hash 值,如果不匹配则拒绝。此时,storaged 是无法对外服务的,也就是 Reject wrong cluster host 这个日志的由来。

以上就是心跳机制大致的介绍,感兴趣的你可以参考下源码实现,GitHub 传送门:https://github.com/vesoft-inc/nebula-graph