Skip to content

intro

etcd 是一个分布式键值存储系统,专为配置共享和服务发现而设计。它由 CoreOS 开发,具有高可用性和一致性,广泛用于微服务架构中。

以下是如何在 Go 中使用 etcd 的详细指南:


1. etcd 核心功能

  • 分布式键值存储:支持高可用和一致性的键值存储。
  • 服务发现:提供服务注册和发现功能。
  • 分布式锁:实现高可用的分布式锁。
  • 监听机制:通过 Watch 功能监听键值变化。
  • 安全性:支持 TLS 和基于角色的权限控制。

2. 安装 etcd

安装方式

  1. etcd 官方下载页面 下载二进制文件。
  2. 解压后将 etcdetcdctl 移动到系统路径下。

启动 etcd

bash
etcd --name my-etcd --data-dir ./data --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://127.0.0.1:2379

默认情况下,etcd 监听 2379 端口用于客户端通信。


3. 在 Go 中使用 etcd

需要使用官方的 clientv3 库。

依赖安装

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

基础操作

初始化客户端

go
package main

import (
	"context"
	"log"
	"time"

	"go.etcd.io/etcd/client/v3"
)

func main() {
	// 创建 etcd 客户端
	client, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"localhost:2379"}, // etcd 地址
		DialTimeout: 5 * time.Second,           // 连接超时时间
	})
	if err != nil {
		log.Fatalf("Failed to connect to etcd: %v", err)
	}
	defer client.Close()

	log.Println("Connected to etcd")
}

键值操作

写入键值

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

_, err = client.Put(ctx, "foo", "bar")
if err != nil {
	log.Fatalf("Failed to put key-value: %v", err)
}
log.Println("Key-value written successfully!")

读取键值

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

resp, err := client.Get(ctx, "foo")
if err != nil {
	log.Fatalf("Failed to get key-value: %v", err)
}

for _, kv := range resp.Kvs {
	log.Printf("Key: %s, Value: %s", kv.Key, kv.Value)
}

删除键值

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

_, err = client.Delete(ctx, "foo")
if err != nil {
	log.Fatalf("Failed to delete key: %v", err)
}
log.Println("Key deleted successfully!")

监听键值变化

etcd 提供 Watch 功能,可以监听键值的变化。

go
watchChan := client.Watch(context.Background(), "foo")
for watchResp := range watchChan {
	for _, event := range watchResp.Events {
		log.Printf("Type: %s, Key: %s, Value: %s", event.Type, event.Kv.Key, event.Kv.Value)
	}
}

服务注册与发现

服务注册

通过约定的键值对进行服务注册。

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

leaseResp, err := client.Grant(ctx, 10) // 创建一个 10 秒的租约
if err != nil {
	log.Fatalf("Failed to create lease: %v", err)
}

_, err = client.Put(ctx, "service/my-service", "127.0.0.1:8080", clientv3.WithLease(leaseResp.ID))
if err != nil {
	log.Fatalf("Failed to register service: %v", err)
}
log.Println("Service registered successfully!")

服务发现

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

resp, err := client.Get(ctx, "service/", clientv3.WithPrefix())
if err != nil {
	log.Fatalf("Failed to discover services: %v", err)
}

for _, kv := range resp.Kvs {
	log.Printf("Service: %s, Address: %s", kv.Key, kv.Value)
}

分布式锁

etcd 提供 concurrency 模块来实现分布式锁。

依赖安装

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

实现锁

go
import (
	"go.etcd.io/etcd/client/v3/concurrency"
)

func main() {
	session, err := concurrency.NewSession(client)
	if err != nil {
		log.Fatalf("Failed to create session: %v", err)
	}
	defer session.Close()

	mutex := concurrency.NewMutex(session, "/my-lock/")

	// 获取锁
	if err := mutex.Lock(context.Background()); err != nil {
		log.Fatalf("Failed to acquire lock: %v", err)
	}
	log.Println("Lock acquired")

	// 释放锁
	if err := mutex.Unlock(context.Background()); err != nil {
		log.Fatalf("Failed to release lock: %v", err)
	}
	log.Println("Lock released")
}

分布式键值存储

写入带 TTL 的键

可以为键设置生存时间,使用租约实现。

go
leaseResp, err := client.Grant(context.Background(), 5) // 创建 5 秒租约
if err != nil {
	log.Fatalf("Failed to create lease: %v", err)
}

_, err = client.Put(context.Background(), "temp-key", "temp-value", clientv3.WithLease(leaseResp.ID))
if err != nil {
	log.Fatalf("Failed to put key with lease: %v", err)
}
log.Println("Temporary key written successfully!")

自动续约

go
ch, err := client.KeepAlive(context.Background(), leaseResp.ID)
if err != nil {
	log.Fatalf("Failed to keep lease alive: %v", err)
}

for ka := range ch {
	log.Printf("Lease %v renewed", ka.ID)
}

最佳实践

  1. 健康检查与监控
    • 使用 Prometheus 或其他工具监控 etcd 集群。
  2. 高可用部署
    • 使用奇数节点(如 3 或 5)组成 etcd 集群。
  3. 权限控制
    • 启用身份认证和 TLS,保证数据安全。
  4. 分布式锁与事务
    • 在分布式场景中使用锁来避免数据竞争。
  5. Watch 谨慎使用
    • 避免 Watch 太多键值,以免影响性能。

etcd 是强一致性的分布式系统,非常适合需要高可靠性和一致性的场景。如果你有更具体的问题或需求,随时告诉我!