Skip to content

etcd从零到生产级:Raft、MVCC、Revision、Watch、Lease与故障恢复

etcd 是面向分布式协调和控制面元数据的强一致键值存储。它使用 Raft 复制有序日志,以MVCC保存键值历史,通过全局revision、事务比较、Watch和Lease支撑Kubernetes状态、服务发现、配置、选主和锁等场景。

etcd不是“高性能业务缓存”,也不是把Redis命令换成gRPC。它的正确使用依赖多数派、线性一致读、历史压缩、租约失效、外部Fencing、低延迟磁盘、成员变更和灾难恢复。任何一项没设计好,控制面会出现Watch永久断流、Lease误过期、后端配额耗尽或恢复后缓存不收敛。

一、学习目标

  1. 区分Raft日志、MVCC revision和单个Key version。
  2. 解释写事务如何经Leader和多数派提交。
  3. 区分默认线性一致读和Serializable读。
  4. 使用Range、Put、Delete、Txn和Compare实现原子状态转换。
  5. 从List快照的header revision启动Watch,并处理Compacted错误。
  6. 解释Lease、KeepAlive、TTL、Revoke和失效窗口。
  7. 设计服务发现、分布式锁、选主和Fencing Token。
  8. 区分Compaction和Defrag,处理NOSPACE告警。
  9. 执行成员变更、Snapshot、Restore和生产Runbook。

二、etcd适合做什么,不适合做什么

适合原因
Kubernetes控制面状态需要强一致元数据、Watch和版本并发控制
服务实例与成员发现Prefix + Lease + Watch
小型动态配置MVCC、Txn、Watch
业务Leader选举Lease、Txn、顺序revision
分布式协调锁Txn、Lease、Watch;仍需Fencing
控制器期望状态List-Watch增量收敛

不适合:

  • 高频订单、流水和日志明细。
  • 大对象、图片和文件。
  • 低价值热点计数器。
  • 需要复杂SQL和多表分析的业务。
  • 以为Lease到期可以代替精确定时任务。
  • 以为etcd事务能和MySQL、Redis、MQ原子提交。

三、组件和数据路径

mermaid
flowchart TD
    A["Client通过gRPC访问Endpoint"] --> B["接收请求的etcd Member"]
    B --> C{"操作是否需要Raft写提交"}
    C -- "写Txn" --> D["转交当前Raft Leader排序"]
    D --> E["WAL追加并复制给Follower"]
    E --> F["多数派确认后提交"]
    F --> G["Apply到MVCC和Backend"]
    G --> H["生成revision并通知Watch"]
    C -- "读" --> I["按一致性选项执行Range"]
对象含义
Member一个etcd Server进程及持久数据
Endpoint客户端可连接地址
Leader当前Raft日志排序者
Follower复制日志并可服务部分读请求
Learner非投票学习成员,便于追平后再晋升;版本有边界
Cluster ID集群身份,防止错误成员混入
Member ID成员身份

连接任意Endpoint不代表它就是Leader。客户端库通常维护多个Endpoint、重试和负载;需要Leader协调的操作由集群内部转发或协调。

四、一次写事务完整过程

mermaid
flowchart TD
    A["Client提交Put/Delete/Txn"] --> B["Member认证、限额和请求校验"]
    B --> C["Leader把命令追加为Raft日志"]
    C --> D["复制给Follower"]
    D --> E{"满足多数派提交规则"}
    E -- "否" --> F["等待、超时或失去Leader"]
    E -- "是" --> G["推进commit index"]
    G --> H["各Member按序Apply"]
    H --> I["MVCC事务产生新的全局revision"]
    I --> J["响应Client并投递Watch事件"]

Leader本地追加不等于提交。3个投票成员需要多数2,5个需要多数3。少数派旧Leader即使进程仍活着,也不能继续安全提交。

Client超时或连接断开仍可能发生在“已经提交、响应丢失”之后;因此写结果可能UNKNOWN,必须用幂等业务键和后续Range/Txn确认。

