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 | 发起选举,请求其他节点投票 | 选举超时后重新发起新一轮选举 |
2.1.2 日志复制流程
- 客户端请求:客户端将写请求发送到 Leader。
- 追加日志:Leader 将请求封装为日志条目,追加到本地日志中。
- 并行复制:Leader 向所有 Follower 发送
AppendEntries RPC,包含新日志条目。 - 等待 Quorum:Leader 等待多数节点(
N/2 + 1)成功复制日志。 - 提交并应用:Leader 将日志条目标记为已提交(committed),应用到状态机,并通知客户端。
- 后续通知: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 工作流程
- 注册 Watch:客户端通过 gRPC 流式 RPC 注册 Watch,指定监听的键或前缀。
- 事件缓存:etcd 在内存中维护一个事件环形缓冲区(
watchableStore),容量默认 1000 个事件。 - 事件推送:当键值发生变化时,etcd 将事件写入缓冲区,并通过 gRPC 流推送给客户端。
- 历史事件回放:如果客户端断开重连,可以指定从某个 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 * 预期恢复时间,避免恢复过程中因租约过期导致服务中断。同时,恢复后应优先恢复租约续约流程,再处理其他业务逻辑。