etcd Raft协议实现
约 1994 字大约 7 分钟
etcdraft
2025-05-18
etcd 是一个分布式键值存储系统,以 Raft 共识协议为核心,为 Kubernetes 等分布式系统提供可靠的配置存储和服务发现。本文深入分析 etcd 的架构设计和 Raft 协议的具体实现。
etcd 整体架构
核心组件
| 组件 | 职责 |
|---|---|
| gRPC API | 处理客户端读写请求 |
| Raft | 共识协议,保证数据一致性 |
| WAL | 预写日志,持久化 Raft 日志条目 |
| Snapshot | 定期快照,截断 WAL 日志 |
| MVCC | 多版本并发控制存储引擎 |
| BoltDB | 底层 B+ Tree 键值存储 |
Raft 协议核心
节点角色
| 角色 | 职责 |
|---|---|
| Leader | 处理所有写请求,向 Follower 复制日志 |
| Follower | 被动接收 Leader 的日志复制和心跳 |
| Candidate | 选举过渡状态,请求其他节点投票 |
Leader 选举
选举规则:
- 每个节点在每个 term 只能投一票(先到先得)
- 候选人的日志必须至少和投票者一样新
- 选举超时随机化(150ms-300ms)避免多个候选人同时发起选举
日志复制 (Log Replication)
Raft 日志结构
日志一致性保证:
- 如果两个日志条目有相同的 index 和 term,它们存储相同的命令
- 如果两个日志条目有相同的 index 和 term,它们之前的所有日志条目都相同
Snapshot (快照)
当 WAL 日志过长时,通过快照压缩日志。
# etcd 快照配置
--snapshot-count 100000 # 每 100000 条日志触发快照Linearizable Read (线性化读)
etcd 支持线性化读,保证读到最新已提交的数据。
# etcd 客户端读操作
etcdctl get mykey --consistency=l # 线性化读(默认)
etcdctl get mykey --consistency=s # 串行化读(可能读到旧数据,但更快)Watch 机制
etcd 的 Watch 机制允许客户端监听 key 的变化事件,是 Kubernetes 中 Informer 的基础。
# Watch 示例
etcdctl watch /services/ --prefix
etcdctl watch /config/db --rev 100 # 从指定 revision 开始
# 编程示例 (Go)
# watcher := client.Watch(ctx, "/services/", clientv3.WithPrefix())
# for resp := range watcher {
# for _, event := range resp.Events {
# fmt.Printf("%s %s = %s\n", event.Type, event.Kv.Key, event.Kv.Value)
# }
# }Watch 的 MVCC 支持
etcd 使用 MVCC 存储,每次修改都有全局递增的 revision。Watch 可以从任意历史 revision 开始,不会丢失事件。
# 查看 key 的所有历史版本
etcdctl get mykey --rev 0 -w json
# etcd 压缩旧版本(节省存储空间)
etcdctl compaction 1000 # 压缩 revision 1000 之前的版本
# 自动压缩
--auto-compaction-retention=1h # 保留最近 1 小时Lease (租约)
Lease 是 etcd 的 TTL 机制,用于实现服务注册、分布式锁等场景。
# 创建 10 秒的 Lease
etcdctl lease grant 10
# lease 694d8175b6d83f01 granted with TTL(10s)
# 将 key 绑定到 Lease
etcdctl put --lease=694d8175b6d83f01 /services/web "192.168.1.100:8080"
# 续约(保持在线)
etcdctl lease keep-alive 694d8175b6d83f01
# 查看 Lease 信息
etcdctl lease timetolive 694d8175b6d83f01
# Lease 过期后,绑定的所有 key 自动删除etcd vs ZooKeeper 对比
| 维度 | etcd | ZooKeeper |
|---|---|---|
| 共识协议 | Raft | ZAB (类 Paxos) |
| 数据模型 | 扁平键值 | 树形节点(ZNode) |
| API | gRPC + HTTP/JSON | 自定义协议 |
| 语言 | Go | Java |
| Watch | 基于 MVCC revision | 一次性触发,需重新注册 |
| 线性化读 | ReadIndex / Lease Read | sync() + getData() |
| TTL | Lease 机制 | 临时节点(Ephemeral) |
| 部署 | 3/5/7 节点 | 3/5/7 节点 |
| 性能 | 写 ~10K QPS | 写 ~10K QPS |
| Kubernetes | 原生支持 | 需适配 |
运维与监控
# 集群状态
etcdctl endpoint status --cluster -w table
# 集群健康检查
etcdctl endpoint health --cluster
# 成员管理
etcdctl member list -w table
etcdctl member add node4 --peer-urls=https://10.0.0.4:2380
etcdctl member remove <member-id>
# 性能测试
etcdctl check perf --load="l" # s: small, m: medium, l: large
# 碎片整理
etcdctl defrag --cluster
# 备份
etcdctl snapshot save /backup/etcd-snapshot.db
etcdctl snapshot restore /backup/etcd-snapshot.db --data-dir=/var/lib/etcd-new关键监控指标
# Prometheus 指标
# etcd_server_leader_changes_seen_total — Leader 切换次数
# etcd_disk_wal_fsync_duration_seconds — WAL fsync 延迟
# etcd_disk_backend_commit_duration_seconds — BoltDB 提交延迟
# etcd_network_peer_round_trip_time_seconds — 节点间 RTT
# etcd_server_proposals_committed_total — 已提交的 proposal 总数
# etcd_server_proposals_pending — 待处理的 proposal
# etcd_mvcc_db_total_size_in_bytes — 数据库大小生产配置建议
# 硬件建议
# - SSD 磁盘(WAL 性能关键)
# - 低延迟网络(节点间 RTT < 10ms)
# - 独立部署(不与其他高 IO 服务混部)
# 关键配置
--heartbeat-interval=100 # 心跳间隔(ms)
--election-timeout=1000 # 选举超时(ms),应为心跳的 10 倍
--snapshot-count=100000 # 快照触发阈值
--auto-compaction-retention=8h # 自动压缩保留时间
--quota-backend-bytes=8589934592 # 存储配额 8GB总结
- etcd 基于 Raft 协议实现强一致性,Leader 处理所有写请求
- 日志复制确保多数节点确认后才提交,保证不丢数据
- Watch + MVCC revision 提供可靠的事件通知,不丢事件
- Lease 机制实现了 TTL,广泛用于服务发现和分布式锁
- 相比 ZooKeeper,etcd 的 API 更现代,Watch 更可靠,是 Kubernetes 的首选存储
- 生产部署需要 SSD 和低延迟网络,通常使用 3 或 5 节点集群
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于