五、Raft日志与MVCC不是同一层

解决问题主要标识
Raft多Member对命令顺序和提交达成一致term、log index、commit index
Apply状态机按已提交顺序执行命令applied index
MVCC保存Key在不同revision下的值和元数据revision、create_revision、mod_revision
Backend持久化MVCC索引和值DB大小、in-use大小、quota

不能把etcd revision直接叫做Raft log index。二者通常相关地向前推进,但属于不同抽象,恢复、只读事务和内部维护可能使简单一一对应假设失效。

六、MVCC和全局revision

etcd每次修改键空间的已提交事务产生新的全局revision。一个Txn同时修改多个Key,这些事件可以共享同一revision:

text
revision 100:
  PUT /services/order/a
  PUT /services/order/b
  DELETE /services/order/old

revision是集群键空间的逻辑版本,不是毫秒时间戳,也不是单个Key被修改的次数。

七、每个Key的四个关键元数据

字段含义
create_revision当前Key生命周期首次创建的全局revision
mod_revision最近修改该Key的全局revision
version当前Key生命周期内修改次数;删除后重建会开始新生命周期
lease当前绑定的Lease ID,没有则为0

示例:

text
rev=10 PUT /config/order=v1
  create_revision=10, mod_revision=10, version=1

rev=15 PUT /config/order=v2
  create_revision=10, mod_revision=15, version=2

rev=20 DELETE /config/order

rev=30 PUT /config/order=v3
  create_revision=30, mod_revision=30, version=1

所以:

  • 判断“不存在”常比较 version == 0
  • 防并发覆盖常比较 mod_revision == expected
  • 排队锁可利用创建revision表达先后。
  • 不能把Key version当全局revision。

八、Range和Prefix读取

etcd v3 KV读取本质是Range:

  • 精确Key:key到该Key范围。
  • Prefix:计算前缀的结束边界。
  • 范围:[key, range_end)
  • 可指定历史revision读取旧快照,只要该revision未被Compaction。
  • 可设置limit、排序、只取keys/count等选项。

大前缀不能无限一次性返回。生产客户端应分页并记录整次List的快照revision,避免边分页边读取不同revision形成不一致集合。

九、默认线性一致读为什么更贵

etcd v3 Range默认提供线性一致语义。接收请求的Member需要确认当前Leader仍有多数派领导权,并确保本地状态机应用到安全读位置,常涉及ReadIndex/Leader协调,而不是随便读取Follower本地Backend。

线性一致读保证该读可放进全局实时顺序,不会返回已经被一个明确完成写覆盖的旧状态。

mermaid
flowchart TD
    A["Client发起线性一致Range"] --> B["Member向Leader确认安全读位置"]
    B --> C["Leader确认仍拥有Quorum领导权"]
    C --> D["等待本地applied index达到安全位置"]
    D --> E["从MVCC读取并返回"]

十、Serializable读是什么

设置Serializable选项后,Member可以从本地状态读取,不必完成线性一致读的Leader确认,延迟更低、可扩展性更好,但可能返回落后值。

这里的Serializable不是数据库“可串行化事务隔离级别”的同义词。它表示结果符合某个已提交串行历史,但可能不是最新线性一致状态。

适用:

  • 容忍短暂陈旧的监控展示。
  • 大规模只读缓存预热。
  • 明确不用于所有权裁决的查询。

不适用:

  • 获取锁后确认当前持有者。
  • Leader选举裁决。
  • 权限或配置发布的关键前置检查。
  • 资金、唯一状态转换。

十一、Txn比较—成功—失败模型

etcd Txn由三部分组成:

text
IF compares全部成立
THEN 执行success operations
ELSE 执行failure operations

可比较:

  • version
  • create_revision
  • mod_revision
  • value。
  • lease。

可执行Range、Put、Delete和受版本限制的组合操作,具体嵌套/操作数量受API和Server限制。

11.1 原子创建

