认识 etcd

170 阅读3分钟

Etcd 是一个强一致性的分布式键值存储系统,它为需要被分布式系统或集群中的机器访问的数据提供了一个可靠的存储方式。在网络分区期间,etcd优雅地处理领导者选举,并且可以容忍机器故障,即使在领导者节点发生故障时也能正常运行。

etcd 特点

  1. 高可用性:Etcd 集群中的多个节点可以相互协作来保证数据的可靠性和一致性。如果某个节点失效,其他节点可以接替其工作,确保服务不会中断。
  2. 可扩展性:Etcd 可以水平扩展单个节点的性能,同时也可以添加新节点来扩展集群规模。这使得 Etcd 能够适应不断增长的数据需求。
  3. 数据一致性:Etcd 采用Raft协议来实现数据的一致性和可靠性。在写操作时,Etcd 会将数据同步到所有节点上,确保所有节点的数据都是相同的。这种机制使得 Etcd 能够提供强一致性的数据访问。
  4. API 支持:Etcd 提供了丰富的API支持,包括Go客户端、命令行工具、Web界面等。这些API使得用户能够方便地进行操作和管理。
  5. 灵活的数据模型:Etcd 支持多种数据模型,包括简单键值对、有序集合、哈希表等。这使得 Etcd 能够满足不同应用场景的需求。

etcd 使用场景

  1. 服务发现和注册:Etcd 可以用作服务发现和注册的中心。当服务启动时,它会将自己的 IP 地址和端口号写入 Etcd 中,其他服务可以通过查询 Etcd 来发现可用的服务。
  2. 配置共享:Etcd 可以用作配置文件的共享存储库。应用程序可以将其配置存储在 Etcd 中,并在运行时读取它们。这使得应用程序可以在不同的环境中保持一致的配置。
  3. 分布式锁:Etcd 可以用于实现分布式锁。多个客户端可以同时请求同一个锁,但只有一个客户端能够成功获得锁。当一个客户端释放锁时,其他客户端也可以尝试获得锁。
  4. 集群管理:Etcd 可以用于管理分布式集群。例如,可以使用 Etcd 实现负载均衡、故障转移等功能。

go 语言操作 etcd

先导包

   go get go.etcd.io/etcd/clientv3

put 和 get 操作

put 设置键值对,get 获取键值对

package main

import (
    "context"
    "fmt"
    "time"

    "go.etcd.io/etcd/clientv3"
)

// etcd client put/get demo
// use etcd/clientv3

func main() {
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"127.0.0.1:2379"},
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        // handle error!
        fmt.Printf("connect to etcd failed, err:%v\n", err)
        return
    }
    fmt.Println("connect to etcd success")
    defer cli.Close()
    // put
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    _, err = cli.Put(ctx, "lmh", "lmh")
    cancel()
    if err != nil {
        fmt.Printf("put to etcd failed, err:%v\n", err)
        return
    }
    // get
    ctx, cancel = context.WithTimeout(context.Background(), time.Second)
    resp, err := cli.Get(ctx, "lmh")
    cancel()
    if err != nil {
        fmt.Printf("get from etcd failed, err:%v\n", err)
        return
    }
    for _, ev := range resp.Kvs {
        fmt.Printf("%s:%s\n", ev.Key, ev.Value)
    }
}

watch

watch用来获取未来更改的通知。

package main

import (
    "context"
    "fmt"
    "time"

    "go.etcd.io/etcd/clientv3"
)

// watch demo

func main() {
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"127.0.0.1:2379"},
        DialTimeout: 5 * time.Second,
    })
    if err != nil {
        fmt.Printf("connect to etcd failed, err:%v\n", err)
        return
    }
    fmt.Println("connect to etcd success")
    defer cli.Close()
    // watch key:lmh change
    rch := cli.Watch(context.Background(), "lmh") // <-chan WatchResponse
    for wresp := range rch {
        for _, ev := range wresp.Events {
            fmt.Printf("Type: %s Key:%s Value:%s\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
        }
    }
}

执行上面的代码,etcd 就会监控 lmh 的变化。

我们打开终端使用 etcdctl 操作 lmh ,程序就会提醒我们。

lease

租约

package main

import (
    "fmt"
    "time"
)

// etcd lease

import (
    "context"
    "log"

    "go.etcd.io/etcd/clientv3"
)

func main() {
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"127.0.0.1:2379"},
        DialTimeout: time.Second * 5,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("connect to etcd success.")
    defer cli.Close()

    // 创建一个5秒的租约
    resp, err := cli.Grant(context.TODO(), 5)
    if err != nil {
        log.Fatal(err)
    }

    // 5秒钟之后, /lmh/ 这个key就会被移除
    _, err = cli.Put(context.TODO(), "/lmh/", "lmh", clientv3.WithLease(resp.ID))
    if err != nil {
        log.Fatal(err)
    }
}

keepalive

package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "go.etcd.io/etcd/clientv3"
)

// etcd keepAlive

func main() {
    cli, err := clientv3.New(clientv3.Config{
        Endpoints:   []string{"127.0.0.1:2379"},
        DialTimeout: time.Second * 5,
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("connect to etcd success.")
    defer cli.Close()

    resp, err := cli.Grant(context.TODO(), 5)
    if err != nil {
        log.Fatal(err)
    }

    _, err = cli.Put(context.TODO(), "/lmh/", "lmh", clientv3.WithLease(resp.ID))
    if err != nil {
        log.Fatal(err)
    }

    // the key 'foo' will be kept forever
    ch, kaerr := cli.KeepAlive(context.TODO(), resp.ID)
    if kaerr != nil {
        log.Fatal(kaerr)
    }
    for {
        ka := <-ch
        fmt.Println("ttl:", ka.TTL)
    }
}

服务注册与发现

基于 lease 和 keepalive ,我们可以实现一个服务注册与发现的中间件。 可以参考juejin.cn/post/722188…

总结

  • etcd 是一个分布式的键值存储系统
  • 基于 etcd 我们可以实现多种功能,如服务注册与发现,配置中心,分布式锁等等
  • 使用 go.etcd.io/etcd/clientv3 对 etcd 进行操作