Skip to content

NebulaGraph 与分布式数据库复习大纲:快照、事务与共识

约 5331 个字 1 张图片 预计阅读时间 27 分钟

使用方式

这篇文档不是教材版知识总结,而是面向数据库内核 / 分布式存储 / 图数据库岗位面试的复习提纲。整理方式沿用你仓库里现有的风格:

  • 先给一条总主线,明确面试官到底在追问什么。
  • 再把高频问题压成“能直接回答”的标准答法。
  • 最后补容易混淆、容易答错的边界条件。

本文事实部分优先基于截至 2026-03-08 仍可公开检索到的官方资料:NebulaGraph 文档、RocksDB 官方 wiki、MySQL 官方手册、Redis 官方文档、Raft/Paxos 原始论文。所谓“面经”主要用来归纳追问方式,不作为事实来源。

结论先看

这类面试的主线通常不是单点知识,而是下面这条链路:

写入路径 -> 索引维护 -> 日志复制 -> 快照/恢复 -> 事务隔离 -> 分布式提交 -> 故障切换

如果你只能回答“定义”,下一问大概率就会被问住。更稳的答法应该固定包含四层:

  1. 这个机制解决什么问题。
  2. 它依赖哪些数据结构或日志。
  3. 正常路径下如何工作。
  4. 崩溃、落后副本、扩容迁移时如何兜底。

一、NebulaGraph、RocksDB 与快照

1. 先把三个“snapshot”分开

这是最容易被问混的一组概念。

RocksDB Snapshot

  • sequence number 意义上的一致性读视图。
  • 它主要用于读隔离,不等于持久化备份。
  • 进程重启后,这个内存对象本身并不会以“备份文件”形式存在。

RocksDB Checkpoint

  • 是对当前 DB 状态做一份可落盘的、一致性的物理拷贝。
  • 底层通常复用 SST 文件并补齐必要元数据/WAL,适合备份和快速恢复。
  • 如果你被问“RocksDB 的快照怎么落盘”,更接近应该回答 Checkpoint,而不是 Snapshot

NebulaGraph Snapshot

  • NebulaGraph 文档里的 CREATE SNAPSHOT 是集群级别的备份能力。
  • 文档显示快照会出现在各服务的 checkpoints 目录下,Storage 的快照内容包含 datawal
  • 结合 NebulaGraph 底层存储是 RocksDB 这一事实,可以合理推断:NebulaGraph 的备份快照在存储层依赖的是 RocksDB 这类持久化 checkpoint 语义,而不是 RocksDB 的易失性 Snapshot 读视图。

Raft Snapshot / Install Snapshot

  • 这是复制协议层的概念,不是备份语义。
  • 作用是日志压缩和落后副本追赶。
  • 如果 follower 落后太多,缺失的日志已经被 leader 压缩掉,就不能只靠日志追平,而要先安装快照,再继续回放快照之后的新日志。

一句话区分:

  • RocksDB Snapshot 是读视图。
  • RocksDB Checkpoint 是持久化拷贝。
  • NebulaGraph Snapshot 是服务层面的备份快照。
  • Raft Snapshot 是复制层面的状态压缩与状态传输。

2. 问“快照和 RocksDB 的快照如何同步”时,推荐怎么答

推荐直接先纠偏:

NebulaGraph 里至少有两层快照概念,不能把备份快照、Raft 快照和 RocksDB Snapshot 当成一个东西。

然后按下面顺序回答:

  1. 如果问的是备份快照:
    - NebulaGraph 的 CREATE SNAPSHOT 会在某个时间点冻结出一份一致性状态。
    - 这份快照本质上对应那个时刻的持久化数据视图。
    - 快照创建之后的新写入不会“自动补到旧快照里”;它们属于快照之后的新状态。

  2. 如果问的是复制同步:
    - leader 和 follower 平时靠 Raft 日志同步。
    - 当 follower 落后过多、旧日志被压缩后,leader 会发送快照状态,再追加快照之后的日志。
    - 真正保证一致性的边界不是“两个快照文件看起来同时更新”,而是 last included index/term 或者等价的日志边界。

  3. 如果问的是 RocksDB 读视图:
    - RocksDB Snapshot 只是本地读一致性,不负责跨副本同步。
    - 跨节点同步依赖的是上层复制协议,不是 RocksDB Snapshot 对象本身。