text
IF version(/jobs/2026-07-18/order) == 0
THEN PUT owner=request-1001
ELSE GET existing owner

只有一个并发请求进入success分支,其他请求直接读取已有结果,避免“先GET不存在、再PUT”的竞态。

11.2 防并发覆盖

text
IF mod_revision(/config/order) == 100
THEN PUT new-config
ELSE GET current-config

失败分支应返回当前值和revision,让调用方合并或提示冲突;不能退化成无条件Put。

11.3 etcd Txn不能包含什么

Txn只覆盖一个etcd集群内的KV操作,不能原子包含MySQL、Kafka、HTTP或Redis。跨系统仍需要Outbox、幂等、状态机和补偿。

十二、Watch事件流原理

Watch订阅Key或Range从某revision之后的变化。事件通常包含:

  • PUT或DELETE类型。
  • Key和新值。
  • create/mod revision、version、lease。
  • 可选前值 prev_kv
  • Watch响应Header中的集群revision。

同一事务修改多个Key时会产生同revision的多个事件。消费者应按revision和事务语义处理,不能假设每个事件都有独立revision。

十三、正确的List-Watch闭环

如果直接先List再从“当前时间”Watch,List和Watch之间可能漏变化。正确流程:

mermaid
flowchart TD
    A["Range/List读取完整前缀快照"] --> B["保存响应Header Revision=R"]
    B --> C["原子发布本地快照"]
    C --> D["从R+1建立Watch"]
    D --> E["按revision应用PUT/DELETE"]
    E --> F{"Watch是否断开或被Compaction"}
    F -- "普通断开" --> D
    F -- "Compacted" --> A

分页List必须保持同一历史revision:第一页响应给出R,后续页指定revision=R;否则翻页期间新增/删除Key会造成重复、遗漏或集合混合。

十四、Watch断开怎样恢复

客户端保存最后完整应用的revision lastAppliedRevision

  1. 流断开后,从 lastAppliedRevision + 1 重新创建Watch。
  2. 事件处理和revision持久化必须有正确原子边界。
  3. 重复事件按revision/业务键幂等吸收。
  4. 若Server返回requested revision已compacted,不能无限重连同一revision,必须重新List全量快照。

Watch进度通知可以帮助客户端知道流仍存活和当前revision,但不是业务变更事件。

十五、Compaction做什么

MVCC保存历史版本会持续增长。Compaction声明某revision及之前的旧历史不再可读/Watch恢复,仅保留当前状态和较新历史。

text
历史revision: 1 ... 1000 ... 5000
compact at 4000
可恢复Watch: > 4000
请求revision 3000: ErrCompacted

Compaction是逻辑历史压缩,不保证Backend文件立刻缩小。运行过慢、长时间离线的Watch客户端必须具备“ErrCompacted → 全量List → 从新R+1 Watch”路径。

15.1 自动Compaction

etcd支持按时间窗口或revision保留策略自动压缩,具体配置和解释随版本。保留过短会让暂时离线控制器频繁全量List;保留过长会扩大Backend、备份和维护成本。

选择依据:

  • 最长允许离线时间。
  • Watch消费者恢复能力。
  • 写入revision增长速率。
  • Backend配额和Snapshot窗口。

十六、Defrag为什么和Compaction不同

操作作用是否回收物理空间
Compaction删除旧revision的逻辑可访问历史不一定立即缩小DB文件
Defrag重建/整理Backend空闲页可降低物理DB占用

Defrag针对单个Member执行,可能阻塞该Member的读写或造成明显延迟,具体行为随版本。生产应逐Member进行,先处理Follower/非Leader,观察健康和追平后再继续,避免一次对所有Endpoint并发Defrag。

十七、Lease与TTL全过程

