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 时间。

  • 启动时恢复流程:

    1. 加载 snapshot(状态快照)

    2. 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 为例,插入到前缀树的步骤如下:

  1. 拆分路径为:["foo", "bar", "baz"]

  2. 检查根节点是否有 "foo"

    • 如果没有,则创建 "foo" 子节点。

  3. "foo" 下找 "bar"

    • 如果没有,则创建 "bar" 节点。

  4. "bar" 下找 "baz"

    • 创建 "baz" 节点,并将其视为叶子节点,绑定版本信息、对应 key 的 revision。

  5. 每个节点上记录该路径下的最近修改版本(用于 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 文档?

posted on 2025-04-24 11:47  吃草的青蛙  阅读(46)  评论(0)    收藏  举报

导航