3. 新数据插入后,索引数据和 RocksDB 如何同步

这题通常真正想问的是:数据行和索引项是不是分开写,如何保证一致。

推荐答法:

  • NebulaGraph 的底层存储是 RocksDB。
  • 如果索引已经存在,那么一条写入在进入存储层时,不只是写“主数据 KV”,还会同时生成对应的“索引 KV”。
  • 这些修改会作为同一次状态机应用的一部分写入 RocksDB,因此语义上是原子更新的,而不是“先写数据文件,再异步补索引文件”。
  • 如果系统使用 Raft 复制,那么 leader 复制的是这次状态变更对应的日志;各副本按相同顺序应用,同样会得到一致的数据 KV 和索引 KV。

面试时最好主动补一句:

这里不要把“索引文件”和“数据文件”理解成两套独立的关系型文件格式。在 RocksDB/LSM 语境里,它们最终都会落到同一个 KV 存储系统管理的 memtable、WAL、SST 和 compaction 流程里。

4. 如果是“先有数据,后建索引”,怎么保证一致

这是 NebulaGraph 非常容易追问的一点。

  • 新建原生索引后,历史数据不会自动立刻变成可索引状态。
  • 官方文档要求显式执行 REBUILD TAG INDEXREBUILD EDGE INDEX
  • 原因很直接:旧数据写入时根本没有生成对应索引项,所以必须做一次回填扫描与重建。

因此最稳的回答是:

  • 索引先存在:新写入会同步维护数据 KV 和索引 KV。
  • 索引后创建:历史数据要靠 REBUILD INDEX 回填;重建完成后,后续增量写入再持续同步维护。

5. “新的数据插入后,索引文件和快照文件如何同步”该怎么说

推荐直接拆成两句话:

  • 在线写入时,系统维护的是“数据 KV 和索引 KV”的一致性,不是“把正在使用的快照文件也同步改掉”。
  • 快照是某个时点冻结出来的结果;快照生成之后的新数据不会写回旧快照,而是体现在后续日志、WAL、memtable、SST 中。需要新的备份点,就再创建新的 snapshot。

如果继续追问副本恢复:

  • 节点恢复时可以从最近快照开始装载状态。
  • 再回放快照之后的增量日志,追到最新。
  • 这和数据库恢复里的“checkpoint + redo”是同一类思想。

6. NebulaGraph 相关高频短答

NebulaGraph 为什么适合讲 Raft

  • Meta Service 本身基于 Raft。
  • Storage Service 的分区副本组也基于 Raft。
  • 所以它天然适合被问到 leader、follower、日志复制、快照安装、成员变更、落后副本追赶。

图数据库里索引是不是越多越好

  • 不是。
  • 索引会增加写放大、空间开销、compaction 压力和 rebuild 成本。
  • 只给高选择性、真实查询路径会走到的 tag/edge 属性建索引。

二、MySQL 的工作流程和原理

1. MySQL 一条 SQL 的典型执行路径

推荐按下面顺序回答:

  1. 连接器处理认证、权限、会话状态。
  2. SQL 进入解析器,做词法分析和语法分析。
  3. 预处理器检查表、列、别名等是否合法。
  4. 优化器选择执行计划,比如走哪个索引、连接顺序怎么排。
  5. 执行器调用存储引擎接口。
  6. InnoDB 再去做 buffer pool、锁、MVCC、undo/redo、页管理和持久化。

一句话总结:

MySQL 是“Server 层 + 存储引擎层”分层架构,很多面试官其实是想确认你能不能把 SQL 执行InnoDB 事务/存储 两层讲清楚。

2. InnoDB 写入路径怎么讲

插入或更新一行数据时,可以按这个顺序描述:

  1. 先在 Buffer Pool 中定位或加载目标页。
  2. 修改内存页。
  3. 生成 undo log,支撑回滚和 MVCC。
  4. 生成 redo log,支撑崩溃恢复。
  5. 事务提交时按提交协议刷日志。
  6. 脏页稍后异步刷回数据文件,不要求提交时立刻落盘。

这套设计本质上就是 WAL

  • 提交时先保证日志足够可靠。
  • 数据页可以延迟刷盘。
  • 崩溃恢复时靠 redo 把已提交但未落盘的修改补回来。

3. redo log 和 binlog 如何协作实现事务提交