mermaid
flowchart TD
    A["Client请求Grant TTL"] --> B["etcd返回Lease ID和实际TTL"]
    B --> C["Put Key并attach Lease"]
    C --> D["Client通过KeepAlive流续租"]
    D --> E{"续租是否持续成功"}
    E -- "是" --> D
    E -- "否且超过TTL" --> F["Leader判定Lease过期"]
    F --> G["删除该Lease附着的Keys"]
    G --> H["产生DELETE Watch事件"]

一个Lease可以绑定多个Key。主动Revoke会撤销Lease并删除附着Key。Key可以重新绑定Lease,具体Put选项要明确。

17.1 Lease不是精确定时器

  • Grant返回的TTL可能经过Server处理,不应只信请求值。
  • KeepAlive响应证明续租在某个时点成功,不保证未来网络正常。
  • Leader变化和调度会影响过期处理时刻。
  • Client进程暂停但KeepAlive线程也暂停,会导致Lease过期。
  • Key删除通知有提交和Watch传播延迟。

支付关单和订单超时应使用数据库截止时间与扫描/延迟消息,Lease仅适合协调性存活标记。

十八、用Prefix、Lease、Watch做服务发现

数据模型:

text
/services/stock/instance-a -> {host,port,zone,protocol}
/services/stock/instance-b -> {host,port,zone,protocol}

每个实例Key绑定Lease并KeepAlive;Consumer执行List-Watch:

  1. Prefix Range取得完整实例快照和revision R。
  2. 从R+1 Watch PUT/DELETE。
  3. 原子更新本地不可变地址快照。
  4. Lease过期产生DELETE后移除实例。
  5. Watch compacted则重新List。

KeepAlive只能证明注册客户端仍能续租,不证明业务端口、数据库和线程池健康。Provider应把readiness与租约注册结合,Consumer仍需超时、熔断和被动失败统计。

十九、etcd分布式锁原理

etcd官方并发Recipe通常使用Session Lease、锁前缀、事务和创建revision建立排队:

text
/locks/inventory/<lease-or-generated-key>

概念过程:

  1. 创建Session和Lease。
  2. 在锁前缀下Txn创建候选Key。
  3. 以create_revision排序。
  4. 最小revision持锁。
  5. 其他竞争者Watch比自己更早的候选删除。
  6. Session结束或Lease过期删除Key。

19.1 为什么仍需要Fencing Token

Client A取得锁revision 101后发生长暂停,Lease过期;Client B取得新锁revision 102并写数据库;A恢复后仍可能继续旧代码。

外部资源必须原子拒绝旧Token:

sql
UPDATE resource_state
SET value = :value,
    fencing_token = :token
WHERE resource_id = :id
  AND fencing_token < :token;

锁Key的create revision可作为候选Token,但必须保证同一集群、同一所有权语义,并由外部资源校验。相同Token的重复请求仍需要业务幂等。

二十、业务选主

Election Recipe与锁相似:候选Key绑定Lease,create revision最小者成为Leader,Leader值可被观察。Leader回调不保证任务Exactly Once:旧Leader可能执行一半后Lease失效,新Leader接管后重复执行。

任务需要:

  • 稳定业务任务键。
  • PENDING/RUNNING/SUCCEEDED状态机。
  • Fencing Token。
  • 接管前事实扫描。
  • 幂等和对账。

二十一、Java客户端版本边界

etcd v3 API基于gRPC。Java常用jetcd等客户端,但不同客户端版本对JDK、Netty、gRPC、TLS和etcd Server版本的要求不同:

  • 存量JDK 8项目应锁定明确兼容版本并做依赖收敛。
  • Java 17+项目应选择仍维护且与框架兼容的版本。
  • 不要手工覆盖Netty/gRPC版本破坏客户端BOM。
  • Spring Boot 2与3项目还要检查javax/jakarta无关但Netty/SLF4J相关的二进制兼容。

概念代码:

java
try (Client client = Client.builder()
        .endpoints("https://etcd-1:2379", "https://etcd-2:2379", "https://etcd-3:2379")
        .build()) {
    KV kv = client.getKVClient();
    ByteSequence key = ByteSequence.from("/config/order", StandardCharsets.UTF_8);
    GetResponse response = kv.get(key).get(3, TimeUnit.SECONDS);
    long revision = response.getHeader().getRevision();
    System.out.println("revision=" + revision);
}

