嵌入式 KV 数据库——基础设施层的隐形支柱 / Embedded Key-Value Databases for Infrastructure
📅 创建时间:2026-05-08 🏷️ 标签:#etcd #RocksDB #LevelDB #Badger #LSMTree #SSTable #嵌入式KV 📚 前置知识:[[00-db-overview]] [[05-redis]](了解 KV 和持久化基础) 📚 相关知识:[[17-lobechat-design-analysis]](LobeChat 的 PGlite)
定位速览
┌─────────────────────────────────────────────────────────────┐
│ 嵌入式 KV 数据库在系统中的位置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 这类数据库很少被直接使用,但它们是无数知名项目的基础: │
│ │
│ etcd → Kubernetes、Docker、etcd 自身 │
│ RocksDB → Kafka、Cassandra、TiDB、Facebook 产品线 │
│ LevelDB → Chrome IndexedDB、Bitcoin、RIPEMD-160 │
│ Badger → Dgraph、BadgerDB(纯 Go 实现) │
│ │
│ 定位: 单机嵌入式 KV,不是客户端/服务器架构 │
│ 核心特征: 高性能、LSM-Tree 写优化、零运维 │
│ 适用场景: 元数据存储、配置中心、存储引擎、日志 │
│ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
第1部分:LSM-Tree 原理——为什么 KV 数据库这么快
1.1 LSM-Tree vs B+Tree
┌─────────────────────────────────────────────────────────────┐
│ LSM-Tree vs B+Tree │
├─────────────────────────────────────────────────────────────┤
│ │
│ B+Tree(MySQL InnoDB / PostgreSQL): │
│ │
│ 读取: O(log n) + 1 次磁盘 IO │
│ 写入: 随机 IO(修改叶子节点,可能触发页分裂) │
│ 更新: 同写入 │
│ 删除: 可能留有墓碑(tombstone) │
│ │
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ 5 │ │10 │ │15 │ │20 │ ← 非叶子层(索引) │
│ └───┘ └─┬─┘ └───┘ └───┘ │
│ ┌───┬───┼───┬───┬───┬───┐ │
│ │ 1 │ 5 │10 │15 │20 │25 │ ← 叶子层(数据 + 链表) │
│ └───┴───┴───┴───┴───┴───┘ │
│ │
│ LSM-Tree(RocksDB / LevelDB / etcd): │
│ │
│ 写入: O(1) + 顺序写(只写 MemTable) │
│ 读取: O(log n) × 多层(MemTable → L0 → L1 → ...) │
│ 更新/删除:追加写入(不原地修改) │
│ │
│ ┌─────────────────────────────┐ │
│ │ MemTable(内存) │ ← 写入先到这里 │
│ │ (有序跳表或红黑树) │ 写完即返回(极快) │
│ └─────────────────────────────┘ │
│ ↓ MemTable 写满 → 刷盘 │
│ ┌─────────────────────────────┐ │
│ │ L0 (磁盘, 4MB) │ ← 每个文件都有范围重叠 │
│ └─────────────────────────────┘ │
│ ↓ L0 写满 → 合并到 L1 │
│ ┌─────────────────────────────┐ │
│ │ L1 (磁盘, 10MB) │ ← 每个文件范围不重叠 │
│ └─────────────────────────────┘ │
│ ↓ 逐层放大(×10) │
│ ┌─────────────────────────────┐ │
│ │ L2, L3, ... (越来越大) │ │
│ └─────────────────────────────┘ │
│ │
│ 为什么 LSM-Tree 写入快? │
│ • 写入 = 顺序写 MemTable = O(1)(不涉及磁盘随机 IO) │
│ • 删除 = 写入墓碑标记 = 同样是顺序写 │
│ • 所有磁盘写入都是顺序的 │
│ │
│ 为什么 LSM-Tree 读取可能慢? │
│ • 数据可能在 MemTable、L0、L1... 多层 │
│ • 需要逐层查找(Bloom Filter 优化) │
│ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
1.2 Compaction(压缩合并)
┌─────────────────────────────────────────────────────────────┐
│ Level Compaction │
├─────────────────────────────────────────────────────────────┤
│ │
│ Leveled Compaction(分层压缩): │
│ │
│ L1: [0-10) [10-20) [20-30) [30-40) │
│ ↓ 合并 │
│ L2: [0-20) [20-40) ← 文件数量不变,范围变大 │
│ ↓ 合并 │
│ L3: [0-40) ← 范围继续扩大 │
│ │
│ 特点: │
│ • 空间放大:同一数据可能在多层存在 │
│ • 写放大:每次压缩都要读写大量数据 │
│ • 读取:越深层越慢,越新数据越快(热数据在上层) │
│ │
│ Bloom Filter(布隆过滤器): │
│ • 每个 SSTable 文件对应一个 Bloom Filter │
│ • 查询前先问 Bloom Filter:"这个 key 在不在这个文件?" │
│ • 如果回答"不在",跳过该文件(节省 IO) │
│ • 如果回答"可能在",才真正读取文件 │
│ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
第2部分:etcd——分布式系统的元数据中心
2.1 etcd 是什么
┌─────────────────────────────────────────────────────────────┐
│ etcd 核心定位 │
├─────────────────────────────────────────────────────────────┤
│ │
│ etcd = embeddable + distributed + reliable + key-value │
│ │
│ 核心角色:Kubernetes 的"大脑" │
│ • 存储 Kubernetes 所有状态(元数据) │
│ • Service 发现、服务注册、配置管理 │
│ • Leader 选举、分布式锁 │
│ │
│ 技术基础: │
│ • Raft 共识算法(强一致) │
│ • gRPC + Protocol Buffers(高效通信) │
│ • BoltDB(RocksDB 之上的 KV 封装) │
│ │
│ 为什么不用 Redis ZooKeeper? │
│ • Redis:无持久化/一致性保证,只有主从复制 │
│ • ZooKeeper:老旧,协议复杂,生态绑定 J2EE │
│ • etcd:Raft + gRPC + 简洁 API,Kubernetes 原生 │
│ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2.2 etcd 实战
bash
# etcd 使用:内置命令行 etcdctl
export ETCDCTL_API=3
export ETCD_CERT=/path/to/ca.crt
export ETCD_KEY=/path/to/client.key
export ETCD_ENDPOINTS=https://127.0.0.1:2379
# ================== 基本操作 ==================
etcdctl put /config/service_a "{\"timeout\":30,\"retries\":3}"
etcdctl get /config/service_a
etcdctl get /config/ --prefix # 前缀匹配
etcdctl del /config/service_a
# ================== 事务(原子操作) ==================
# compare: "version of key == expected value"
# success: execute these operations
# failure: execute these operations
etcdctl txn --interactive
# 示例:分布式锁实现
etcdctl put /lock/mydir my_id --prev-create
# 如果 key 不存在则创建(prev-create)
# 如果 key 存在则失败
# ================== 租约和 TTL ==================
# 创建租约(TTL = 60 秒)
LEASE_ID=$(etcdctl lease grant 60 | cut -d' ' -f2)
# 绑定 key 到租约
etcdctl put /config/timeout "30" --lease=$LEASE_ID
# 续租
etcdctl lease keep-alive $LEASE_ID
# 租约过期后,key 自动删除
# ================== 监视(Watch) ==================
etcdctl watch /config/ --prefix
# 当 /config/ 下的任何 key 变化时,收到通知
# 用于:配置变更推送、服务发现
# ================== 集群健康检查 ==================
etcdctl endpoint health
etcdctl endpoint status1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2.3 etcd 在 Kubernetes 中的应用
bash
# Kubernetes 用 etcd 存储的所有 key
etcdctl get /registry/ --prefix | head -50
# 常见 key 路径:
/registry/services/endpoints/default/kubernetes # API Server 地址
/registry/services/endpoints/default/{svc-name} # Service 端点
/registry/pods/default/{pod-name} # Pod 定义
/registry/configmaps/default/{configmap-name} # ConfigMap
/registry/secrets/default/{secret-name} # Secret
/registry/deployments/default/{deploy-name} # Deployment1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
第3部分:RocksDB——大数据系统的存储引擎
3.1 RocksDB 定位
┌─────────────────────────────────────────────────────────────┐
│ RocksDB 在系统中的位置 │
├─────────────────────────────────────────────────────────────┤
│ │
│ RocksDB 不是给应用程序直接用的数据库, │
│ 而是被其他系统作为底层存储引擎嵌入: │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 使用 RocksDB 的知名系统: │ │
│ │ │ │
│ │ Kafka(0.9+) → 消息存储引擎 │ │
│ │ Apache Spark → 外部排序(sort) │ │
│ │ Apache Flink → 状态后端(state backend) │ │
│ │ TiDB / TiKV → 分布式存储层 │ │
│ │ CockroachDB → 存储引擎 │ │
│ │ Cassandra → 可插拔存储(可选) │ │
│ │ MyRocks → MySQL 存储引擎(用 RocksDB)│ │
│ │ PostgreSQL → 可作为扩展存储引擎 │ │
│ │ SQLite → 可作为存储引擎 │ │
│ │ Kafka Connect → 外部数据源/目标存储 │ │
│ │ Apache Druid → Deep Storage │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3.2 RocksDB vs LevelDB
┌─────────────────────────────────────────────────────────────┐
│ RocksDB vs LevelDB │
├─────────────────────────────────────────────────────────────┤
│ │
│ LevelDB(Google,2011): │
│ • 单进程,不支持持久化存储(内存数据库) │
│ • 不支持增量压缩 │
│ • 不支持多线程 compaction │
│ • 仅支持 String 对 String │
│ │
│ RocksDB(Facebook,2013,基于 LevelDB): │
│ • 从 LevelDB fork,增加大量企业级功能 │
│ • 支持 Column Families(列族,隔离不同数据) │
│ • 支持增量压缩(Leveled Compaction) │
│ • 支持多线程 compaction(利用多核) │
│ • 支持多种 SSTable 格式 │
│ • 支持宇节对齐(PlainTable,兼容 SSD) │
│ • 支持物化视图(MVO) │
│ • 支持追写(Ingest External File) │
│ • 支持备份恢复 │
│ • 生态:大量公司用其作为存储引擎 │
│ │
│ 总结:LevelDB 是理论验证,RocksDB 是工业级实现。 │
│ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
第4部分:Badger——纯 Go 的 LSM KV
go
// Badger:纯 Go 实现的 LSM KV 数据库(Dgraph 使用)
package main
import (
"fmt"
"github.com/dgraph-io/badger/v4"
)
func main() {
// 打开数据库
db, err := badger.Open(badger.DefaultOptions("/tmp/badger"))
if err != nil {
panic(err)
}
defer db.Close()
// 写入
err = db.Update(func(txn *badger.Txn) error {
err := txn.Set([]byte("name"), []byte("张三"))
return err
})
// 读取
err = db.View(func(txn *badger.Txn) error {
item, err := txn.Get([]byte("name"))
if err != nil {
return err
}
val, _ := item.ValueCopy(nil)
fmt.Printf("name = %s\n", val)
return nil
})
// 范围扫描
opts := badger.DefaultIteratorOptions
it := db.NewIterator(opts)
defer it.Close()
for it.Seek([]byte("a")); it.Valid(); it.Next() {
item := it.Item()
fmt.Printf("%s -> %s\n", item.Key(), item.Value())
}
// 删除
db.Update(func(txn *badger.Txn) error {
return txn.Delete([]byte("name"))
})
// 物化视图(把结果预先计算好存起来)
// 适合:频繁查询的聚合结果(总数、平均值等)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
第5部分:对比与选型
┌─────────────────────────────────────────────────────────────┐
│ 嵌入式 KV 数据库横向对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ │ 数据库 │ 语言 │ License │ 特点 │
│ ├───────────┼─────────┼─────────┼─────────────────────────┤
│ │ RocksDB │ C++ │ Apache2 │ 功能最全,性能最佳 │
│ │ LevelDB │ C++ │ BSD │ 轻量,参考实现 │
│ │ Badger │ Go │ Apache2 │ Go 生态,Rust 移植中 │
│ │ etcd/BoltDB│ Go │ Apache2 │ Raft 一致性,K8s 生态 │
│ │ LedisDB │ Go │ BSD │ Redis 协议兼容 │
│ │
│ 选型决策树: │
│ │
│ 需要 Raft 一致性? │
│ → 是:etcd │
│ → 否 ↓ │
│ │
│ 语言偏好? │
│ → C++:RocksDB │
│ → Go:Badger / BoltDB │
│ │
│ 是 Kafka/Flink/Spark 的用户? │
│ → 直接使用这些框架内置的 RocksDB,无需自己集成 │
│ │
│ 需要持久化 + 崩溃恢复 + 高性能? │
│ → RocksDB 是工业标准 │
│ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
学习状态:🟡 开始学习