etcd 深度解析:Go 分布式一致性 KV 存储与 Raft 共识实战

1. 背景

1.1 为什么需要 etcd

在工业数采这类多网关、多服务的分布式场景中,单体应用里用一台数据库就能解决的"配置、选主、互斥"问题,在分布式环境下会变成三个经典痛点:

  1. 配置中心缺失:采集网关、边缘服务、中心平台各自维护配置文件,灰度下发、热更新、版本回滚全靠人肉,改一处配置要重启一批进程。
  2. 选主与高可用难:多台采集网关同时采集同一批 CNC/PLC 点位会造成重复上报;需要"同一时刻只有一个主节点干活"的机制,但主节点宕机后谁来接管、如何避免双主脑裂?
  3. 分布式互斥缺失:多网关共享某台设备的命令下发通道时,需要一把"跨进程、跨机器"的锁------单机内存锁(mutex)和单机 Redis 锁都只能覆盖单进程或单机视角,无法解决多机间的互斥。

etcd(读作 et-cd)正是为解决这些问题而生的:一个分布式的、强一致性的键值存储系统,由 CoreOS 于 2015 年开源(现为 CNCF 毕业项目),是 Kubernetes 的"大脑"(保存集群全部状态)。它把 Raft 共识算法作为分布式一致性的地基,对外提供类 gRPC 的 KV、Watch、Lease、事务 API。

1.2 定位与选型

系统 类型 一致性 适用场景 与 etcd 的关系
etcd 分布式 KV,小数据量、高可靠 Raft 强一致 配置中心、服务发现、分布式锁、选主 本文主角
Redis 单机内存 KV 主从弱一致 / 单机强一致 缓存、实时最新值、队列 与 etcd 互补:缓存层用 Redis,元数据/协调层用 etcd
RocksDB 嵌入式 LSM KV 单机 海量数据持久化 存储引擎层面,无分布式
Zookeeper 分布式协调(树形 znode) ZAB 强一致 同类协调场景 Java 生态对照物,etcd 是 Go 现代替代
Consul 服务网格/服务发现 Raft 服务注册发现 功能有重叠,etcd 更聚焦 KV + Watch

核心选型结论 :缓存/历史数据用 Redis、TDengine;协调元数据(谁在干活、配置是什么、谁有锁)用 etcd------数据量通常只有几十 MB,但必须强一致、可 Watch、可续租。

2. etcd 核心概念速览

2.1 架构全景

复制代码
        ┌──────────────────────────────────────────────┐
        │              etcd 集群(3/5 节点)            │
        │  ┌──────────┐  ┌──────────┐  ┌──────────┐   │
        │  │ Node A   │  │ Node B   │  │ Node C   │   │
        │  │ Leader   │←→│ Follower │←→│ Follower │   │
        │  │  Raft    │  │  Raft    │  │  Raft    │   │
        │  └──────────┘  └──────────┘  └──────────┘   │
        └───────┬──────────────────┬──────────────────┘
                │ gRPC             │ gRPC
        ┌───────┴─────────┐ ┌──────┴─────────────┐
        │ 采集网关 A       │ │ 采集网关 B          │
        │ clientv3        │ │ clientv3           │
        │锁/选主/配置 Watch│ │ 锁/选主/配置 Watch  │
        └─────────────────┘ └────────────────────┘
  • Raft 层:负责日志复制与选主,保证多节点状态一致(强一致)。
  • 存储层:内存 B+tree 索引 + 底层 boltdb 持久化,MVCC 多版本控制。
  • 传输层:gRPC(HTTP/2)+ 可选的 gRPC-gateway(提供 HTTP/JSON 兼容接口)。

2.2 Raft 共识(etcd 的核心地基)

Raft 把一致性问题拆解为三个子问题:

  1. Leader 选举:每个节点有 Leader / Follower / Candidate 三种角色。Follower 在选举超时(etcd 默认 1s)内没收到 Leader 心跳,就变成 Candidate 发起选举,获得多数派投票即成为 Leader。
  2. 日志复制 :所有写请求只发给 Leader;Leader 把操作包装成日志条目,复制给 Follower,过半节点持久化成功后才提交(commit)并返回客户端------这就是强一致的来源。
  3. 安全性:通过 Term(任期号)+ 日志索引比较,保证已提交的日志不会被覆盖、Leader 一定包含所有已提交日志。