生产必须补TLS证书、认证、Deadline、Endpoint故障切换和受控Executor;示例依赖版本应按项目JDK矩阵选择。

二十二、JDK 8 Demo:全局revision与Txn CAS

下面用无依赖模型演示:一次Txn修改两个Key只推进一次全局revision,而Key version分别变化。

java
import java.util.LinkedHashMap;
import java.util.Map;

public class EtcdMvccTxnDemo {

    static final class Entry {
        final String value;
        final long createRevision;
        final long modRevision;
        final long version;

        Entry(String value, long createRevision, long modRevision, long version) {
            this.value = value;
            this.createRevision = createRevision;
            this.modRevision = modRevision;
            this.version = version;
        }
    }

    static final class Store {
        private final Map<String, Entry> data = new LinkedHashMap<String, Entry>();
        private long revision;

        synchronized boolean txnPutIfModRevision(String compareKey,
                                                 long expectedModRevision,
                                                 Map<String, String> updates) {
            Entry compared = data.get(compareKey);
            long actual = compared == null ? 0L : compared.modRevision;
            if (actual != expectedModRevision) return false;

            long nextRevision = ++revision;
            for (Map.Entry<String, String> update : updates.entrySet()) {
                Entry old = data.get(update.getKey());
                long create = old == null ? nextRevision : old.createRevision;
                long version = old == null ? 1L : old.version + 1L;
                data.put(update.getKey(),
                        new Entry(update.getValue(), create, nextRevision, version));
            }
            return true;
        }
    }

    public static void main(String[] args) {
        Store store = new Store();
        Map<String, String> first = new LinkedHashMap<String, String>();
        first.put("/config/order", "timeout=1000");
        first.put("/audit/order", "created");
        System.out.println("first=" + store.txnPutIfModRevision("/config/order", 0L, first));

        Entry config = store.data.get("/config/order");
        Entry audit = store.data.get("/audit/order");
        System.out.println("sameTxnRevision=" + (config.modRevision == audit.modRevision));

        Map<String, String> stale = new LinkedHashMap<String, String>();
        stale.put("/config/order", "timeout=3000");
        System.out.println("staleCompare=" + store.txnPutIfModRevision("/config/order", 0L, stale));
        System.out.println("configVersion=" + config.version + ", globalRevision=" + config.modRevision);
    }
}

预期输出:

text
first=true
sameTxnRevision=true
staleCompare=false
configVersion=1, globalRevision=1

二十三、etcdctl最小实验:真实观察Revision、Txn、Watch和Lease

所有生产命令都应显式指定受信任Endpoint、CA、客户端证书和Deadline。下面使用本地实验环境简写;不要把无TLS写法直接复制到生产。

23.1 查看Endpoint与Leader

bash
export ETCDCTL_API=3
etcdctl --endpoints=http://127.0.0.1:2379 endpoint status --write-out=table
etcdctl --endpoints=http://127.0.0.1:2379 endpoint health

生产TLS形式:

bash
etcdctl \
  --endpoints=https://etcd-1:2379,https://etcd-2:2379,https://etcd-3:2379 \
  --cacert=/etc/etcd/pki/ca.crt \
  --cert=/etc/etcd/pki/client.crt \
  --key=/etc/etcd/pki/client.key \
  endpoint status --cluster --write-out=table

endpoint health成功不等于业务Prefix、Lease和Watch全部正常,还要检查DB/quota、alarm和目标数据。

23.2 Put/Get并观察Revision

bash
etcdctl put /config/order 'timeout=1000'
etcdctl get /config/order --write-out=json
etcdctl put /config/order 'timeout=1500'
etcdctl get /config/order --write-out=json

比较响应中的Header revision、KV create_revision、mod_revision和version。第二次Put通常保持create_revision,推进mod_revision和version。

