go之etcd读写实战

go之etcd读写实战,第1张

什么是etcd

etcd 发音为/ˈɛtsiːdiː/,名字的由来,“distributed etc directory.”,意思是“分布式etc目录”,说明它存的是大型分布式系统的配置信息。
官网的一句话
A distributed, reliable key-value store for the most critical data of a distributed system.
翻译并理解过来就是:一个用于存储分布式系统中最关键的数据的仓库,它是分布式的、可靠的键值对仓库。首先它是个数据存储仓库,它的特性是分布式的、可靠性的,数据存储格式是键值对存储,它主要用于存储分布式系统中的关键数据。

etcd的应用场景

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

1、服务发现
服务发现要解决的也是分布式系统中最常见的问题之一,即在同一个分布式集群中的进程或服务,要如何才能找到对方并建立连接。本质上来说,服务发现就是想要了解集群中是否有进程在监听 udp 或 tcp 端口,并且通过名字就可以查找和连接。
控制时序,即所有想要获得锁的用户都会被安排执行,但是获得锁的顺序也是全局唯一的,同时决定了执行顺序。
2、配置中心
将一些配置信息放到 etcd 上进行集中管理。
这类场景的使用方式通常是这样:应用在启动的时候主动从 etcd 获取一次配置信息,同时,在 etcd 节点上注册一个 Watcher 并等待,以后每次配置有更新的时候,etcd 都会实时通知订阅者,以此达到获取最新配置信息的目的。
3、分布式锁
因为 etcd 使用 Raft 算法保持了数据的强一致性,某次 *** 作存储到集群中的值必然是全局一致的,所以很容易实现分布式锁。锁服务有两种使用方式,一是保持独占,二是控制时序。

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

为什么用etcd而不用zookeeper?

etcd简单,使用Go语言编写部署简单,支持HTTP/JSON API,使用简单:使用Raft算法保证强一致性,让用户易于理解。etcd默认数据一更新就进行持久化。etcd支持SSL客户端安全认证。zookeeper部署维护复杂,其使用的PAXOS强一致性算法难懂。官方只提供了JAVA和C两种语言的接口。zookeeper 使用JAVA编写引入大量依赖。运维人员维护起来比较麻烦。 下面开始实战

安装etcd

所谓安装ectd,实际上就是下载etcd文件,然后解压即可。

etcd下载目录:https://github.com/etcd-io/etcd/releases