etcd 中的 Raft 变形 :etcd 使用 raft(etcd 作者维护的独立 raft 库)实现了 Raft 的PreVote(预投票) 、CheckQuorum 、Learner 节点(只复制日志不参与投票,用于滚动扩容)等优化,并把日志条目直接序列化为 etcd 的业务操作(Put/Delete/Txn 等),由应用层执行后同步到状态机。

2.3 MVCC 多版本并发控制

etcd 的每个 key 变更都会产生新版本号(revision,全局单调递增),旧版本数据保留一段时间(默认 compaction 保留历史 5000 个版本)。好处:

  • Watch 增量推送:客户端 Watch 某个 key 后,服务端按 revision 推送后续变更,客户端重连时可用 WithRev 从断点续传,不会漏事件。
  • 读一致性:客户端可指定读取某个历史 revision 的快照(WithRev / WithPrefix 读旧版本)。

2.4 Watch 机制

Watch 是 etcd 与普通 KV 库最大的差异点之一:客户端对 key 建立长连接,服务端在该 key 的每次变更时实时推送事件(Put/Delete 及对应 value、prev_kv)。这让"配置热更新"变成天然的推送模型:网关不用轮询配置,配置一变立刻收到。

2.5 Lease 租约

Lease(租约)是一段时间的"有效期":创建 Lease 后,把 key 绑定到 Lease 上,Lease 到期而未被续租时,其下所有 key 自动删除。用途:

  • 分布式锁的"锁自动释放"(持有者崩溃后锁不再死锁);
  • 服务注册的"健康检查"(进程挂掉自动摘除节点);
  • 临时配置/会话。

2.6 事务与分布式锁原语

  • Txn(事务) :If → Then → Else 三段式条件事务,基于 revision / value / 键是否存在做条件判断,由 Leader 在同一个 Raft 日志条目中原子执行------这是实现分布式锁的基石。
  • 分布式锁:官方提供 concurrency 包,基于 Txn + Lease + Watch 实现 Mutex(排它锁)、RWMutex、Session 和 Election(选主),比手写 SETNX 更健壮(支持续租、可重入语义、公平排队)。

3. 核心 API 说明(go.etcd.io/client/v3)

安装:

复制代码
go get go.etcd.io/etcd/client/v3

3.1 客户端连接

Go 复制代码
cli, err := clientv3.New(clientv3.Config{
    Endpoints:   []string{"localhost:2379", "localhost:2380"}, // 可配多个端点做故障转移
    DialTimeout: 5 * time.Second,
    // 生产必配:请求级超时、TLS、认证
    Username: "root",
    Password: "root123",
})
  • Endpoints 可传多个;clientv3 内部有 grpc 连接池 + 负载均衡(RoundRobin),会自动剔除不可用节点。
  • 返回的 *clientv3.Client 是并发安全的,全局共享一个即可,不要每请求创建一个。

3.2 KV 操作

Go 复制代码
kv := clientv3.NewKV(cli)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// Put
resp, err := kv.Put(ctx, "/config/gateway/rate", "100", clientv3.WithLease(leaseID))
// Get
gresp, err := kv.Get(ctx, "/config/gateway/rate")          // 单 key
gresp, err = kv.Get(ctx, "/config/gateway/", clientv3.WithPrefix()) // 前缀
gresp, err = kv.Get(ctx, "/config/gateway/", clientv3.WithFromKey(), clientv3.WithLimit(100)) // 从某 key 开始的 100 条
// Delete
dresp, err := kv.Delete(ctx, "/config/gateway/", clientv3.WithPrefix())
// Txn 条件事务
txn := kv.Txn(ctx)
txn.If(clientv3.Compare(clientv3.CreateRevision("/lock/gw1"), "=", 0)).
    Then(clientv3.OpPut("/lock/gw1", "1", clientv3.WithLease(leaseID))).
    Else(clientv3.OpGet("/lock/gw1"))
