etcd 基础回顾:架构、Raft 协议、键值存储模型与 MVCC

在深入存储配额耗尽故障之前,必须牢固掌握 etcd 的核心基础。etcd 是一个高可用的分布式键值存储系统,其设计围绕三个核心支柱:Raft 共识算法保证数据一致性,键值存储模型提供数据组织方式,多版本并发控制(MVCC)实现高效并发与历史查询。本节将系统梳理这些基础概念。

etcd 整体架构

etcd 集群由多个节点组成,每个节点在逻辑上分为多个层次。理解这些层次有助于定位故障根因。

架构要点:etcd 的存储路径是 Client → gRPC API → Raft 层 → WAL(预写日志)→ MVCC 存储引擎 → BoltDB(底层持久化)。任何一层的瓶颈都可能引发存储配额问题。
etcd 节点内部架构分层 客户端 (Client) gRPC API / 认证层 Raft 共识模块 WAL (预写日志) MVCC 存储引擎 + BoltDB (持久化) 存储配额耗尽通常发生在 BoltDB 层或 WAL 日志累积阶段

Raft 协议核心机制

Raft 是一种易于理解的分布式共识算法,etcd 使用它来确保集群中所有节点的数据一致。Raft 将节点角色分为三种:Leader、Follower 和 Candidate。

角色 职责 关键行为
Leader 处理所有客户端写入请求,管理日志复制 定期发送心跳(Heartbeat)维持权威;负责日志条目分发
Follower 被动接收 Leader 的日志复制,响应投票请求 如果选举超时未收到心跳,则转为 Candidate 发起选举
Candidate 在选举阶段请求其他节点投票 获得多数票(quorum)后成为新 Leader
运维启示:存储配额耗尽时,Leader 节点可能因无法写入 WAL 而触发降级或选举超时,导致集群短暂不可用。这是故障场景中需要优先监控的指标。

键值存储模型

etcd 的键值存储模型具有以下特点:

  • 层次化键空间:键以 / 分隔,形成类似文件系统的目录结构,例如 /registry/services/specs/nginx。
  • 键值对:每个键关联一个值,值可以是任意字节序列(通常为 JSON 或 protobuf 序列化数据)。
  • 租约(Lease):键可以绑定到租约上,租约过期时自动删除关联的键,用于实现 TTL 自动清理。
  • Watch 机制:客户端可以监视某个键或前缀的变化,实现事件驱动。
# 示例:写入一个键值对
etcdctl put /cluster/config/max_nodes "100"

# 读取键值
etcdctl get /cluster/config/max_nodes

# 带租约写入(60秒后自动删除)
etcdctl put /tmp/session "active" --lease=60s

多版本并发控制(MVCC)

MVCC 是 etcd 实现高效并发读和故障恢复的核心机制。每次对键的修改都会生成一个新的版本号,旧版本数据不会被立即覆盖,而是保留一段时间。

概念 说明 与存储配额的关系
revision 全局单调递增的版本号,每次事务(put/delete)递增 历史 revision 越多,BoltDB 占用空间越大
keyValue 存储键、值、创建版本、最后修改版本、租约等信息 每个版本都占用存储空间
历史版本压缩 通过 etcdctl compaction 或自动压缩策略清理旧版本 未及时压缩是存储配额超限的常见原因
碎片整理 压缩后 BoltDB 文件内部产生碎片,需 defrag 回收空间 即使压缩后,磁盘占用可能仍不下降,需手动整理
MVCC 版本链与压缩示意 rev=1 (创建) rev=2 (更新) rev=3 (更新) rev=4 (删除) 压缩点 (compaction) — 清理 rev ≤ 3 的历史版本 rev=4 (当前) BoltDB 内部碎片 (未回收) defrag → 磁盘空间回收 关键点:压缩(compaction)释放逻辑空间,碎片整理(defrag)释放物理磁盘空间
常见误区:很多运维人员认为执行了 etcdctl compaction 后磁盘占用会立即下降。实际上,压缩只是标记旧版本为可回收,BoltDB 文件大小不会变化,必须配合 etcdctl defrag 才能回收物理空间。在存储配额耗尽场景中,如果只压缩不整理,告警不会解除。

关键参数速查表

以下参数直接影响存储配额行为和故障恢复策略:

参数 默认值 说明
--quota-backend-bytes 2 GB BoltDB 存储配额上限,超过后拒绝写入
--auto-compaction-mode periodic 自动压缩模式:periodic(周期)或 revision(版本数)
--auto-compaction-retention 1h 保留历史版本的时间或版本数量
--max-request-bytes 1.5 MB 单个请求的最大字节数,防止大 key 撑爆内存