(如果上面的地址下载不了。就进入这个地址下载:https://studygolang.com/articles/28682)

下载到本地window,通过scp (或者ftp工具)上传到linux目录即可:

scp -r ./etcd-v3.4.7-linux-amd64.tar.gz [email protected]:/usr/local

到linux的环境执行以下 *** 作:
cd /usr/local
tar -xzvf etcd-v3.4.7-linux-amd64.tar.gz

解压成功后能看到下面的文件:

首先,必须开启etcd(需要在etcd的解压目录下执行)

./etcd (后台挂起程序:nohup ./etcd &

查看etcd默认的端口是否已经开启:netstat -antp | grep 2379

然后就可以在xshell控制台新增和读取key-value(需要在etcd的解压目录下执行)

./etcdctl --endpoints=localhost:2379 put mylove lxz
./etcdctl --endpoints=localhost:2379 get mylove
#删除key:
./etcdctl del /hello


如果没开启etcd,就进行读取或新增key的话,会出现下面的报错:

通过go代码实现etcd的读写 *** 作

设置key-value
package main
import (
	"fmt"
	"context"
	clientv3 "go.etcd.io/etcd/client/v3"
	"time"
)
// 设置etcd的值
func main() {
    key := "bookname"
    value := "收敛之道"
    SetEtcdData(key,value)
}

func SetEtcdData(key string,value string) {
	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, key, value)
    cancel()
    if err != nil {
        fmt.Printf("put to etcd failed, err:%v\n", err)
        return
    }
}
读取key-value
package main
import (
	"fmt"
	"context"
	clientv3 "go.etcd.io/etcd/client/v3"
	"time"
)

//获取etcd的值
func main() {
	fmt.Println("获取etcd的key")
	//获取单个key
	// key := "foo"
	// GetEtcdData(key)
	// GetEtcdData2(key)

	//获取多个key
	keys := []string{"foo","mylove"}
	GetEtcdData3(keys...)
}

func GetEtcdData(key string) {
	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()
    //限制请求时间为1秒
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    resp, err := cli.Get(ctx, key)
    cancel()
    if err != nil {
        fmt.Printf("get from etcd failed, err:%v\n", err)
        return
    }
    fmt.Println(resp.Kvs) //[key:"foo" create_revision:2 mod_revision:3 version:2 value:"bar" ]
    fmt.Println(resp.Kvs[0])//key:"foo" create_revision:2 mod_revision:3 version:2 value:"bar"
    for _, ev := range resp.Kvs {
        fmt.Printf("获取etcd的key-value为:%s:%s\n", ev.Key, ev.Value)
    }
}


func GetEtcdData2(key string) {
	var kv clientv3.KV
	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()
    //限制请求时间为1秒
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    kv = clientv3.NewKV(cli)//用newKv的方式获取键值对
    resp, err := kv.Get(ctx, key)
    cancel()
    if err != nil {
        fmt.Printf("get from etcd failed, err:%v\n", err)
        return
    }
    fmt.Println(resp.Kvs) //[key:"foo" create_revision:2 mod_revision:3 version:2 value:"bar" ]
    fmt.Println(resp.Kvs[0])//key:"foo" create_revision:2 mod_revision:3 version:2 value:"bar"
    for _, ev := range resp.Kvs {
        fmt.Printf("22获取etcd的key-value为:%s:%s\n", ev.Key, ev.Value)
    }
}


//获取多个键值对
func GetEtcdData3(keys...string) {
	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()
    ctx := context.TODO()//做法1
    //限制请求时间为1秒
    for _,k := range keys {
    	//做法2
    	// ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	    resp, err := cli.Get(ctx, k)
	    // cancel()
	    if err != nil {
	        fmt.Printf("get from etcd failed, err:%v\n", err)
	        return
	    }
	    for _, ev := range resp.Kvs {
	        fmt.Printf("获取etcd的多个key-value为:%s:%s\n", ev.Key, ev.Value)
	    }
    }
}
监听key的变化
package main

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

// watch demo
//监听某个key的变化
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:bookname change
    rch := cli.Watch(context.Background(), "bookname") // <-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 *** 作多个key:

#获取某个前缀的key,--keys-only代表不展示value。
./etcdctl --endpoints=localhost:2379 get / --prefix --keys-only 


#获取所有key,以及对应的value
./etcdctl --endpoints=localhost:2379 get  ""  --prefix

#获取所有key
./etcdctl --endpoints=localhost:2379 get  ""  --prefix --keys-only

#监听某个前缀的所有key的修改
./etcdctl --endpoints=localhost:2379 watch "/k" --prefix

#删除前缀是/hello开头下的所有key:
./etcdctl del /hello --prefix


代码实战:

//获取某个前缀下的所有key
//当pre_key=""的时候,获取全部的key。
func GetEtcdData4(pre_key string) {
	var kv clientv3.KV
	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()
    //限制请求时间为1秒
    ctx := context.TODO()//做法1
    kv = clientv3.NewKV(cli)//用newKv的方式获取键值对
    //获取前缀下面的所有key。当pre_key=""的时候,获取全部的key。
    resp, err := kv.Get(ctx, pre_key, clientv3.WithPrefix())
    if err != nil {
        fmt.Printf("get from etcd failed, err:%v\n", err)
        return
    }
    fmt.Println(resp.Kvs) //[key:"foo" create_revision:2 mod_revision:3 version:2 value:"bar" ]
    // fmt.Println(resp.Kvs[0])//key:"foo" create_revision:2 mod_revision:3 version:2 value:"bar"
    for _, ev := range resp.Kvs {
        fmt.Printf("22获取etcd的key-value为:%s:%s\n", ev.Key, ev.Value)
    }
}

推荐阅读:
https://blog.csdn.net/wohu1104/article/details/108552649
https://www.topgoer.com/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C/go%E6%93%8D%E4%BD%9Cetcd/etcd%E4%BB%8B%E7%BB%8D.html

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/990726.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存