推荐答法直接说“内部两阶段提交”:

  1. InnoDB 先把事务的 redo 写到 prepare 状态。
  2. Server 层写 binlog,并在需要时刷盘。
  3. binlog 成功后,再把 redo 标记为 commit

这样做是为了避免下面两种不一致:

  • 只写了 redo,没写 binlog:主从复制和增量恢复会丢事务。
  • 只写了 binlog,没写 redo:崩溃恢复后本地数据没有这个事务。

恢复时的判断逻辑:

  • 如果 redo 里是完整提交状态,直接认为事务已提交。
  • 如果 redo 只有 prepare,则结合 binlog 判断这个事务是否应该补提交。

这就是面试里最常见的“为什么要两阶段提交”。

三、事务、MVCC 与隔离级别

1. 事务介绍应该怎么起手

建议不要只背 ACID,最好补实现:

  • Atomicity 主要靠 undo/回滚。
  • Consistency 依赖约束、应用逻辑和事务机制共同保证。
  • Isolation 依赖锁、MVCC、读视图和序列化控制。
  • Durability 依赖 redo/WAL 和刷盘策略。

再补一句:

InnoDB 的事务实现核心不是单一机制,而是 锁 + MVCC + undo + redo + read view 的组合。

2. A 未提交 update,B 在 RC 下普通 select,会怎么样

场景:

  • 会话 A:UPDATE ...,但还没提交。
  • 会话 B:在 READ COMMITTED 下执行普通 SELECT

标准回答:

  • B 不会读到 A 的未提交数据。
  • B 通常也不会被 A 阻塞。
  • B 会读到该行最近一次已提交版本,也就是旧版本。

原因:

  • 普通 SELECT 在 InnoDB 里是快照读。
  • RC 下每条语句创建自己的 read view。

3. 如果 B 用的是 SELECT ... FOR UPDATE,会怎么样

这时就不是快照读,而是当前读。

  • B 会尝试给目标记录加锁。
  • 如果 A 已经持有冲突锁,B 会等待 A 提交或回滚。
  • 等待结束后,B 才能继续读取并锁定最新版本。

一句话区分:

  • 普通 SELECT 是快照读。
  • SELECT ... FOR UPDATE / LOCK IN SHARE MODE 这类是当前读。

4. RC 和 RR 下,B 做 update 时加锁有什么区别

这是 InnoDB 高频题,推荐抓住“gap lock 是否普遍启用”。

RC

  • 以记录锁为主。
  • gap lock 在 RC 下通常关闭。
  • 例外主要是外键检查、唯一键冲突检查等场景。
  • 对不满足条件的记录,锁释放得更积极。

RR

  • 更常使用 next-key lock = record lock + gap lock
  • 对范围扫描、范围更新尤其明显。
  • 目的是防止幻读,让同一事务内的当前读范围更稳定。

面试里的稳妥补充:

  • 如果是唯一索引等值命中单行,RC 和 RR 都可能只锁记录本身,差异没那么大。
  • 真正拉开差距的是范围条件、非唯一索引条件和是否需要防幻读。

5. RC 和 RR 的一句话差异怎么背

  • RC:每条语句看当前最新已提交版本,重点防脏读。
  • RR:同一事务内第一次一致性读确定视图,后续快照读保持一致,配合 next-key lock 进一步处理幻读问题。

四、Raft、Paxos、2PC、3PC、Multi-Raft

1. Raft 是什么

一句话:

  • Raft 是一种崩溃故障模型下的复制状态机一致性协议。
  • 它通过强领导者、任期、日志复制和多数派提交来保证副本状态一致。

2. Paxos 和 Raft 有什么区别

推荐这样回答:

  • 两者都能在 crash-stop 故障模型下达成多数派一致。
  • Paxos 理论更早、更抽象,工程上常见变体多,理解门槛高。
  • Raft 把 leader 选举、日志复制、成员变更拆得更清楚,可解释性更强,工程实现更友好。

如果被问更倾向哪个:

  • 做工程系统时,我通常更倾向 Raft/Multi-Raft,因为更容易实现、调试和向团队解释。
  • 如果已有成熟 Paxos 体系或需要某些特定变体能力,再考虑 Paxos 家族方案。

3. 2PC 和 3PC 怎么讲

