tikv学习4etcd2
当然可以,以下是一个完整、结构清晰、覆盖面最全的 etcd 数据更新、持久化与加载机制的详细说明文档,涵盖了所有你关心的内容,包括数据是如何在“树”上更新的、如何持久化、是否全部加载到内存,以及其加载机制,附带流程图和关键函数参考:
📘 etcd 数据更新、持久化与加载机制详解(最全总结版)
一、数据在“树”结构上的更新
1.1 etcd 核心结构
etcd 内部将所有 key-value 数据以 Trie(前缀树)结构 + MVCC(多版本控制) 方式组织管理。
-
Trie:前缀树用于构建 key 的层级结构,例如
/foo/bar会插入到/foo下。 -
MVCC:每个 key 在 Trie 树中对应一个版本列表(通过 revision 管理历史)。
数据更新通过 mvcc.Store 完成,核心方法是 Put() 或 Txn()。
1.2 树上更新流程(文本流程图)
Client PUT /foo/bar
|
v
┌───────────────┐
│ raft.Propose()│ <- Leader 发起写提案
└──────┬────────┘
|
v
┌────────────────────────────┐
│ raft log replication │ <- 多数派复制日志
└──────┬─────────────────────┘
|
v
┌──────────────────────────────────────────────┐
│ apply log entry │
│ └── applierV3.Put() │
│ └── mvcc.Store.TxnWrite() │
│ ├── store.write() │
│ │ └── index.Put() │ <- Trie 插入/更新
│ └── kvindex.Put() │ <- 内存索引映射更新
│ └── record revision │ <- 写入历史版本控制
└──────┬───────────────────────────────────────┘
|
+-------------------> backend.BatchTx.Put() <- 写入 BoltDB(持久化)
|
+-------------------> wal.Save() <- 写入 WAL 日志(预写日志)
二、写入持久化机制:WAL + backend(BoltDB)
2.1 WAL(Write-Ahead Log)
-
所有写入都会首先写入 WAL。
-
存储内容包括:entry、term、index、请求数据。
-
作用:
-
提供崩溃恢复能力;
-
保证数据顺序;
-
是 Raft 日志持久化核心。
-
2.2 backend(BoltDB)
-
等待 Raft 共识成功后,提交数据会写入 backend。
-
backend 是基于 BoltDB 的嵌入式事务性存储,采用 mmap 映射。
-
写入方式:
backend.BatchTx.Put(bucket, key, value)
三、数据加载机制和内存占用
3.1 是否全部加载到内存?
❌ 不是全部加载。
etcd 并不会将所有数据一次性加载到内存,只会将热点数据保留在内存(通过索引 + OS 页缓存优化)。冷数据访问时触发 page fault 加载磁盘页。
3.2 加载机制详解
-
BoltDB mmap:backend 使用 mmap,将 BoltDB 页映射到内存。
-
访问机制:访问数据时通过 B+Tree 查询页位置 → 页不在内存则由 OS 加载。
-
Trie 和 kvindex:内存中维护 key 的索引结构,用于快速定位 backend 中的物理位置。
-
热点数据缓存:系统自动将常用页面保留在 OS page cache。
四、快照与压缩(Compact)
4.1 Compact 机制
-
etcd 支持版本控制,但长期积累会导致 Trie 和 BoltDB 增大。
-
mvcc.Store.Compact()可压缩旧版本,仅保留最新值。 -
支持配置自动 compact:如
--auto-compaction-retention=1(1小时保留)。
4.2 快照与恢复
-
etcd 支持快照(snapshot),定期将状态写入快照,减少重启时的日志 replay 时间。
-
启动时恢复流程:
-
加载 snapshot(状态快照)
-
Replay WAL 日志恢复最新状态
-
五、关键模块与函数参考表
| 层级 | 结构/函数 | 功能说明 |
|---|---|---|
| 客户端处理 | v3rpc.Put() |
处理来自 client 的 Put 请求 |
| Raft 提案 | raftNode.Propose() |
向 Raft 集群提交提案 |
| 日志应用 | applierV3.Put() |
处理 Raft 日志 entry,提交至 MVCC |
| Trie 更新 | index.Put() |
更新 key 的前缀树结构 |
| 索引更新 | kvindex.Put() |
更新内存中的 revision 索引 |
| 数据写入 backend | backend.BatchTx.Put() |
写入 BoltDB 中持久化存储 |
| WAL 写入 | wal.Save() |
记录写操作到预写日志 |
| 数据压缩 | store.Compact() |
清理旧版本,减少 backend 文件大小 |
| 启动恢复 | recoverSnapshot() + replayWAL() |
通过快照 + WAL 重建内存状态 |
六、完整总结表
| 主题 | 内容说明 |
|---|---|
| 树结构更新 | 使用 Trie 前缀树插入更新键,结合 MVCC 版本控制 |
| 写入如何持久化 | WAL 写入预日志,backend 写入状态机数据,WAL 提供崩溃恢复、backend 提供读取性能 |
| 数据是否全加载内存 | ❌ 不全部加载,只缓存索引和部分热数据,依赖 mmap 和 OS 页缓存 |
| 如何加载冷数据 | 使用 BoltDB 的 B+Tree 索引定位页面,若页不在内存则触发 page fault 加载 |
| 如何保障一致性与可靠性 | 依赖 Raft 多数共识后再 apply + 写 WAL,具备强一致性和故障恢复能力 |
| 如何避免数据膨胀 | 使用定期 compact 保留有限版本,同时使用 snapshot 减少恢复时的 replay 开销 |
| 重启恢复流程 | 快照加载 → WAL replay → 内存结构恢复完整数据状态 |
如果你还需要这个内容导出为 Markdown 文档、PDF 报告、演示文稿 或嵌入你已有的架构说明文档,我都可以继续帮你生成。
是否还需我继续补充比如 watch/lease 等部分?还是你打算拿这份文档直接使用?
好的,下面是 etcd 中 Trie(前缀树)结构详细更新流程 的 文本流程图(Text-based flowchart),聚焦于 mvcc.index 中 Trie 的插入与更新过程,这是 etcd 在内存中维护 key 的索引结构的核心逻辑。
📍etcd Trie(前缀树)更新流程图(index.Put)
当执行 mvcc.Store.Put() 或 Txn() 时,涉及如下流程中的 index.Put():
mvcc.Store.Put(key, value)
|
v
store.write(key, rev)
|
v
┌────────────────────────────┐
│ kvindex.Put(key, rev) │ <- 树结构更新
│ └── index.Put() │
│ ├─ split key by '/'│ <- 解析路径为层级节点
│ │ e.g. /a/b/c │ --> [a, b, c]
│ ├─ Traverse levels │
│ │ ├─ check if child exists
│ │ └─ create node if absent
│ ├─ Set revision │ <- 设置当前层节点的修改版本
│ └─ Store pointer │ <- 叶子节点绑定实际 key 记录
└────────────────────────────┘
🔍 举例说明
以 key /foo/bar/baz 为例,插入到前缀树的步骤如下:
-
拆分路径为:
["foo", "bar", "baz"] -
检查根节点是否有
"foo":-
如果没有,则创建
"foo"子节点。
-
-
在
"foo"下找"bar":-
如果没有,则创建
"bar"节点。
-
-
在
"bar"下找"baz":-
创建
"baz"节点,并将其视为叶子节点,绑定版本信息、对应 key 的 revision。
-
-
每个节点上记录该路径下的最近修改版本(用于 prefix 查询与 compact)。
🧠 结构数据模型(内存结构)
type keyIndex struct {
key []byte
modified revision // 最近修改版本
generations []generation
}
type generation struct {
ver int64 // 当前版本号
revs []revision // 所有版本
}
type treeNode struct {
pathPart string
children map[string]*treeNode
keyIndex *keyIndex // 仅叶子节点维护
}
🧩 Trie 特性总结
| 特性 | 描述 |
|---|---|
| 层级结构 | 以路径为层级,如 /foo/bar/baz 拆成 foo -> bar -> baz |
| 内存高效 | 共用前缀节点,节省空间 |
| 快速定位 | key 更新时仅需插入/更新路径节点 |
| 支持 prefix 查询 | 可快速查找某一前缀下所有 key,例如 List("/foo") |
| 支持 Compact 与清理 | 随着 compact 操作,旧版本的叶子节点会被从树中移除 |
如果你还想了解具体实现文件和函数位置(比如 index.go 中的实现),我也可以给你列出来。是否需要我继续补图或转 Markdown 文档?
浙公网安备 33010602011771号