2. etcd 核心架构回顾:Raft 共识算法、存储引擎、网络层与 Watch 机制
在深入压测沙盘调试之前,必须重新梳理 etcd 的四大核心支柱。etcd 本质上是一个强一致性、高可用的分布式键值存储系统,其架构围绕 Raft 共识、持久化存储、网络通信和事件驱动四个维度展开。理解这些模块的交互关系,是后续进行分布式追踪和动态日志调优的基础。
2.1 整体架构分层
etcd 的架构可以抽象为三层:客户端网络层(gRPC API)、共识与状态机层(Raft + MVCC)以及持久化存储层(bbolt)。Watch 机制则横跨网络层与状态机层,提供实时事件推送能力。
架构要点:gRPC 层负责协议转换与流控;Raft 层保证日志一致复制;bbolt 层提供 ACID 事务与快速点查;Watch 层通过事件队列与 MVCC revision 实现增量推送。压测时,瓶颈往往出现在 Raft 的磁盘 fsync 延迟或 Watch 的事件堆积。
2.2 Raft 共识算法
Raft 是 etcd 的一致性大脑。它将分布式共识问题分解为三个子问题:领导者选举(Leader Election)、日志复制(Log Replication)和安全性(Safety)。
2.2.1 核心角色与任期
- Leader:负责处理所有客户端写入请求,将日志条目复制到 Follower,并在日志提交后应用状态机。
- Follower:被动接收 Leader 的 AppendEntries RPC,投票给 Candidate。
- Candidate:选举发起者,向其他节点请求投票,获得多数票后成为 Leader。
- Term(任期):逻辑时钟,每个选举周期对应一个递增的任期号。
2.2.2 日志复制流程
- 客户端通过 gRPC 发送
Put请求到 Leader。 - Leader 将操作封装为 Log Entry(包含 Term、Index、Command),追加到本地日志。
- Leader 并行向所有 Follower 发送
AppendEntries RPC。 - Follower 收到后校验日志一致性(prevLogIndex/prevLogTerm),写入本地日志并返回成功。
- Leader 收到多数派(quorum)确认后,将日志条目标记为 committed,应用到状态机(MVCC)。
- Leader 在后续心跳中通知 Follower 提交该条目。
压测关注点:Raft 的 磁盘 I/O(WAL fsync)和 网络 RTT 直接决定写入延迟。当集群出现网络分区或磁盘抖动时,Leader 可能频繁切换,导致吞吐量骤降。分布式追踪中应重点标记
propose、commit、apply 三个阶段的时间戳。
2.3 存储引擎:bbolt
etcd 使用 bbolt(BoltDB 的 fork)作为底层持久化存储引擎。bbolt 是一个嵌入式、ACID 兼容的 B+Tree 数据库,直接映射到内存映射文件(mmap)。
| 特性 | 说明 | 压测影响 |
|---|---|---|
| B+Tree 索引 | 所有键值对存储在 B+Tree 中,支持范围扫描和前缀查询。 | 大量随机写入会导致 B+Tree 频繁分裂与合并,增加写放大。 |
| 事务(Tx) | 只读事务可并发,读写事务串行执行(同一时刻只有一个写事务)。 | 写事务持有锁期间,所有读事务必须等待,影响并发读性能。 |
| 页管理(Page) | 数据按页(默认 4KB)组织,空闲页通过 freelist 管理。 | 大量删除操作会导致 freelist 膨胀,增加下次写入的分配开销。 |
| 预写日志(WAL) | 所有写操作先写入 WAL,再写入数据文件,保证崩溃恢复。 | WAL 的 fsync 频率是写入延迟的关键瓶颈。 |
常见陷阱:bbolt 的 freelist 碎片化是 etcd 长期运行后性能下降的主要原因之一。压测时建议监控
etcd_disk_backend_commit_duration_seconds 和 etcd_server_quota_backend_bytes 指标,必要时执行 etcdctl defrag 进行碎片整理。
2.4 网络层:gRPC
etcd 的所有客户端交互和节点间通信均基于 gRPC(HTTP/2 协议)。gRPC 提供了双向流、多路复用和高效的 Protobuf 序列化能力。
2.4.1 主要 gRPC 服务
- KV Service:
Range、Put、DeleteRange、Txn(事务操作)。 - Watch Service:
Watch(创建/维持 Watch 流,推送事件)。 - Lease Service:
LeaseGrant、LeaseRevoke、LeaseKeepAlive。 - Cluster Service:
MemberAdd、MemberRemove、MemberUpdate。 - Maintenance Service:
Alarm、Defragment、Snapshot、Status。
2.4.2 流控与背压
gRPC 内置了基于 HTTP/2 的流控机制。etcd 在服务端实现了 请求限流(max-inflight)和 内存限流(max-request-bytes)。当客户端发送大量并发请求时,gRPC 的流控窗口可能成为瓶颈,表现为请求超时或连接重置。
调试技巧:在压测沙盘中,通过动态调整
--max-request-bytes 和 --max-txn-ops 参数,可以模拟不同的网络拥塞场景。结合分布式追踪中的 grpc.recv_bytes 和 grpc.send_bytes 标签,精准定位背压点。
2.5 Watch 机制
Watch 是 etcd 实现事件驱动架构的核心能力。客户端通过 gRPC 流订阅特定键或前缀,当键值发生变化时,etcd 实时推送事件。
2.5.1 工作流程
- 客户端调用
WatchRPC,指定要监听的 key 或前缀,以及起始 revision。 - 服务端 WatchableStore 将请求注册到对应的 watcher 列表中。
- 当 MVCC 状态机 apply 一个新写入时,生成 Event(包含 key、value、type、revision)。
- WatchableStore 将事件写入 事件缓存(ring buffer),并唤醒对应的 watcher。
- watcher 通过 gRPC 流将事件序列化后推送给客户端。
2.5.2 同步与异步模式
| 模式 | 行为 | 适用场景 |
|---|---|---|
| 同步(sync) | 事件在 apply 线程中直接推送,延迟低但阻塞状态机。 | 低并发、低延迟要求的场景。 |
| 异步(async) | 事件先写入缓存,由独立 goroutine 批量推送,不阻塞 apply。 | 高并发、大量 watcher 的场景(默认模式)。 |
压测风险:当客户端消费速度跟不上事件产生速度时,事件缓存(ring buffer)会溢出,导致 watcher 被强制关闭并返回
ErrCompacted 错误。压测时应监控 etcd_watch_events_total 和 etcd_watch_dropped_total 指标,动态调整 --experimental-watch-progress-notify-interval 参数。
2.6 核心模块交互总结
下表总结了四大模块在 etcd 写入路径中的交互关系,是压测沙盘中定位瓶颈的参考地图。
| 步骤 | 涉及模块 | 关键操作 | 潜在瓶颈 |
|---|---|---|---|
| 1 | gRPC Server | 反序列化请求,鉴权,限流 | 连接数过多、请求体过大 |
| 2 | Raft Node | Propose 日志条目,写入 WAL | 磁盘 fsync 延迟、网络分区 |
| 3 | Raft 网络 | AppendEntries RPC 广播 | 网络带宽、RTT、丢包 |
| 4 | Raft State Machine | Commit 日志,Apply 到 MVCC | CPU 竞争、goroutine 调度 |
| 5 | MVCC + bbolt | 生成 revision,写入 B+Tree | bbolt 写事务锁、freelist 碎片 |
| 6 | WatchableStore | 生成 Event,写入缓存,推送 | 事件堆积、缓存溢出 |
| 7 | gRPC Stream | 序列化 Event,发送给客户端 | 流控窗口、客户端消费慢 |
沙盘调试启示:在压测沙盘中,我们可以通过动态调整日志级别(如将
capnslog 或 zap 的级别从 info 切换为 debug),实时观察 Raft 的 propose/commit 日志、bbolt 的事务提交日志以及 Watch 的事件推送日志,从而在不重启进程的情况下定位上述瓶颈点。