NebulaGraph 与分布式数据库复习大纲:快照、事务与共识¶
约 5331 个字 1 张图片 预计阅读时间 27 分钟
使用方式¶
这篇文档不是教材版知识总结,而是面向数据库内核 / 分布式存储 / 图数据库岗位面试的复习提纲。整理方式沿用你仓库里现有的风格:
- 先给一条总主线,明确面试官到底在追问什么。
- 再把高频问题压成“能直接回答”的标准答法。
- 最后补容易混淆、容易答错的边界条件。
本文事实部分优先基于截至 2026-03-08 仍可公开检索到的官方资料:NebulaGraph 文档、RocksDB 官方 wiki、MySQL 官方手册、Redis 官方文档、Raft/Paxos 原始论文。所谓“面经”主要用来归纳追问方式,不作为事实来源。
结论先看¶
这类面试的主线通常不是单点知识,而是下面这条链路:
写入路径 -> 索引维护 -> 日志复制 -> 快照/恢复 -> 事务隔离 -> 分布式提交 -> 故障切换
如果你只能回答“定义”,下一问大概率就会被问住。更稳的答法应该固定包含四层:
- 这个机制解决什么问题。
- 它依赖哪些数据结构或日志。
- 正常路径下如何工作。
- 崩溃、落后副本、扩容迁移时如何兜底。
一、NebulaGraph、RocksDB 与快照¶
1. 先把三个“snapshot”分开¶
这是最容易被问混的一组概念。
RocksDB Snapshot¶
- 是
sequence number意义上的一致性读视图。 - 它主要用于读隔离,不等于持久化备份。
- 进程重启后,这个内存对象本身并不会以“备份文件”形式存在。
RocksDB Checkpoint¶
- 是对当前 DB 状态做一份可落盘的、一致性的物理拷贝。
- 底层通常复用 SST 文件并补齐必要元数据/WAL,适合备份和快速恢复。
- 如果你被问“RocksDB 的快照怎么落盘”,更接近应该回答
Checkpoint,而不是Snapshot。
NebulaGraph Snapshot¶
- NebulaGraph 文档里的
CREATE SNAPSHOT是集群级别的备份能力。 - 文档显示快照会出现在各服务的
checkpoints目录下,Storage 的快照内容包含data和wal。 - 结合 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 当成一个东西。
然后按下面顺序回答:
-
如果问的是备份快照:
- NebulaGraph 的CREATE SNAPSHOT会在某个时间点冻结出一份一致性状态。
- 这份快照本质上对应那个时刻的持久化数据视图。
- 快照创建之后的新写入不会“自动补到旧快照里”;它们属于快照之后的新状态。 -
如果问的是复制同步:
- leader 和 follower 平时靠 Raft 日志同步。
- 当 follower 落后过多、旧日志被压缩后,leader 会发送快照状态,再追加快照之后的日志。
- 真正保证一致性的边界不是“两个快照文件看起来同时更新”,而是last included index/term或者等价的日志边界。 -
如果问的是 RocksDB 读视图:
- RocksDBSnapshot只是本地读一致性,不负责跨副本同步。
- 跨节点同步依赖的是上层复制协议,不是 RocksDB Snapshot 对象本身。
3. 新数据插入后,索引数据和 RocksDB 如何同步¶
这题通常真正想问的是:数据行和索引项是不是分开写,如何保证一致。
推荐答法:
- NebulaGraph 的底层存储是 RocksDB。
- 如果索引已经存在,那么一条写入在进入存储层时,不只是写“主数据 KV”,还会同时生成对应的“索引 KV”。
- 这些修改会作为同一次状态机应用的一部分写入 RocksDB,因此语义上是原子更新的,而不是“先写数据文件,再异步补索引文件”。
- 如果系统使用 Raft 复制,那么 leader 复制的是这次状态变更对应的日志;各副本按相同顺序应用,同样会得到一致的数据 KV 和索引 KV。
面试时最好主动补一句:
这里不要把“索引文件”和“数据文件”理解成两套独立的关系型文件格式。在 RocksDB/LSM 语境里,它们最终都会落到同一个 KV 存储系统管理的 memtable、WAL、SST 和 compaction 流程里。
4. 如果是“先有数据,后建索引”,怎么保证一致¶
这是 NebulaGraph 非常容易追问的一点。
- 新建原生索引后,历史数据不会自动立刻变成可索引状态。
- 官方文档要求显式执行
REBUILD TAG INDEX或REBUILD 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 的典型执行路径¶
推荐按下面顺序回答:
- 连接器处理认证、权限、会话状态。
- SQL 进入解析器,做词法分析和语法分析。
- 预处理器检查表、列、别名等是否合法。
- 优化器选择执行计划,比如走哪个索引、连接顺序怎么排。
- 执行器调用存储引擎接口。
- InnoDB 再去做 buffer pool、锁、MVCC、undo/redo、页管理和持久化。
一句话总结:
MySQL 是“Server 层 + 存储引擎层”分层架构,很多面试官其实是想确认你能不能把 SQL 执行 和 InnoDB 事务/存储 两层讲清楚。
2. InnoDB 写入路径怎么讲¶
插入或更新一行数据时,可以按这个顺序描述:
- 先在 Buffer Pool 中定位或加载目标页。
- 修改内存页。
- 生成 undo log,支撑回滚和 MVCC。
- 生成 redo log,支撑崩溃恢复。
- 事务提交时按提交协议刷日志。
- 脏页稍后异步刷回数据文件,不要求提交时立刻落盘。
这套设计本质上就是 WAL:
- 提交时先保证日志足够可靠。
- 数据页可以延迟刷盘。
- 崩溃恢复时靠 redo 把已提交但未落盘的修改补回来。
3. redo log 和 binlog 如何协作实现事务提交¶
推荐答法直接说“内部两阶段提交”:
- InnoDB 先把事务的 redo 写到
prepare状态。 - Server 层写 binlog,并在需要时刷盘。
- 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 语境下,至少要持久化:
currentTermvotedFor- 已提交和未提交的日志
- 快照元数据,例如
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,跨分片一致性靠上层事务协议或协调器。
九、参考资料¶
- NebulaGraph Storage engine: https://docs.nebula-graph.io/3.4.0/1.introduction/3.nebula-graph-architecture/4.storage-engine/
- NebulaGraph Create native index: https://docs.nebula-graph.io/3.8.0/3.ngql-guide/14.native-index-statements/1.create-native-index/
- NebulaGraph Rebuild native index: https://docs.nebula-graph.io/3.8.0/3.ngql-guide/14.native-index-statements/3.rebuild-native-index/
- NebulaGraph Manage snapshots: https://docs.nebula-graph.io/3.4.0/6.deploy-and-scenarios/6.monitor-and-metrics/4.snapshot/
- RocksDB Snapshots: https://github.com/facebook/rocksdb/wiki/Snapshots
- RocksDB Checkpoints: https://github.com/facebook/rocksdb/wiki/Checkpoints
- MySQL InnoDB consistent nonlocking reads: https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html
- MySQL InnoDB locking reads: https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html
- MySQL transaction isolation levels: https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html
- MySQL InnoDB redo log: https://dev.mysql.com/doc/refman/8.0/en/innodb-redo-log.html
- MySQL binary log: https://dev.mysql.com/doc/refman/8.0/en/binary-log.html
- Redis replication: https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
- Raft paper: https://raft.github.io/raft.pdf
- Paxos Made Simple: https://lamport.azurewebsites.net/pubs/paxos-simple.pdf