tresp, err := txn.Commit()

关键返回值字段:resp.Kvs(每个 *mvccpb.KeyValue 含 Key/Value/CreateRevision/ModRevision/Version/Lease)、resp.Header.Revision(本次操作的全局版本号,Watch 续传用)。

3.3 Watch

Go 复制代码
watch := clientv3.NewWatch(cli)
// 阻塞式接收变更事件
wch := watch.Watch(ctx, "/config/gateway/", clientv3.WithPrefix())
for wresp := range wch {
    for _, ev := range wresp.Events {
        switch ev.Type {
        case mvccpb.PUT:
            fmt.Printf("PUT %s = %s (rev %d)\n", ev.Kv.Key, ev.Kv.Value, ev.Kv.ModRevision)
        case mvccpb.DELETE:
            fmt.Printf("DELETE %s\n", ev.Kv.Key)
        }
    }
}
  • WithPrefix() 监听前缀下所有 key;WithRev(rev) 从指定历史版本开始监听(断线续传)。
  • Watch 是事件流:服务端推送事件后客户端不必再 Get 一次,直接消费即可。

3.4 Lease

Go 复制代码
lease := clientv3.NewLease(cli)
// 创建 10 秒租约
lresp, err := lease.Grant(ctx, 10)
// 续租:后台循环 KeepAlive,keepAliveCh 收到响应即续租成功
kaCh, err := lease.KeepAlive(ctx, lresp.ID)
go func() {
    for range kaCh { /* 每 ~1/3 TTL 收到一次续租确认 */ }
}()
// 绑定 key 到租约
kv.Put(ctx, "/lock/gw1", "1", clientv3.WithLease(lresp.ID))
// 撤销租约(立即删除其下所有 key)
lease.Revoke(ctx, lresp.ID)

3.5 分布式锁与选主(concurrency 包)

Go 复制代码
sess, err := concurrency.NewSession(cli, concurrency.WithTTL(10)) // 自动续租的会话
defer sess.Close()

// 排它锁
m := concurrency.NewMutex(sess, "/locks/gw1")
if err := m.Lock(ctx); err != nil { /* 获取失败 */ }
defer m.Unlock(ctx)
// 锁内做互斥业务...

// 选主:同一前缀下只有一个节点成为 Leader
e := concurrency.NewElection(sess, "/election/collector")
if err := e.Campaign(ctx, "gateway-A"); err == nil {
    // 我是主节点
}
// 其他节点可 e.Observe(ctx) 观察当前主节点、e.Resign(ctx) 退位

concurrency 包内部实现:Mutex.Lock 用 Txn + CreateRevision == 0 抢 key,失败则 Watch 前一个持有者删除事件后重试,天然公平排队(FIFO);Session 自动 KeepAlive,会话过期锁自动释放,避免死锁。

3.6 集群维护与鉴权

Go 复制代码
// 用户与角色(生产环境必须开启认证)
auth := clientv3.NewAuth(cli)
auth.UserAdd(ctx, "root", "root123")
auth.RoleAdd(ctx, "readonly")
auth.UserGrantRole(ctx, "app", "readonly")
auth.AuthEnable(ctx)

// 维护:成员列表、状态、快照
cli.MemberList(ctx)
cli.Status(ctx, endpoint)
cli.Snapshot(ctx) // 备份

4. 详细使用说明(可运行示例)

以下示例均基于 go.etcd.io/client/v3,配套本地单节点(etcd 二进制)或 3 节点集群运行。

4.1 最小读写闭环

Go 复制代码
package main

import (
    "context"
    "fmt"
    "time"
    clientv3 "go.etcd.io/etcd/client/v3"
)

