2. etcd 架构基础:Raft 共识算法、存储引擎、Watch 机制、租约机制

etcd 作为云原生控制平面的核心数据存储,其架构设计围绕 强一致性、高可用 和 实时观测 展开。本章深入剖析支撑 etcd 的四大基础组件:Raft 共识算法、存储引擎、Watch 机制与租约机制,为后续灾备恢复测试验证奠定理论基础。

2.1 Raft 共识算法

Raft 是 etcd 实现分布式数据一致性的核心算法,通过 Leader 选举、日志复制 和 安全性 三个子问题保证集群在部分节点故障时仍能对外提供一致的服务。

核心原则: 集群中最多只有一个 Leader,所有写请求必须经过 Leader,Leader 将日志条目复制到多数节点(Quorum)后才算提交成功。

2.1.1 节点角色与状态转换

角色 职责 超时机制
Leader 处理客户端请求、管理日志复制、发送心跳 无超时,主动发送 AppendEntries RPC
Follower 被动响应 Leader 和 Candidate 的 RPC 请求 选举超时(election timeout)后转为 Candidate
Candidate 发起选举,请求其他节点投票 选举超时后重新发起新一轮选举
Raft 节点角色状态转换图 Follower Candidate Leader 选举超时 获得多数票 发现更高任期 Leader 或网络分区 选举超时或收到更高任期 Leader 说明:任期(Term)是 Raft 的逻辑时钟,每个任期最多只有一个 Leader。 节点通过比较任期号来保证日志的新旧顺序。

2.1.2 日志复制流程

  1. 客户端请求:客户端将写请求发送到 Leader。
  2. 追加日志:Leader 将请求封装为日志条目,追加到本地日志中。
  3. 并行复制:Leader 向所有 Follower 发送 AppendEntries RPC,包含新日志条目。
  4. 等待 Quorum:Leader 等待多数节点(N/2 + 1)成功复制日志。
  5. 提交并应用:Leader 将日志条目标记为已提交(committed),应用到状态机,并通知客户端。
  6. 后续通知:Leader 在后续心跳中通知 Follower 提交该日志。
灾备启示: 恢复备份时,必须确保备份数据包含已提交但尚未应用到状态机的日志条目。etcd 的 snapshot 机制会定期压缩日志,备份应同时包含 snapshot 和后续的 WAL 日志。

2.2 存储引擎:BoltDB 与 MVCC

etcd v3 使用 BoltDB 作为底层存储引擎,这是一个基于 B+ 树 的嵌入式键值存储库。BoltDB 通过 MVCC(多版本并发控制) 实现高效的读写并发。

2.2.1 存储架构层次

层次 组件 说明
API 层 gRPC Server / v3 API 接收客户端请求,转换为内部操作
KV 层 etcdserver / mvcc 实现键值操作、事务、Watch 注册
存储层 backend / BoltDB 持久化键值数据与事务日志
WAL 层 wal / snap 预写日志与快照,保证崩溃恢复

2.2.2 MVCC 与版本管理

每个键值对在 BoltDB 中存储多个版本,每个版本对应一个 revision(修订号)。revision 是一个全局递增的 64 位整数,由 main(事务 ID)和 sub(事务内操作序号)组成。

// 键值存储结构(简化)
type KeyValue struct {
    Key            []byte
    CreateRevision int64  // 创建时的 revision
    ModRevision    int64  // 最后修改时的 revision
    Version        int64  // 修改次数
    Value          []byte
    Lease          int64  // 绑定的租约 ID
}
备份恢复关键点: 恢复备份时,BoltDB 文件中的 revision 信息必须完整保留。如果 revision 回退,可能导致 Watch 客户端收到重复事件或丢失事件。生产环境建议使用 etcdctl snapshot restore 命令,它会自动处理 revision 的连续性。

2.3 Watch 机制

Watch 是 etcd 实现 事件驱动 的核心机制,允许客户端监听特定键或前缀的变化,实现实时配置同步、服务发现等功能。

2.3.1 Watch 工作流程

  1. 注册 Watch:客户端通过 gRPC 流式 RPC 注册 Watch,指定监听的键或前缀。
  2. 事件缓存:etcd 在内存中维护一个事件环形缓冲区(watchableStore),容量默认 1000 个事件。
  3. 事件推送:当键值发生变化时,etcd 将事件写入缓冲区,并通过 gRPC 流推送给客户端。
  4. 历史事件回放:如果客户端断开重连,可以指定从某个 revision 开始回放历史事件。
Watch 事件推送流程 Watch 客户端 etcd Server BoltDB Watch(key, rev) 读取历史 事件缓冲区 新事件 推送事件 说明:Watch 事件包含 EventType(PUT/DELETE)、Key、Value 和 Revision。 客户端通过比较 revision 判断是否丢失事件,必要时重新同步。

2.3.2 Watch 与备份恢复的关系

  • 事件丢失风险:如果备份数据中的 revision 落后于实际集群,恢复后 Watch 客户端可能无法获取中间事件。
  • 恢复策略:使用 etcdctl snapshot restore 时,可以指定 --skip-revision-check 跳过 revision 检查,但可能导致 Watch 行为异常。
  • 最佳实践:灾备恢复后,建议重启所有 Watch 客户端,从最新 revision 重新开始监听。

2.4 租约机制

租约(Lease)是 etcd 实现 分布式锁、服务心跳 和 自动过期清理 的核心机制。租约是一个带有 TTL(生存时间) 的对象,键值可以绑定到租约上,租约过期时自动删除所有关联的键值。

2.4.1 租约生命周期

阶段 操作 说明
创建 LeaseGrant 指定 TTL(秒),返回 Lease ID
绑定 Put 时指定 Lease ID 键值关联到租约,租约过期时自动删除
续约 LeaseKeepAlive 客户端定期发送心跳,重置 TTL
过期 TTL 归零 etcd 自动删除所有关联键值,并触发 DELETE 事件
撤销 LeaseRevoke 主动释放租约,立即删除关联键值

2.4.2 租约在灾备中的注意事项

  • 时间敏感性:恢复备份时,租约的剩余 TTL 会丢失。恢复后的租约将使用备份时的原始 TTL,可能导致键值提前过期。
  • 续约中断:如果备份恢复导致集群停机时间超过 TTL,所有绑定租约的键值将被自动删除。
  • 恢复建议:灾备恢复后,应尽快恢复客户端续约流程。对于关键服务,建议使用较长的 TTL(如 60 秒以上),并配合健康检查机制。
# 创建租约(TTL=60秒)
etcdctl lease grant 60

# 绑定键值到租约
etcdctl put /service/backend "alive" --lease=694d6c8e8c7f4d1a

# 续约(客户端需周期性执行)
etcdctl lease keep-alive 694d6c8e8c7f4d1a
生产建议: 在控制平面灾备方案中,建议将租约 TTL 设置为 2 * 预期恢复时间,避免恢复过程中因租约过期导致服务中断。同时,恢复后应优先恢复租约续约流程,再处理其他业务逻辑。