2PC

  • 第一阶段 prepare:协调者问所有参与者能不能提交。
  • 第二阶段 commit/abort:协调者收齐结果后统一决定。

优点:

  • 逻辑简单,是分布式事务最常见骨架。

缺点:

  • 协调者失败会阻塞。
  • 单靠 2PC 不能解决协调者自身状态丢失的问题,所以工业实现常把协调者日志也持久化,甚至复制化。

3PC

  • 在 2PC 上增加 pre-commit 一类中间阶段,试图降低阻塞。
  • 但它依赖更强的网络与超时假设。
  • 在真实存在网络分区、时钟漂移和复杂故障的环境里,3PC 并不常作为主流工业答案。

面试里最稳的话术:

工业系统里,跨分片事务常是 2PC 思想,但底层参与者日志和协调者状态通常会放在 Raft/Paxos 复制之上,而不是裸奔 2PC。

4. Multi-Raft 是什么

  • 不是“更高级的一个 Raft 协议”,而是“很多个独立的 Raft Group 并行运行”。
  • 每个分片、分区、tablet、region 通常对应一个 Raft Group。
  • 这样才能横向扩展,不可能让整个数据库所有数据共享一条全局日志。

5. 分片系统达成的共识是全局的,还是 group 内的

这个问题非常容易答错。

更准确的答案是:

  • 大多数分布式数据库的复制共识是 group 内 的,不是“全局一个 Raft”。
  • 每个分片副本组内部对该分片的数据日志达成共识。
  • 元数据服务可能自己也有一个单独的共识组。
  • 真正跨分片的一致性,要靠上层事务协议、时间戳服务或者协调器来组合,不是让全局所有数据共用一个日志。

所以如果面试官追问“全局一致性怎么来”,就要继续讲:

  • 分片内靠 Raft。
  • 跨分片靠 2PC / timestamp ordering / certifier / 全局事务管理器。

五、分布式事务与场景题

1. 分布式事务原理和实践怎么答

先分成单分片和跨分片:

单分片事务

  • 如果数据只落在一个副本组里,本质上还是本地事务。
  • 提交后再通过该分片自己的复制协议把日志复制出去。

跨分片事务

  • 每个参与分片先执行本地事务并进入 prepare。
  • 协调者收齐后决定 commit 或 abort。
  • 各分片分别提交自己的本地日志和状态。

面试里最好再补三个工程点:

  • 需要幂等提交/回滚,避免重试时重复执行。
  • 需要超时清理和恢复流程,处理协调者或参与者宕机。
  • 需要把事务状态持久化,否则恢复后不知道事务是否该提交。

2. 脑裂怎么解释

  • 脑裂本质上是系统把两个节点都当成合法主节点,导致双写。
  • Raft/Paxos 通过多数派和任期机制尽量避免双主。
  • 真正工程上还会加 fencing token / epoch,防止旧主在网络恢复后继续写。

3. 数据回放、重复执行怎么防

  • 复制日志回放要求状态机确定性。
  • 客户端重试可能带来重复请求,所以需要 request id / sequence id 去重。
  • 对外暴露写接口时,幂等设计非常关键。

4. 分片怎么做

最常见两种:

  • 按哈希分片:均摊更容易,适合 key-value / OLTP 主键访问。
  • 按范围分片:范围查询友好,但更容易热点集中。

5. 数据在哪个分片,怎么确定

  • 先根据分片键做哈希或范围映射,得到逻辑分片 ID。
  • 再通过元数据服务,把逻辑分片 ID 映射到具体副本组和节点。

6. 迁移是改了哈希结果吗

更好的回答是:

  • 通常不是直接改哈希函数。
  • 更常见做法是把“哈希到逻辑分片”的规则保持稳定,只调整“逻辑分片到物理节点”的映射关系。
  • 这样迁移成本更可控,也便于做虚拟分片、bucket、tablet 级别重平衡。

7. 分片负载均衡策略怎么答

不要只说“均分”。

更完整的说法是综合考虑:

  • 数据量是否均匀。
  • QPS / 吞吐是否均匀。
  • 热点分片是否需要拆分或迁移。
  • leader 是否分布均衡。
  • 节点磁盘、CPU、网络带宽是否均衡。

8. 持久化了什么

Raft 语境下,至少要持久化:

  • currentTerm
  • votedFor
  • 已提交和未提交的日志
  • 快照元数据,例如 lastIncludedIndex/Term