23.3 线性读与Serializable读

bash
# 默认线性一致Range
etcdctl get /config/order

# 允许从本地已提交状态读取,可能陈旧
etcdctl get /config/order --consistency=s

命令参数形式可能随etcdctl版本变化,使用前执行 etcdctl get --help。不要仅凭延迟不同判断读取来自Leader还是Follower,要结合Endpoint和指标。

23.4 Txn原子创建

交互输入示意:

bash
etcdctl txn

依次输入:

text
version("/jobs/2026-07-18/order") = "0"

put /jobs/2026-07-18/order request-1001

get /jobs/2026-07-18/order

含义是:Key不存在则创建,否则读取已有值。空行用于结束Compare、Success和Failure各段;具体交互格式以当前版本帮助为准。

23.5 从Revision建立Watch

先取得完整快照及Header revision R:

bash
etcdctl get /services/stock/ --prefix --write-out=json

再从R+1开始:

bash
etcdctl watch /services/stock/ --prefix --rev=101

另一个终端写入:

bash
etcdctl put /services/stock/instance-a '10.0.0.11:20880'
etcdctl del /services/stock/instance-a

生产程序必须自动保存并恢复revision;命令中的101只是教学示例。

23.6 Lease注册和续租

bash
etcdctl lease grant 30
etcdctl put /services/stock/instance-a '10.0.0.11:20880' --lease=<LEASE_ID>
etcdctl lease timetolive <LEASE_ID> --keys
etcdctl lease keep-alive <LEASE_ID>

停止KeepAlive并等待Lease过期后,Key会被删除并产生Watch DELETE事件。不要把实验观察到的删除秒数当作永久精确定时承诺。

23.7 Compaction、Defrag和Alarm

bash
etcdctl compact <SAFE_REVISION>
etcdctl endpoint status --cluster --write-out=table
etcdctl defrag --endpoints=https://etcd-follower-1:2379
etcdctl alarm list

必须先确定安全revision,Defrag逐Member执行并使用TLS参数。不能在未备份、不了解Watch恢复窗口时复制一个revision直接Compact。

二十四、Backend配额与NOSPACE告警

Backend物理DB达到 quota-backend-bytes 附近时,etcd会触发NOSPACE告警并限制会扩大状态的写入,以保护集群。

恢复不能只扩大磁盘:

  1. 检查endpoint status中的DB size和in-use size。
  2. 确认写入增长来源和Compaction策略。
  3. 对安全revision执行Compaction。
  4. 逐Member Defrag回收物理空间。
  5. 确认DB降到配额内。
  6. 再解除NOSPACE alarm。
  7. 验证Watch、Lease和业务控制器恢复。

在未回收空间前反复disarm alarm,只会让告警再次出现。

二十五、成员变更与Learner

成员变更是Raft配置变化,应一次操作一个成员并保持多数派。

推荐思想:

mermaid
flowchart TD
    A["确认Cluster健康和Snapshot"] --> B["新增Learner"]
    B --> C["等待Learner追平Leader"]
    C --> D["Promote为Voting Member"]
    D --> E["确认Quorum和延迟稳定"]
    E --> F["再移除旧Member"]

Learner支持和数量限制随etcd版本变化。它不参与投票,避免尚未追平的新节点立即改变Quorum。不能一次替换多数成员,也不能把复制data-dir当作成员加入。

二十六、Snapshot备份

使用受支持的 etcdctl snapshot save 或客户端维护API从健康Endpoint获取Snapshot,并用对应工具检查状态。备份还应保存:

  • etcd精确版本。
  • 成员和Endpoint拓扑。
  • TLS证书、CA和认证配置。
  • 启动参数与配额。
  • Kubernetes场景下的兼容信息和加密配置。

定期Snapshot不是完成灾备;必须在隔离环境执行Restore演练并测量RTO。

二十七、Restore为什么会创建新逻辑集群