func main() {
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"localhost:2379"},
        DialTimeout: 3 * time.Second,
    })
    if err != nil { panic(err) }
    defer cli.Close()

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    kv := clientv3.NewKV(cli)
    if _, err := kv.Put(ctx, "/demo/k", "v1"); err != nil { panic(err) }
    gresp, err := kv.Get(ctx, "/demo/k")
    if err != nil { panic(err) }
    for _, kvp := range gresp.Kvs {
        fmt.Printf("key=%s value=%s modRev=%d\n", kvp.Key, kvp.Value, kvp.ModRevision)
    }
}

4.2 配置中心:Watch 热更新(贴合工业数采)

采集网关订阅点位采集配置,配置变更实时生效、无需重启:

Go 复制代码
func WatchConfig(cli *clientv3.Client, prefix string) {
    rch := cli.Watch(context.Background(), prefix, clientv3.WithPrefix())
    for resp := range rch {
        for _, ev := range resp.Events {
            switch ev.Type {
            case mvccpb.PUT:
                applyConfig(string(ev.Kv.Key), string(ev.Kv.Value)) // 热更新到全局配置
            case mvccpb.DELETE:
                removeConfig(string(ev.Kv.Key))
            }
        }
    }
}

注意:Watch 只推送变更,服务启动时应先 Get(prefix, WithPrefix()) 拉全量配置,再进入 Watch 增量模式------避免漏掉启动前的变更。

4.3 分布式锁:多网关互斥命令下发

多台网关向同一台 CNC 下发写指令时,用 etcd 锁保证同一时刻只有一台在写:

Go 复制代码
sess, _ := concurrency.NewSession(cli, concurrency.WithTTL(10))
defer sess.Close()
m := concurrency.NewMutex(sess, "/locks/cnc/001")
if err := m.Lock(context.Background()); err != nil {
    log.Fatalf("lock failed: %v", err)
}
defer m.Unlock(context.Background())

// 临界区:写 PLC/CNC 点位、下发命令
writeCncPoint("001", "X100", 12.5)

4.4 选主:多网关主备采集

Go 复制代码
sess, _ := concurrency.NewSession(cli, concurrency.WithTTL(10))
defer sess.Close()
e := concurrency.NewElection(sess, "/election/collector/main")

// 尝试竞选主节点
if err := e.Campaign(context.Background(), "gateway-A"); err == nil {
    log.Println("I am the leader, start collecting")
    runCollector() // 阻塞直到退位/会话过期
    e.Resign(context.Background())
} else {
    log.Println("not leader, observing:", e.Leader(context.Background()))
}

4.5 事务:读改写原子操作(实现计数器/配额)

Go 复制代码
// 原子地"读-判断-写",全程无锁
for {
    gresp, _ := kv.Get(ctx, "/quota/conn")
    cur := 0
    if len(gresp.Kvs) > 0 { cur = parseInt(gresp.Kvs[0].Value) }
    if cur >= 100 { break } // 已达上限
    txn := kv.Txn(ctx).
        If(clientv3.Compare(clientv3.ModRevision("/quota/conn"), "=", gresp.Header.Revision)).
        Then(clientv3.OpPut("/quota/conn", strconv.Itoa(cur+1)))
    tresp, _ := txn.Commit()
    if tresp.Succeeded { break } // 事务成功,退出重试
    // 否则版本冲突,重读重试(乐观锁模式)
}

4.6 服务注册与健康检查

Go 复制代码
// 注册:key 绑定 Lease,TTL 到期自动摘除
leaseResp, _ := cli.Grant(ctx, 5)
_, _ = cli.Put(ctx, "/services/gw1", "10.0.0.1:9000", clientv3.WithLease(leaseResp.ID))
kaCh, _ := cli.KeepAlive(ctx, leaseResp.ID)
go func() { for range kaCh {} }()
// 其他服务 Watch "/services/" 即可实时感知服务上下线

4.7 快照备份与恢复

复制代码
# 备份(对集群一致性点做快照)
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F).db
# 恢复
etcdctl snapshot restore /backup/etcd-2026-10-08.db --data-dir /var/lib/etcd-new

5. 底层实现剖析

5.1 写路径全链路

