Go mod 好菜系列 - 0x1A etcd/clientv3 这口分布式协调菜别只会 put 和 get
详细聊 etcd/clientv3 在配置、选主、租约和分布式协调里的常见用法,为什么 watch 与 lease 很关键,以及它和普通 KV 存储不是一回事。
很多人第一次接触 etcd,会把它理解成“一个更可靠点的 KV 数据库”。这个理解不能说全错,但非常不够。真正在项目里常用到它时,你会发现它更像一块分布式协调地基,而不是拿来随便存业务数据的抽屉柜。
etcd/clientv3 常见在干嘛
- 配置动态监听
- 服务注册信息存储
- 分布式锁
- 选主与租约
- 协调类元数据管理
这几件事有个共同点:它们都更偏“系统状态协同”,而不是普通业务记录。
为什么不能把它当普通 KV
因为 etcd 的优势并不在“便宜地存很多业务数据”,而在于它对一致性、变更监听、租约和协调语义的支持。你如果拿它去存一堆高频业务明细,大概率会把最有价值的部分浪费掉。
watch 为什么这么重要
因为很多场景要的不是“我现在查一下值”,而是“只要值变了你立刻告诉我”。这就是动态配置、路由更新、服务状态变化能成立的基础。
项目一旦开始需要“感知变化”,轮询就会越来越笨,watch 的价值就出来了。
lease 又是在解决什么
租约本质上是在表达一种“临时有效”的状态。比如一个实例注册了自己,只要它持续续租,就说明它还活着;一旦续租断了,这条信息就自然过期。
这种机制对服务注册、选主和临时会话状态都非常有用。
watch 和 lease 的最小示例
cli, err := clientv3.New(clientv3.Config{
Endpoints: []string{"127.0.0.1:2379"},
DialTimeout: 5 * time.Second,
})
if err != nil {
log.Fatal(err)
}
leaseResp, err := cli.Grant(context.Background(), 10)
if err != nil {
log.Fatal(err)
}
_, err = cli.Put(context.Background(), "/service/user-api/1", "10.0.0.12:8080", clientv3.WithLease(leaseResp.ID))
if err != nil {
log.Fatal(err)
}
watchCh := cli.Watch(context.Background(), "/service/user-api/", clientv3.WithPrefix())
for resp := range watchCh {
for _, ev := range resp.Events {
fmt.Println(ev.Type, string(ev.Kv.Key), string(ev.Kv.Value))
}
}把 watch 和 lease 放在一起看,etcd 的协调味道就出来了。它不是单纯存一条记录,而是在表达“谁活着、谁变化了、变化何时发生”。
它最容易让人踩坑的地方
- 把业务数据也全塞进去
- 只会 put/get,不会 watch/lease
- 没有处理好连接抖动和重连
- 把分布式锁当成万能灵药
尤其是分布式锁,很多时候你以为自己在“严谨控制并发”,实际上是在给系统加一个更难排障的新脆点。
什么时候它很值得引入
- 系统里已经出现动态配置和协调需求
- 你需要可靠的 watch 机制
- 你确实有租约、会话、选主这类需求
如果只是想找个地方存几个开关值,甚至 Redis 都可能更省心。别为了“分布式系统味道”过早上 etcd。
小结
etcd/clientv3 这口菜要学的是协调思维:
- 它更像分布式协调底座,不是普通业务 KV
- watch 和 lease 是真正高价值能力
- 适合动态配置、选主、注册和临时状态管理
- 分布式锁要谨慎上,不要把它当万能钥匙