Restore从Snapshot生成新的Member数据目录、Cluster/Member身份和启动配置,不能直接覆盖正在运行的旧data-dir后让节点“自动继续”。

Kubernetes等大量使用Watch缓存的系统恢复旧Snapshot后,revision可能回退,控制器本地缓存却记得更高resourceVersion。较新etcd恢复流程支持revision bump和标记compact等机制来迫使Watch消费者重建;具体命令和要求必须按etcd/Kubernetes版本官方灾备文档执行。

恢复后要验证:

  • 所有Member形成新Quorum。
  • revision/compact策略满足消费者恢复。
  • API Server、控制器和Watch重新List。
  • Lease和临时注册按业务重新建立。
  • 不把旧集群Member误连到新集群。

二十八、安全边界

  • Client端启用TLS并校验Server证书。
  • Peer通信启用双向TLS,防止伪造Member。
  • 启用用户/RBAC,按Prefix最小授权。
  • 证书轮换要滚动验证,避免同时过期失去Quorum。
  • etcd值默认不能被假定为磁盘加密;使用磁盘加密或上层信封加密。
  • Kubernetes Secret的Base64不是加密,需配置API Server静态加密/外部KMS。
  • 2379/2380等端口只在控制网络开放。
  • 日志、命令和指标不输出密钥值和认证Token。

二十九、关键监控指标

Raft和集群

  • has leader、leader changes。
  • proposals committed/applied/pending/failed。
  • Member applied index与Leader差距。
  • peer round-trip time。

磁盘与Backend

  • WAL fsync duration。
  • backend commit duration。
  • DB physical size和in-use size。
  • quota使用率、NOSPACE alarm。
  • Snapshot和Defrag耗时。

API与业务

  • gRPC request duration和错误码。
  • Range/Txn/Watch/Lease QPS。
  • Watch stream数量、慢Watcher、compacted恢复次数。
  • Lease grant/keepalive/expired数量。
  • Client重试、DeadlineExceeded和Unavailable。

WAL fsync和Backend commit的P99通常比平均值更能解释控制面尾延迟。

三十、Runbook:没有Leader或Leader频繁变化

  1. 检查投票成员可达数是否达到Quorum。
  2. 检查Peer TLS证书、时间有效期和SAN。
  3. 检查2380网络RTT、丢包和防火墙。
  4. 查看WAL磁盘fsync、磁盘满和IO错误。
  5. 检查CPU throttling、长GC和宿主机停顿。
  6. 检查是否同时重启或移除多数Member。
  7. 不要清空data-dir或强制创建多个独立单节点集群。

三十一、Runbook:请求慢和apply request took too long

沿链路拆解:

text
Client排队/网络
+ Leader ReadIndex或Raft提议
+ WAL fsync和多数派复制
+ Apply等待
+ Backend commit
+ 大Range序列化

检查WAL和Backend P99、peer RTT、proposals pending、applied lag、大Txn、大Prefix Range、Watch扇出、Compaction/Defrag和宿主机资源。增加客户端超时不会消除存储瓶颈。

三十二、Runbook:Watch报required revision has been compacted

  1. 停止从旧revision无限重连。
  2. 对目标Prefix执行分页List,固定同一最新revision R。
  3. 原子替换本地快照。
  4. 从R+1重新Watch。
  5. 监控全量重建耗时和内存。
  6. 若频繁发生,检查消费者离线时间、处理积压和Compaction保留窗口。

三十三、Runbook:Lease意外过期

  1. 查KeepAlive最后成功时间和响应TTL。
  2. 检查客户端进程暂停、GC、Executor阻塞。
  3. 检查Client到所有Endpoint网络和TLS。
  4. 查看Leader变化和集群请求延迟。
  5. 确认Key确实绑定目标Lease。
  6. 新Session重建Key前先撤销旧业务所有权并取得新Token。

三十四、Runbook:DB碎片率高

