3. etcd 核心原理回顾:为压测打下理论基础

在构建多集群 etcd 压测沙盘之前,必须深入理解 etcd 的四大核心支柱:Raft 共识算法、MVCC 存储引擎、Watch 机制以及事务 API。这些机制直接决定了 etcd 在高压场景下的行为特征、性能瓶颈和故障模式。本节将逐一剖析,为后续压测指标设计和故障注入提供理论依据。

3.1 Raft 共识算法:分布式一致性的基石

etcd 使用 Raft 算法在集群节点间复制日志,确保数据强一致性。Raft 将节点角色分为三种:Leader、Follower 和 Candidate。所有写请求必须由 Leader 处理,Leader 将日志条目复制到多数节点(Quorum)后,才视为提交成功。

压测关键指标:Leader 选举时间、日志复制延迟、Quorum 健康度。当集群出现网络分区或节点故障时,Leader 重新选举期间集群不可写,这是压测中必须模拟的故障场景。
Raft 共识算法核心流程 客户端 Leader 处理所有写请求 Follower 1 日志复制 Follower 2 日志复制 Follower 3 日志复制 写请求 AppendEntries Quorum 计算: 3 节点集群 Quorum = 2(多数),5 节点集群 Quorum = 3。Leader 必须收到多数 Follower 的确认才能提交日志。

3.1.1 选举与心跳

Leader 通过定期发送 Heartbeat(心跳)维持权威。Follower 如果在选举超时时间内未收到心跳,则转为 Candidate 并发起选举。选举超时通常为 150~300ms 随机值,避免多个节点同时发起选举导致选票分裂。

参数 默认值 压测影响
Heartbeat Interval 100ms 心跳间隔越短,网络开销越大;间隔过长会延迟故障发现
Election Timeout 1000ms 超时时间影响 Leader 切换速度,压测中可模拟网络延迟触发频繁选举
Snapshot Count 10000 触发快照的日志条目数,影响磁盘 I/O 和内存占用
压测陷阱:当集群节点数增加(如 5 节点 → 7 节点),Raft 的日志复制延迟会显著上升,因为 Leader 需要等待更多节点的确认。压测时应对比不同集群规模的写入吞吐量。

3.2 MVCC 存储引擎:版本管理与并发控制

etcd 采用 多版本并发控制(MVCC) 存储引擎,底层基于 BoltDB(etcd v3 使用 bbolt)。每次写操作不会原地覆盖旧值,而是生成一个新版本号(revision),旧版本数据保留在磁盘上,直到被压缩(Compaction)回收。

MVCC 版本链与压缩机制 Key: /app/config rev=1 rev=2 rev=3 rev=4 (已压缩) rev=5 (已压缩) rev=6 rev=7 Compaction 机制: etcd 定期压缩历史版本(如保留最近 2 小时的数据),释放磁盘空间。压测时需监控压缩延迟和磁盘 I/O 峰值。 当前版本

3.2.1 版本号与事务隔离

etcd 使用全局单调递增的 revision 作为版本号。每个 key 的每次修改都会产生一个新 revision。MVCC 使得读操作无需加锁,读请求可以基于某个固定 revision 获取一致性快照,实现 可重复读(Repeatable Read) 隔离级别。

  • 全局 revision:集群范围内单调递增,每次事务提交时递增。
  • key 的版本:每个 key 维护 create_revision、mod_revision、version(修改次数)。
  • 读事务:基于 revision 的快照读,不阻塞写操作。
压测建议:监控 etcd_server_slow_read_indexes_total 指标,如果读索引变慢,说明 MVCC 版本链过长或磁盘 I/O 成为瓶颈。压测时可通过调整 --auto-compaction-retention 参数控制版本保留时间。

3.3 Watch 机制:事件驱动的核心

Watch 机制允许客户端监听 key 或 key 前缀的变化。etcd 通过 gRPC 双向流 将事件推送给客户端。Watch 事件包含 PUT、DELETE 类型,以及变更前后的 key-value 和版本号。

Watch 类型 描述 压测关注点
Key Watch 监听单个 key 的变化 事件推送延迟、丢事件概率
Prefix Watch 监听某个前缀下的所有 key 大量 key 变更时的事件吞吐量
ProgressNotify 定期发送进度通知,确保 Watch 不超时 长时间空闲连接的心跳开销

3.3.1 Watch 实现原理

etcd 的 Watch 基于 MVCC 的事件历史记录实现。当客户端创建 Watch 时,etcd 会从指定的 revision 开始扫描事件历史,然后持续监听新事件。Watch 事件存储在内存中的 EventHistory 环形缓冲区,默认容量为 1000 条。

压测陷阱:如果客户端消费速度跟不上事件产生速度,EventHistory 缓冲区会溢出,导致事件丢失。压测时应模拟慢消费者场景,验证 etcd 的背压机制是否生效。

3.4 事务 API:原子性与条件更新

etcd v3 提供 事务(Txn) API,支持在一个原子操作中执行多个比较(If)、成功操作(Then)和失败操作(Else)。事务基于乐观锁实现,通过比较 revision 或 value 来保证原子性。

// 事务示例:原子 CAS(Compare-And-Swap)
txn := client.Txn(ctx)
txn.If(client.Compare(client.Value("/app/config"), "=", "old_value"))
txn.Then(client.Put("/app/config", "new_value"))
txn.Else(client.Get("/app/config"))
resp, err := txn.Commit()

3.4.1 事务性能特征

事务的性能受以下因素影响:

  • 比较条件数量:每个比较条件需要读取一次 key,增加延迟。
  • 操作数量:Then/Else 中的操作越多,事务执行时间越长。
  • 冲突概率:高并发下事务冲突导致重试,降低吞吐量。
事务类型 典型延迟(3 节点集群) 适用场景
单 key 写入 0.5~2ms 简单配置更新
多 key 事务(5 个操作) 2~5ms 分布式锁、服务注册
条件事务(CAS) 1~3ms Leader 选举、资源分配
压测核心指标:事务吞吐量(TPS)、P99 延迟、事务冲突率、重试次数。压测时应模拟不同比例的事务类型(读多写少 vs 写多读少),观察 etcd 的性能拐点。

3.5 四大机制协同工作:压测视角

在压测场景中,Raft、MVCC、Watch 和事务 API 并非独立运行,而是紧密耦合:

  1. 写路径:客户端发起事务 → Leader 接收 → Raft 日志复制 → 多数节点确认 → 提交到 MVCC 存储 → 生成新 revision → 触发 Watch 事件推送。
  2. 读路径:客户端发起读请求 → 基于 revision 的快照读 → 从 MVCC 存储返回数据(无需 Raft 共识)。
  3. 故障场景:Leader 宕机 → Raft 选举新 Leader → 期间写请求失败 → 客户端重试 → 新 Leader 恢复服务。