数据库本身还要持久化:

  • 状态机数据
  • WAL / redo
  • checkpoint 或 snapshot

9. 快照压缩怎么讲

  • 当日志前缀已经反映到状态机里,就没必要永远保留全部旧日志。
  • 系统会把当前状态机导出成快照。
  • 快照成功后,可以丢弃快照点之前的大量历史日志,只保留之后的增量。

这本质上就是:

  • 状态快照 + 少量新日志 代替 无限增长的全量历史日志

10. 节点恢复时为什么需要 Install Snapshot

  • 如果一个节点刚加入,或者落后太久,缺失日志太多。
  • 这时直接从头 replay 可能极慢,甚至缺失日志已经被删除。
  • leader 会把某个快照发给它,节点先装载快照,再追平后续日志。

六、拜占庭问题、Redis 与常见追问

1. 拜占庭问题怎么解释

  • 拜占庭问题假设节点不只是宕机,还可能撒谎、作恶、发不同消息给不同节点。
  • Raft/Paxos 默认处理的是 crash-stop/crash-recovery,不处理拜占庭恶意行为。
  • 如果系统要抗拜占庭,需要 PBFT、HotStuff 一类 BFT 协议,成本更高。

2. Redis 一致性也是通过 Raft 实现的吗

默认答案:不是。

  • 经典 Redis 主从复制是异步复制。
  • Redis Sentinel 负责故障发现和自动切换,但不是 Raft。
  • Redis Cluster 也不是基于 Raft 做默认数据复制一致性。
  • 如果被追问例外,可以补充有 RedisRaft 这样的模块化方案,但那不是默认 Redis 的标准复制路径。

3. Raft 对比 Paxos 的优缺点

Raft 优点

  • 角色和流程更清晰。
  • 强 leader 模型更便于日志复制和排障。
  • 工程落地和团队协作成本更低。

Raft 缺点

  • 强 leader 容易形成热点。
  • 设计空间相对保守,一些更激进的优化不如某些 Paxos 变体灵活。

Paxos 优点

  • 理论完备,变体丰富。
  • 在某些定制场景下更灵活。

Paxos 缺点

  • 原始表述更抽象。
  • 许多工程团队实现和维护成本更高。

七、面试时最容易答错的点

1. 不要把所有 snapshot 混为一谈

  • 读视图不是备份。
  • 备份快照不是 Raft 快照。
  • Raft 快照不是“每次写入都同步更新的一份静态文件”。

2. 不要把分布式数据库说成“全局一个共识”

  • 共识通常发生在分片副本组内部。
  • 全局一致性通常是上层事务协议拼出来的。

3. 不要把 RC 和 RR 的差别只说成“一个可重复读,一个不可重复读”

更好的答法是:

  • RC 每条语句生成新 read view。
  • RR 在事务级维持一致性视图,并在当前读场景更积极使用 gap/next-key lock。

4. 不要把 Redis 默认复制说成 Raft

  • 这是非常常见的口误。
  • 讲清楚“默认复制不是,某些扩展/模块可以是”就够了。

八、30 秒速答模板

1. NebulaGraph 的快照和 RocksDB 快照如何同步

NebulaGraph 这里至少有三层概念:RocksDB Snapshot 是读视图,RocksDB Checkpoint 才是持久化拷贝,NebulaGraph Snapshot 是服务层备份;副本追赶时用的又是 Raft Snapshot。真正保证一致性的不是“两个快照文件同步改”,而是日志边界和状态机应用顺序。

2. 新数据插入后,索引和数据如何同步

如果索引已经存在,写入时会同时生成数据 KV 和索引 KV,并作为同一次状态机更新落到 RocksDB;如果索引是后来创建的,历史数据要靠 REBUILD INDEX 回填。

3. A 未提交 update,B 在 RC 下 select 会怎样

普通 SELECT 看到旧的已提交版本,不阻塞;SELECT ... FOR UPDATE 是当前读,会等待 A 释放锁。

4. RC 和 RR 下 update 加锁差异

RC 主要是记录锁,gap lock 通常关闭;RR 更常用 next-key lock,尤其是范围条件下,用来抑制幻读。

5. 分片系统是全局共识吗

通常不是。每个 shard/partition 一个 Raft Group,跨分片一致性靠上层事务协议或协调器。

九、参考资料