当physical DB远大于in-use:

  1. 确认已经对安全历史执行Compaction。
  2. 备份并确认Cluster健康。
  3. 逐个Member执行Defrag。
  4. 每次等待Member恢复和applied index追平。
  5. Leader最后处理或按版本推荐流程切换。
  6. 观察延迟和NOSPACE状态。

三十五、Runbook:成员数据损坏或Hash不一致

使用endpoint status/health和hash检查定位异常Member;保留日志和数据副本,不要把损坏data-dir复制到其他节点。若仍有健康Quorum,按官方流程移除异常Member、以Learner重新加入并追平。多数数据都损坏时进入Snapshot灾备,不能凭单个旧节点任意成为新事实源。

三十六、etcd、ZooKeeper、Consul、Redis和Nacos比较

组件核心模型典型优势主要边界
etcdRaft + MVCC KV + revision/Watch/Lease云原生控制面、强一致TxnBackend配额、Compaction、磁盘要求
ZooKeeperZAB + znode + Session/Watch成熟协调Recipe、传统Dubbo内存树、Watcher和Session语义
ConsulRaft Catalog/KV + Agent + Gossip服务发现、健康检查、多DCGossip与Raft职责需区分
Redis内存数据结构 + 复制/Cluster高吞吐缓存和原子脚本普通复制不是强一致共识
Nacos服务发现 + 配置Spring Cloud Alibaba集成临时/持久实例和一致性不能粗暴一概而论

三十七、etcd在Kubernetes中的边界

正常组件通过API Server访问Kubernetes对象,不直接读写etcd,因为API Server负责:

  • 认证和授权。
  • Admission。
  • 默认值和校验。
  • API版本转换。
  • 审计。
  • 对象并发控制。
  • Secret加密转换。

Kubernetes resourceVersion与底层存储revision有关,但API语义将其视为不透明并发/Watch标识。业务代码不应把不同资源的resourceVersion当普通整数做自定义全局排序,也不应绕过API Server修改etcd。

三十八、常见错误及后果

错误后果
revision等于Key versionCAS和Watch恢复错误
revision等于Raft log index混淆共识与MVCC层
Serializable读等于数据库可串行化用陈旧值做所有权裁决
Watch断开总从旧revision重连Compaction后永久失败循环
Compaction会自动缩小DB文件NOSPACE仍然存在
并发Defrag所有Member集群请求停顿和失去可用性风险
Lease是精确定时器订单关闭延迟或错误
KeepAlive成功等于业务健康注册存在但Provider线程池已死
etcd锁不需要Fencing暂停旧客户端覆盖新持有者
直接改Kubernetes的etcd绕过API校验并破坏控制器假设

三十九、面试标准回答

etcd使用Raft让多数Member对写命令顺序达成一致,提交后Apply到MVCC并生成全局revision;一次Txn修改多个Key可共享一个revision。每个Key还有create_revision、mod_revision、version和lease,不能混用。Range默认线性一致,需要确认Leader/安全读位置;Serializable读可本地返回但可能陈旧。正确Watch先List得到header revision R,再从R+1订阅;若旧revision已Compaction,必须重新List,不能无限重连。Compaction删除旧MVCC历史的逻辑可见性,Defrag才回收Backend物理空页。Lease通过KeepAlive维护协调性存活,但过期不精确,锁和选主仍需外部Fencing与业务幂等。生产重点监控Leader、Raft pending/applied、WAL fsync、Backend commit、DB/quota、Watch恢复和Lease过期,并通过Snapshot与逐成员恢复保障灾备。

四十、关联知识点

本章小结

etcd的完整链路是“Raft提交命令 → Apply到MVCC → 全局revision推进 → Txn原子裁决 → Watch增量传播 → Lease管理协调性生命周期”。生产系统还必须处理Compaction后的全量重建、Defrag、Quota、成员追平、Snapshot恢复和旧持有者Fencing。只会Put/Get或说“etcd是Kubernetes数据库”,还不能解释它为什么可靠、怎样失败和如何恢复。