[ 后端与etcd快速入门| 青训营笔记]

242 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 10 天

etcd名字的由来,“distributed etc directory.”,意思是“分布式etc目录”,说明它存的是大型分布式系统的配置信息。一个用于存储分布式系统中最关键的数据的仓库,它是分布式的、可靠的键值对仓库。首先它是个数据存储仓库,它的特性是分布式的、可靠性的,数据存储格式是键值对存储,它主要用于存储分布式系统中的关键数据。

1.etcd的应用场景

etcd的应用场景:服务发现和服务注册、配置中心、分布式锁。

  1. 服务发现

    服务发现要解决的也是分布式系统中最常见的问题之一,即在同一个分布式集群中的进程或服务,要如何才能找到对方并建立连接。本质上来说,服务发现就是想要了解集群中是否有进程在监听 udp 或 tcp 端口,并且通过名字就可以查找和连接。 控制时序,即所有想要获得锁的用户都会被安排执行,但是获得锁的顺序也是全局唯一的,同时决定了执行顺序。

  2. 配置中心

    将一些配置信息放到 etcd 上进行集中管理。这类场景的使用方式通常是这样:应用在启动的时候主动从 etcd 获取一次配置信息,同时,在 etcd 节点上注册一个 Watcher 并等待,以后每次配置有更新的时候,etcd 都会实时通知订阅者,以此达到获取最新配置信息的目的。

  3. 分布式锁

    因为 etcd 使用 Raft 算法保持了数据的强一致性,某次操作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序。

  • 保持独占即所有获取锁的用户最终只有一个可以得到。etcd 为此提供了一套实现分布式锁原子操作 CAS(CompareAndSwap)的API。通过设置prevExist值,可以保证在多个节点同时去创建某个目录时,只有一个成功。而创建成功的用户就可以认为是获得了锁。
  • 控制时序,即所有想要获得锁的用户都会被安排执行,但是获得锁的顺序也是全局唯一的,同时决定了执行顺序。etcd 为此也提供了一套API(自动创建有序键),对一个目录建值时指定为POST动作,这样 etcd会自动在目录下生成一个当前最大的值为键,存储这个新的值(客户端编号)。同时还可以使用 API按顺序列出所有当前目录下的键值。此时这些键的值就是客户端的时序,而这些键中存储的值可以是代表客户端的编号。

2.etcd的安装

  1. 下载 wget https:``//github.com/etcd-io/etcd/releases/download/v3.4.15/etcd-v3.4.15-linux-amd64.tar
  2. 解压 tar xf etcd-v3.4.15-linux-amd64.tar
  3. 进入解压目录 cd etcd-v3.4.15-linux-amd64
  4. 把etcd的服务端和客服端放入环境变量
    cp etcd /usr/sbin/
    cp etcdctl /usr/sbin/
    
  5. 运行etcd服务
etcd

3.go简单操作

安装etcd/clientv3

go get go.etcd.io/etcd/clientv3

main.go

package main

import (
    "context"
    "fmt"
    "go.etcd.io/etcd/clientv3"
    "time"
)

var cli *clientv3.Client
func Init(addr string, timeout time.Duration) (err error){
    cli,err = clientv3.New(clientv3.Config{
    Endpoints:            []string{addr},
    DialTimeout:          timeout,
   })
   if err != nil{
      fmt.Println("connect to etcd failed,err:",err)
   return
   }
   return
}
func main() {
    addr := "127.0.0.1:2379"
    timeout := 5 * time.Second
    err := Init(addr,timeout)
    if err != nil {
        fmt.Printf("etcd.Init err:%vn",err)
    return
    }
    fmt.Println("etcd Init success")
    // put
    ctx,cancel := context.WithTimeout(context.Background(),time.Second)
    _, err = cli.Put(ctx,"key","sanqing")
    cancel()
    if err != nil{
        fmt.Printf("cli.Put err:%vn",err)
        return
    }
    ctx,cancel = context.WithTimeout(context.Background(),time.Second)
    _,err = cli.Delete(ctx,"key2")
    cancel()
    if err != nil{
    fmt.Printf("cli.Delete err:%vn",err)
    return
    }
    // get
    ctx,cancel = context.WithTimeout(context.Background(),time.Second)
    resp,err := cli.Get(ctx,"key")
    cancel()
    if err != nil{
       fmt.Printf("cli.Get err:%vn",err)
       return
    }
    fmt.Println(resp)
    for _,ev :=range resp.Kvs{
       fmt.Printf("%s:%sn",ev.Key,ev.Value)
    }
    // 派一个哨兵 一直监视 key 的变化(新增,修改,删除)
    ch := cli.Watch(context.Background(),"key")
    // 尝试重通道取值(监视信息)
    for wresp := range ch{
       for _,evt := range wresp.Events{
        fmt.Printf("type:%v; key:%v; value:%v n",evt.Type,string(evt.Kv.Key),string(evt.Kv.Value))
      }
   }
}