Skip to content
Go 客户端

Go 客户端

etcd 官方 Go 客户端 clientv3 完整指南:Client 创建与生命周期、KV / Watch / Lease / Cluster / Maintenance 子客户端、错误处理模式、连接重试,以及生产级配置。


安装

go get go.etcd.io/etcd/client/v3

Client 创建与生命周期

最简示例

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 只配了一个节点 单个节点挂掉会导致整个客户端不可用