复制代码
Client Put → gRPC 请求 → Leader 的 KV Server → raft 模块提议
→ 追加到本地 WAL(预写日志)→ 并行发送 AppendEntries 给 Follower
→ 过半节点持久化 ACK → 提交该日志条目 → 应用到状态机(boltDB + 内存索引)
→ 更新 MVCC 版本 → 通知 WatchableStore 推送事件 → 返回响应给客户端

关键点:客户端收到成功响应时,数据已经过半节点落盘------这就是强一致;任何节点读到的都是同一份已提交状态。

5.2 Raft 的日志与持久化

  • WAL(Write-Ahead Log):所有提议先追加到 WAL 文件,崩溃后靠 WAL 重放恢复未提交日志。etcd 默认 wal 目录 + 2 个文件(日志 + 元数据)。
  • Snapshot :日志无限增长会导致恢复时间无限长,etcd 定期(--snapshot-count 默认 10 万条日志)把当前状态机快照落盘并截断日志;Follower 落后太多时 Leader 直接发快照而非逐条补日志。
  • boltdb:etcd v3 用 bbolt(纯 Go B+tree)存 KV 数据,key 为 (revision, key) 组合,value 为历史版本数据;内存中再用 B+tree 索引 (key, revision) 到 value,读写路径高效。

5.3 MVCC 与版本管理

  • 每次写操作 revision 全局 +1(保存在 meta bucket)。
  • 内存索引结构:treeIndex(B-tree,key → 版本链 generation),查询"最新版本"走索引,读历史版本从 boltdb 按 revision 取。
  • Compaction:--auto-compaction-mode=revision --auto-compaction-retention=5000 定期清理历史版本,否则 boltdb 无限膨胀。

5.4 Watch 的实现

  • 每个 Watch 客户端在 WatchableStore 中注册 watcher(key + prefix + start revision)。
  • 服务端在每次 Put/Delete 提交后,遍历注册的 watcher,把匹配的事件同步追加到每个 watcher 的 eventChan,再经 gRPC 流推送给客户端。
  • 若 watcher 的 start revision 已被 compaction 清掉,服务端返回 ErrCompacted,客户端需用 WithRev 或 WithRequireLeader 重新同步------这是 Watch 最容易踩的坑之一。

5.5 Lease 的实现

  • Lease 存储在内存 leaseMap + 持久化到 boltdb 的 lease bucket。
  • Lessor 维护每个 lease 的到期时间;--heartbeat-interval(默认 500ms)驱动检查:到期 → 回收该 lease 下所有 key → 删除内存/持久化记录 → 触发对应 key 的删除事件。
  • KeepAlive 续租把到期时间向后延长;客户端崩溃不续租,TTL 到期锁/服务自动释放,不需要"看门狗"。

5.6 读一致性:线性读 vs 串行读

  • 串行读(默认):直接读本节点状态,快,但可能读到旧数据(Leader 切换瞬间)。
  • 线性读:Get(..., clientv3.WithSerializable(false)) 或默认 --read-consistency=linearizable:客户端向 Leader 发读请求,Leader 先与多数派确认自己仍是 Leader(读索引 ReadIndex),再返回------保证"读到最新已提交数据"。

6. 常错点/坑(22 条)

  1. 每次请求新建 Client:clientv3.Client 内部有连接池,全局复用;频繁新建导致连接泄漏、握手开销巨大。
  2. context 无超时:所有 API 都应传带超时/取消的 context,否则网络分区时请求会无限阻塞。
  3. Watch 只增量不拉全量:启动时先 Get 全量再 Watch 增量,否则漏掉启动前的配置。
  4. Watch 遇到 ErrCompacted :历史版本被 compaction 清理后 Watch 断点续传失败;正确做法是 Watch 失败后重新 Get 全量 + 从头 Watch。
  5. Lease 不 KeepAlive 导致锁/服务意外释放:锁、注册 key 必须配套后台 KeepAlive(concurrency.Session 已内置),否则 TTL 一到自动消失。
  6. KeepAlive 返回 nil channel:ctx 取消或 lease 已过期时 KeepAlive 返回的 channel 为 nil,for range nilCh 会永久阻塞------务必检查。
  7. 分布式锁忘 Unlock / 不处理会话过期:临界区 panic 前必须 defer Unlock;Session 过期后锁已失效但本地不知,需在业务里感知(观察锁事件)。
  8. Txn 条件写错 revision:乐观锁必须比较 ModRevision(当前版本)而非 CreateRevision 或旧值,否则无法实现 CAS。
  9. 多 key 需要原子性却用多次 Put:跨 key 原子写必须用 Txn(If().Then(OpPut...)),多次 Put 不保证原子。
  10. 值类型混用:etcd value 是 \[\]byte,存数值要自己定好序列化(strconv/JSON/protobuf),读写两端必须一致。
  11. 大 value 滥用:单 value 超过 1MB(默认 max-request-bytes 1.5MiB)会直接拒绝;配置类数据应保持 KB 级。
  12. 用 etcd 存海量业务数据:etcd 定位是协调元数据(几十 MB 级),大数据应放 Redis/TDengine/对象存储;强行当数据库用会拖垮 Raft 复制。
  13. Watch 前缀过宽:WithPrefix() 监听 / 会把集群所有变更推给自己,高负载下内存与带宽爆炸;按业务前缀收敛。
  14. 集群节点数选偶数:3 节点容忍 1 故障,5 节点容忍 2 故障;偶数节点既不提升容错又浪费(4 节点仍只能容忍 1 故障)。
  15. 不配 TLS/认证就上生产:默认无鉴权,内网被扫到即数据裸奔;生产必开 --client-cert-auth + RBAC。
  16. follower 直写:所有写请求只能走 Leader;客户端连多个 Endpoint 时 grpc 负载均衡会自动把请求转发到 Leader,但只配单端点时写会失败(etcdserver: no leader 是重试信号)。
  17. 忽略 cluster ID mismatch:用旧快照恢复到新集群时数据目录与集群 ID 不匹配,必须 snapshot restore 生成新目录而非直接拷贝。
  18. 快照恢复后客户端还连旧集群:恢复会产生新成员 ID,客户端 Endpoints 需更新,且旧数据目录要清理避免重复加入。
  19. compaction 策略缺失:不开自动 compaction,历史版本无限累积,boltdb 膨胀、查询变慢、磁盘占满。
  20. Watch 事件里 value 为 nil:DELETE 事件的 ev.Kv.Value 为 nil;要拿删除前的值必须 WithPrevKV()。
  21. 把锁 key 与配置 key 放同一前缀:Watch 配置时会把锁的变更也推过来,事件流被无关更新刷屏;锁/选举用独立前缀。
  22. 跨机房部署忽略延迟:Raft 要求过半节点 ACK,跨地域延迟会直接放大写延迟;etcd 集群节点应部署在同一低延迟机房。

7. 性能优化与工程实践

7.1 集群规划

  • 生产集群 3 节点起(容 1 故障),核心元数据 5 节点(容 2 故障);磁盘用 SSD。
  • --heartbeat-interval=100 --election-timeout=1000 可降低故障切换时间,但过小会误判(默认 500/2500ms 通常够用)。
  • 开启 --auto-compaction-mode=revision --auto-compaction-retention=5000。
  • 数据目录(--data-dir)与 WAL 分离盘(可选),监控 etcd_server_has_leader、etcd_server_leader_changes_seen_total。

7.2 客户端工程化清单

  • 全局单 Client + 多 Endpoints(自动故障转移)。
  • 所有调用带超时 context(读写 2~5s,锁内业务另算)。
  • 配置热更新:Get 全量 + Watch 增量 + ErrCompacted 后全量重同步。
  • 锁/选主/注册一律走 concurrency 包(Session 自动续租)。
  • 数据序列化统一:简单标量用 strconv,结构化用 JSON/protobuf。
  • 监控客户端指标:请求延迟、Watch 断连次数、锁等待时间。

7.3 工业数采链路落地建议

复制代码
采集网关 A/B/C(主备选主 election → 主节点采集)
        │ 点位配置 / 设备互斥锁 / 服务注册 / 心跳
        ▼
      etcd 集群(3 节点,强一致元数据层)
        ▲
      Kafka(历史数据流)/ TDengine(时序落库)→ 分析/AI
  • 选主:多网关通过 concurrency.Election 竞争采集权,主节点宕机自动切换备节点,杜绝重复采集。
  • 互斥:共享 CNC 写通道用 concurrency.Mutex,锁 TTL=10s + 自动续租。
  • 配置:采集点位表、采集周期、上下限告警阈值全部放 etcd,运维改配置网关秒级生效,无需重启。
  • 注册与发现:网关启动注册 /services/gw1(Lease 5s),平台 Watch /services/ 实时感知网关在线状态。

8. 总结

etcd 值得掌握的三句话:

  1. 强一致是它的灵魂:所有写操作经 Raft 过半 ACK 才返回,读支持线性读,保证分布式环境下"看到的配置/锁状态一定是全网一致的"。
  2. Watch + Lease 是它区别于普通 KV 的两把利器:配置推送、服务上下线感知、锁自动释放都建立在这两个机制上,是构建自愈分布式系统的关键原语。
  3. 只做协调,不做存储:它适合存"谁在干活、配置是什么、谁有锁"这种小数据高可靠元数据;数据本体交给 Kafka/TDengine/Redis。

上手建议:本地起 3 节点集群(etcd --initial-cluster 三进程)→ 跑完 4.1~4.6 示例 → 再看 5 章源码路径(raft、mvcc、wal、lease、watchable_store.go)→ 最后把 4.3/4.4 的锁与选主接入自己的采集网关。

FAQ 速查

  1. Q:etcd 和 Zookeeper 怎么选? A:Go 生态、gRPC、原生 Watch 用 etcd;存量 Java/树形 znode 体系用 ZooKeeper;功能定位几乎相同。
  2. Q:etcd 数据能存多大? A:官方建议几十 MB 到几 GB 级别;它是元数据存储,不是大数据存储。
  3. Q:写请求只能到 Leader 吗? A:是;但客户端配多个 Endpoint 时 grpc 层自动转发,应用层无感。
  4. Q:Watch 为什么会断? A:网络分区、客户端重启、服务端 compaction 清理了起始版本(ErrCompacted);都要做全量重同步。
  5. Q:锁的 TTL 设多少合适? A:大于临界区最坏执行时间,通常 5~15s;太短会误释放,太长故障恢复慢。
  6. Q:多节点集群最少几个? A:3 个(容忍 1 故障);2 个是反模式(任何 1 故障都写不进去)。
  7. Q:和 Redis 分布式锁有什么区别? A:Redis 锁(SETNX+TTL)单机强一致但主从切换有丢失风险;etcd 锁基于 Raft 强一致 + 自动续租 + 公平排队,适合对一致性要求高的协调场景。
  8. Q:怎么备份? A:etcdctl snapshot save 定期快照 + WAL 归档;恢复用 snapshot restore 生成新数据目录。

参考资料

相关推荐
谢亮_vipxieliang1 小时前
Go Gin 中间件与参数验证:从入门到实战
中间件·golang·gin
小师兄吃牛肉9 小时前
Java 同步系列之 ZooKeeper 分布式锁
java·分布式·java-zookeeper
microrain11 小时前
同一个值,四个名字,四套口径:SagooIoT 属性上报链路的 Canonical 收敛
物联网·golang·开源·sagooiot
糟了好像在长脑子11 小时前
面试总被问分布式ID? 十分钟带你读懂美团 Leaf 号段模式
分布式
tachibana212 小时前
Golang 中数组和切片的区别?
开发语言·后端·golang·数组·切片
小小龙学IT12 小时前
Redis 源码深度解析:从常用命令到内部实现
redis·golang·开源
徐小黑ACG1 天前
Golang 基础05 结构体struct
开发语言·算法·golang
北冥you鱼1 天前
Go 语言空接口(interface{})使用场景与最佳实践
开发语言·windows·golang
方方洛1 天前
ray教程-00-前言与导读
人工智能·分布式·机器学习