Go 客户端
etcd 官方 Go 客户端 clientv3 完整指南:Client 创建与生命周期、KV / Watch / Lease / Cluster / Maintenance 子客户端、错误处理模式、连接重试,以及生产级配置。
安装
go get go.etcd.io/etcd/client/v3Client 创建与生命周期
最简示例
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: 5 * time.Second,
})
if err != nil {
panic(err)
}
defer cli.Close()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
resp, err := cli.Put(ctx, "/hello", "world")
if err != nil {
fmt.Printf("put error: %v\n", err)
return
}
fmt.Printf("put success, rev=%d\n", resp.Header.Revision)
}配置项完整说明
cli, err := clientv3.New(clientv3.Config{
// ── 必填配置 ──
Endpoints: []string{
"localhost:2379",
"localhost:22379",
"localhost:32379",
},
// ── 超时配置 ──
DialTimeout: 5 * time.Second, // 建立连接超时
DialKeepAliveTime: 10 * time.Second, // 客户端 keepalive 间隔
DialKeepAliveTimeout: 3 * time.Second, // keepalive 包等待时间
// ── 认证配置 ──
Username: "root",
Password: "rootpass",
// ── TLS 配置 ──
TLS: &tls.Config{
// ...
},
// ── 重试配置 ──
AutoSyncInterval: 0, // 自动同步端点(0=关闭)
RejectOldCluster: true, // 拒绝旧集群(防止连到被替换的集群)
// ── 日志配置 ──
Logger: nil, // 实现 clientv3.Logger 接口
LogConfig: nil, // *zap.Config(使用 zap 时)
// ── 高级配置 ──
PermitWithoutStream: false, // 允许客户端无 stream 连接时发送请求
MaxCallSendMsgSize: 0, // 发送最大消息大小(0=默认 2MB)
MaxCallRecvMsgSize: 0, // 接收最大消息大小(0=默认 math.MaxInt32)
})Client 生命周期
// ① 创建
cli, err := clientv3.New(clientv3.Config{...})
// ② 使用(并发安全,多个 goroutine 共享一个 Client)
go func() {
cli.Put(ctx, "/key1", "val1")
}()
go func() {
cli.Get(ctx, "/key2")
}()
// ③ 关闭(阻塞等待所有活跃请求完成)
cli.Close()💡 最佳实践:
- 一个应用只创建一个
Client实例,全局共享(并发安全)- 务必
defer cli.Close()或在 shutdown 时关闭Endpoints列出所有集群节点,客户端会自动发现 Leader 并路由请求
子客户端
Client 提供方法创建各服务的子客户端,用于更精细的 API 调用:
cli, _ := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"}})
defer cli.Close()
// KV 服务 — 键值读写、事务
kv := clientv3.NewKV(cli)
// Watcher 服务 — 监听 key 变更
watcher := clientv3.NewWatcher(cli)
// Lease 服务 — 租约管理
lease := clientv3.NewLease(cli)
// Cluster 服务 — 集群成员管理
cluster := clientv3.NewCluster(cli)
// Maintenance 服务 — 运维(状态、快照、告警、碎片整理)
maintenance := clientv3.NewMaintenance(cli)💡 最佳实践:大多数场景直接用
cli.Put(),cli.Get()等便利方法即可。只有需要高级选项(如WithIgnoreLease())时才显式创建kv子客户端。
便利方法与子客户端对比
// 便利方法(Client 直接调用)
cli.Put(ctx, key, val)
cli.Get(ctx, key, clientv3.WithPrefix())
// 等价的子客户端方式
kv := clientv3.NewKV(cli)
kv.Put(ctx, key, val)
kv.Get(ctx, key, clientv3.WithPrefix())
// 区别:子客户端支持更多 OpOption
kv.Put(ctx, key, val, clientv3.WithIgnoreLease()) // KV 子客户端独有错误处理
import (
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
)
resp, err := cli.Put(ctx, key, val)
if err != nil {
switch err {
case context.Canceled:
log.Println("请求被取消")
case context.DeadlineExceeded:
log.Println("请求超时")
case rpctypes.ErrEmptyKey:
log.Println("Key 为空")
case rpctypes.ErrKeyNotFound:
log.Println("Key 不存在")
default:
// 转换为 rpc 错误获取详细信息
if ev, ok := err.(rpctypes.EtcdError); ok {
log.Printf("etcd 错误: code=%s, msg=%s", ev.Error(), ev.Message())
}
}
}常见错误码
| 错误变量 | 说明 |
|---|---|
rpctypes.ErrEmptyKey |
Key 为空 |
rpctypes.ErrKeyNotFound |
Key 不存在 |
rpctypes.ErrGRPCCompacted |
Revision 已被压缩 |
rpctypes.ErrGRPCFutureRev |
Revision 大于当前值 |
rpctypes.ErrGRPCNoSpace |
存储空间不足 |
rpctypes.ErrGRPCNotCapable |
操作不被允许(如 Learner 上执行 Txn) |
rpctypes.ErrGRPCUnhealthy |
端点不健康 |
端点自动发现与故障转移
// 方式一:手动同步端点列表
cli.Sync(context.Background())
// 方式二:自动定期同步(推荐)
cli, _ := clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379"},
AutoSyncInterval: 30 * time.Second, // 每 30s 从集群同步端点列表
})AutoSyncInterval 会定期请求 MemberList,自动发现新节点并移除下线节点,实现透明的故障转移。
🚨 陷阱:
AutoSyncInterval默认为 0(关闭)。生产环境建议设置 30s-5min。但需注意首次连接到的端点必须可达,否则客户端无法启动。
权限与认证的自动处理
// 客户端在创建时就会携带认证凭据
cli, _ := clientv3.New(clientv3.Config{
Endpoints: []string{"localhost:2379"},
Username: "myuser",
Password: "mypassword",
})
// 所有后续请求自动附带认证 Token
cli.Get(ctx, "/foo") // 无需手动传递凭据常见陷阱
| 陷阱 | 说明 |
|---|---|
| 🚨 Client 不 Close 导致资源泄漏 | gRPC 连接不会自动回收,goroutine 随连接数增长 |
| 🚨 AutoSyncInterval 为 0 | 默认不会自动同步集群成员,故障时不感知新 Leader |
| 🚨 共享太多 Client | 不需要为每个 goroutine 创建 Client,一个全局 Client 并发安全 |
| 🚨 未设置 context 超时 | 默认无超时,网络故障时请求可能永久阻塞 |
| 🚨 Endpoints 只配了一个节点 | 单个节点挂掉会导致整个客户端不可用 |