作者介绍:简历上没有一个精通的运维工程师,下面的思维导图也是预计更新的内容和当前进度(不定时更新)。

数据库是一个系统(应用)最重要的资产之一,所以我们的数据库将从以下几个数据库来进行介绍。
MySQL
PostgreSQL
Redis
Etcd(本章节)
Etcd的数据存储体系并非简单的键值写入磁盘,而是一套分层解耦、各司其职的精巧架构。它要同时满足三个看似矛盾的需求:Raft协议对持久性的苛刻要求、多版本并发控制对历史数据的保留需求、生产环境对高性能读写的现实追求。etcd的存储设计,正是这三者平衡的产物。本文将从Raft内存存储、WAL持久化、MVCC逻辑存储、BoltDB物理存储四个层次,完整还原etcd的数据流转全貌。
Etcd的Raft模块是一个纯粹的状态机算法库,它本身不负责持久化,也不感知磁盘。所有日志条目在Raft模块内部存储在两个关键结构中:
MemoryStorage:这并非“内存存储易失数据”之意,而是已持久化日志的内存缓存。etcd将WAL落盘后的日志载入MemoryStorage,供Raft算法快速访问。节点重启时,WAL全量回放重新填充MemoryStorage。
unstable:这是Raft模块收到但尚未通知上层持久化的日志。当etcdserver从readyc通道消费Ready结构时,unstable中的日志才进入WAL写入流程。写入成功后,这些日志从unstable移动至MemoryStorage。
这一设计的精髓在于:Raft算法层只操作内存数据结构,磁盘I/O由上层异步处理,二者通过Channel解耦。etcd的“高性能共识”由此奠基。
WAL(Write-Ahead Log)是etcd数据可靠性的第一道防线。任何数据在写入状态机之前,必须先写入WAL并fsync落盘——这是Raft协议对持久性的硬性要求。
WAL以分段文件形式存储在磁盘,默认单文件64MB。当文件达到阈值,etcd执行“切分”(cut):新文件序列号+1,起始索引为当前最大索引+1。WAL中包含五类记录:MetadataType(节点元信息)、EntryType(用户日志)、StateType(Raft状态变更)、SnapshotType(快照标记)、CrcType(文件校验)-5。
写入路径:etcdserver从readyc获取一批待持久化日志,调用wal.Save()顺序追加至当前WAL文件尾部,立即执行fsync刷盘。这是etcd写入路径中唯一的同步磁盘操作,批处理机制使其代价被均摊至数十条请求。
恢复路径:节点重启时,从WAL目录读取所有分段文件,通过ReadAll()重放日志条目。若存在快照,则从快照后的第一条日志开始恢复——这是日志压缩与快照机制的衔接点
etcd v3最核心的存储革新是多版本并发控制(MVCC)。它不再原地更新数据,而是每次写操作生成新版本,旧版本仍可访问,直至被压缩回收。
逻辑视图:etcd对外暴露的是扁平二进制键空间,按字节字典序排列。每个原子写操作(即使事务中包含多个修改)会生成一个全局单调递增的revision(修订版本)。键值对的每一次修改,都是该键在这个revision下的一个新版本。
Generation与Version:每个键从创建到删除为一个generation(代际)。创建时version=1,每次修改version+1;删除操作生成墓碑(tombstone),结束当前代际。再次Put该键时,新建代际,version从1重新计数。这种设计精妙地解决了“删除后重建”的历史版本混淆问题。
物理视图的双层索引:etcd并未将所有版本数据杂乱堆放,而是构建了内存B-tree + 磁盘B+tree的双层架构:
(major, sub, type)三元组为Key,存储完整的mvccpb.KeyValue结构体。注意:这里存储的是每次修改的“完整结果”,而非差值(delta)。这使得读取操作一次B+树查询即可拿到全部数据,无需追溯版本链。Etcd选择BoltDB作为底层存储引擎,这是一款纯Go实现的嵌入式KV数据库,其核心是B+树 + 内存映射文件(mmap)。
磁盘页(Page):BoltDB将文件划分为固定4KB的页,包含meta page(事务元数据)、freelist page(空闲页索引)、branch page(索引页)、leaf page(数据页)。两个meta page交替写入,确保写入中途崩溃时可回滚-5。
事务模型:BoltDB支持完全ACID的读写事务。etcd利用其单写者、多读者能力:
BatchTx():获取批量写事务,积累至batchLimit(默认10000)或batchInterval(默认100ms)后统一提交fsync——这是etcd写吞吐量破万的关键。ReadTx()/ConcurrentReadTx():获取读事务,基于mmap直接内存访问,零系统调用,与写事务完全并发-5。Size与SizeInUse:这是运维中极易混淆的两个指标。Size()是BoltDB文件的物理大小(含空闲空间),SizeInUse()是实际有效数据大小。二者差值越大,碎片越严重——这正是defrag命令的修复目标。
etcd的存储体系可以凝练为三条原则: