1. 背景
1.1 为什么需要 etcd
在工业数采这类多网关、多服务的分布式场景中,单体应用里用一台数据库就能解决的"配置、选主、互斥"问题,在分布式环境下会变成三个经典痛点:
- 配置中心缺失:采集网关、边缘服务、中心平台各自维护配置文件,灰度下发、热更新、版本回滚全靠人肉,改一处配置要重启一批进程。
- 选主与高可用难:多台采集网关同时采集同一批 CNC/PLC 点位会造成重复上报;需要"同一时刻只有一个主节点干活"的机制,但主节点宕机后谁来接管、如何避免双主脑裂?
- 分布式互斥缺失:多网关共享某台设备的命令下发通道时,需要一把"跨进程、跨机器"的锁------单机内存锁(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 把一致性问题拆解为三个子问题:
- Leader 选举:每个节点有 Leader / Follower / Candidate 三种角色。Follower 在选举超时(etcd 默认 1s)内没收到 Leader 心跳,就变成 Candidate 发起选举,获得多数派投票即成为 Leader。
- 日志复制 :所有写请求只发给 Leader;Leader 把操作包装成日志条目,复制给 Follower,过半节点持久化成功后才提交(commit)并返回客户端------这就是强一致的来源。
- 安全性:通过 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 条)
- 每次请求新建 Client:clientv3.Client 内部有连接池,全局复用;频繁新建导致连接泄漏、握手开销巨大。
- context 无超时:所有 API 都应传带超时/取消的 context,否则网络分区时请求会无限阻塞。
- Watch 只增量不拉全量:启动时先 Get 全量再 Watch 增量,否则漏掉启动前的配置。
- Watch 遇到 ErrCompacted :历史版本被 compaction 清理后 Watch 断点续传失败;正确做法是 Watch 失败后重新 Get 全量 + 从头 Watch。
- Lease 不 KeepAlive 导致锁/服务意外释放:锁、注册 key 必须配套后台 KeepAlive(concurrency.Session 已内置),否则 TTL 一到自动消失。
- KeepAlive 返回 nil channel:ctx 取消或 lease 已过期时 KeepAlive 返回的 channel 为 nil,for range nilCh 会永久阻塞------务必检查。
- 分布式锁忘 Unlock / 不处理会话过期:临界区 panic 前必须 defer Unlock;Session 过期后锁已失效但本地不知,需在业务里感知(观察锁事件)。
- Txn 条件写错 revision:乐观锁必须比较 ModRevision(当前版本)而非 CreateRevision 或旧值,否则无法实现 CAS。
- 多 key 需要原子性却用多次 Put:跨 key 原子写必须用 Txn(If().Then(OpPut...)),多次 Put 不保证原子。
- 值类型混用:etcd value 是 \[\]byte,存数值要自己定好序列化(strconv/JSON/protobuf),读写两端必须一致。
- 大 value 滥用:单 value 超过 1MB(默认 max-request-bytes 1.5MiB)会直接拒绝;配置类数据应保持 KB 级。
- 用 etcd 存海量业务数据:etcd 定位是协调元数据(几十 MB 级),大数据应放 Redis/TDengine/对象存储;强行当数据库用会拖垮 Raft 复制。
- Watch 前缀过宽:WithPrefix() 监听 / 会把集群所有变更推给自己,高负载下内存与带宽爆炸;按业务前缀收敛。
- 集群节点数选偶数:3 节点容忍 1 故障,5 节点容忍 2 故障;偶数节点既不提升容错又浪费(4 节点仍只能容忍 1 故障)。
- 不配 TLS/认证就上生产:默认无鉴权,内网被扫到即数据裸奔;生产必开 --client-cert-auth + RBAC。
- follower 直写:所有写请求只能走 Leader;客户端连多个 Endpoint 时 grpc 负载均衡会自动把请求转发到 Leader,但只配单端点时写会失败(etcdserver: no leader 是重试信号)。
- 忽略 cluster ID mismatch:用旧快照恢复到新集群时数据目录与集群 ID 不匹配,必须 snapshot restore 生成新目录而非直接拷贝。
- 快照恢复后客户端还连旧集群:恢复会产生新成员 ID,客户端 Endpoints 需更新,且旧数据目录要清理避免重复加入。
- compaction 策略缺失:不开自动 compaction,历史版本无限累积,boltdb 膨胀、查询变慢、磁盘占满。
- Watch 事件里 value 为 nil:DELETE 事件的 ev.Kv.Value 为 nil;要拿删除前的值必须 WithPrevKV()。
- 把锁 key 与配置 key 放同一前缀:Watch 配置时会把锁的变更也推过来,事件流被无关更新刷屏;锁/选举用独立前缀。
- 跨机房部署忽略延迟: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 值得掌握的三句话:
- 强一致是它的灵魂:所有写操作经 Raft 过半 ACK 才返回,读支持线性读,保证分布式环境下"看到的配置/锁状态一定是全网一致的"。
- Watch + Lease 是它区别于普通 KV 的两把利器:配置推送、服务上下线感知、锁自动释放都建立在这两个机制上,是构建自愈分布式系统的关键原语。
- 只做协调,不做存储:它适合存"谁在干活、配置是什么、谁有锁"这种小数据高可靠元数据;数据本体交给 Kafka/TDengine/Redis。
上手建议:本地起 3 节点集群(etcd --initial-cluster 三进程)→ 跑完 4.1~4.6 示例 → 再看 5 章源码路径(raft、mvcc、wal、lease、watchable_store.go)→ 最后把 4.3/4.4 的锁与选主接入自己的采集网关。
FAQ 速查
- Q:etcd 和 Zookeeper 怎么选? A:Go 生态、gRPC、原生 Watch 用 etcd;存量 Java/树形 znode 体系用 ZooKeeper;功能定位几乎相同。
- Q:etcd 数据能存多大? A:官方建议几十 MB 到几 GB 级别;它是元数据存储,不是大数据存储。
- Q:写请求只能到 Leader 吗? A:是;但客户端配多个 Endpoint 时 grpc 层自动转发,应用层无感。
- Q:Watch 为什么会断? A:网络分区、客户端重启、服务端 compaction 清理了起始版本(ErrCompacted);都要做全量重同步。
- Q:锁的 TTL 设多少合适? A:大于临界区最坏执行时间,通常 5~15s;太短会误释放,太长故障恢复慢。
- Q:多节点集群最少几个? A:3 个(容忍 1 故障);2 个是反模式(任何 1 故障都写不进去)。
- Q:和 Redis 分布式锁有什么区别? A:Redis 锁(SETNX+TTL)单机强一致但主从切换有丢失风险;etcd 锁基于 Raft 强一致 + 自动续租 + 公平排队,适合对一致性要求高的协调场景。
- Q:怎么备份? A:etcdctl snapshot save 定期快照 + WAL 归档;恢复用 snapshot restore 生成新数据目录。
参考资料
- etcd 官方文档:Documentation versions | etcd
- clientv3 包文档:clientv3 package - go.etcd.io/etcd/client/v3 - Go Packages
- Raft 论文(Ongaro & Ousterhout, 2014):Raft Consensus Algorithm
- etcd 源码:GitHub - etcd-io/etcd: Distributed reliable key-value store for the most critical data of a distributed system · GitHub