kv分布式系统,etcd+golang,入门

参考文章
官网:https://etcd.io/
godoc文档:https://pkg.go.dev/go.etcd.io/etcd/clientv3?tab=doc
参考文章:https://www.liwenzhou.com/posts/Go/go_etcd/
git地址:https://github.com/etcd-io/etcd/releases

简介

etcd是使用Go语言开发的一个开源的、高可用的分布式key-value存储系统,可以用于配置共享和服务的注册和发现。

特点

  • 完全复制:集群中的每个节点都可以使用完整的存档
  • 高可用性:Etcd可用于避免硬件的单点故障或网络问题
  • 一致性:每次读取都会返回跨多主机的最新写入
  • 简单:包括一个定义良好、面向用户的API(gRPC)
  • 安全:实现了带有可选的客户端证书身份验证的自动化TLS
  • 快速:每秒10000次写入的基准速度
  • 可靠:使用Raft算法实现了强一致、高可用的服务存储目录

etcd对比zookeeper

  • 更轻量级、更易用
  • 高负载下的稳定读写
  • 数据模型的多版本并发控制
  • 稳定的watcher功能,通知订阅者监听值的变化
  • 客户端协议使用gRPC协议,支持go、C++、Java等,而Zookeeper的RPC协议是自定制的,目前只支持C和Java
  • 可以容忍脑裂现象的发生
  • 脑裂现象指的是,在一个分布式集群中,只允许一个leader协调工作,由于网络或其他原因,导致一个集群分成了两个集群,产生了两个leader同时工作,此时集群不在具备读写一致性。
  • etcd是使用raft算法解决的脑裂问题,详细参考:http://thesecretlivesofdata.com/
  • 关于zookeeper如何使用参考上篇博文:https://blog.csdn.net/qq_25490573/article/details/107280724

安装

ETCD_VER=v3.4.8

# choose either URL
GOOGLE_URL=https://storage.googleapis.com/etcd
GITHUB_URL=https://github.com/etcd-io/etcd/releases/download
DOWNLOAD_URL=${GOOGLE_URL}

rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
rm -rf /tmp/etcd-download-test && mkdir -p /tmp/etcd-download-test

curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/etcd-download-test --strip-components=1
rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz

/tmp/etcd-download-test/etcd --version
/tmp/etcd-download-test/etcdctl version
# start a local etcd server
/tmp/etcd-download-test/etcd
# 开启远程服务
/tmp/etcd-download-test/etcd --listen-client-urls 'http://0.0.0.0:2379' --advertise-client-urls 'http://0.0.0.0:2379' --initial-advertise-peer-urls http://0.0.0.0:2380  --listen-peer-urls 'http://0.0.0.0:2380' > out.txt &

# write,read to etcd
/tmp/etcd-download-test/etcdctl --endpoints=localhost:2379 put foo bar
/tmp/etcd-download-test/etcdctl --endpoints=localhost:2379 get foo

简单栗子

下载开发包

go get go.etcd.io/etcd/clientv3

由于grpc包的升级和更改,如果go build时报如下错误

E:\goproject\gotest\redistest>go run mian.go
# github.com/coreos/etcd/clientv3/balancer/resolver/endpoint
C:\Users\18322\go\pkg\mod\github.com\coreos\[email protected]+incompatible\clientv3\balancer\resolver\endpoint\endpoint.go:114:78: undefined: resolver.BuildOption
C:\Users\18322\go\pkg\mod\github.com\coreos\[email protected]+incompatible\clientv3\balancer\resolver\endpoint\endpoint.go:182:31: undefined: resolver.ResolveNowOption
# github.com/coreos/etcd/clientv3/balancer/picker
C:\Users\18322\go\pkg\mod\github.com\coreos\[email protected]+incompatible\clientv3\balancer\picker\err.go:37:44: undefined: balancer.PickOptions
C:\Users\18322\go\pkg\mod\github.com\coreos\[email protected]+incompatible\clientv3\balancer\picker\roundrobin_balanced.go:55:54: undefined: balancer.PickOptions

修改go.mod 文件,降低grpc版本

replace google.golang.org/grpc => google.golang.org/grpc v1.26.0
//replace github.com/lucas-clemente/quic-go => github.com/lucas-clemente/quic-go v0.14.1

封装操作

type EtcdManage struct {
	Client *clientv3.Client
	RequestTimeout time.Duration
}
//新建一个etcd实例
func NewEtcdManage(addrs []string)*EtcdManage{
	em := new(EtcdManage)
	//连接etcd远程服务
	client,err := clientv3.New(clientv3.Config{
		Endpoints:addrs,
		DialTimeout: time.Second * 5,
	})
	if err!=nil{
		panic(err)
	}
	em.Client = client
	em.RequestTimeout = time.Second * 3
	return em
}
//设置kv
func (e * EtcdManage)Put(key,value string)error{
	ctx,cancel := context.WithTimeout(context.Background(),e.RequestTimeout)
	_,err := e.Client.Put(ctx,key,value)
	cancel()
	return err
}
//获取kv
func (e * EtcdManage)Get(key string)(value []string,err error){
	ctx,cancel := context.WithTimeout(context.Background(),e.RequestTimeout)
	getresp,err := e.Client.Get(ctx,key)
	cancel()
	if err!=nil{
		return nil,err
	}
	kvs := make([]string,0)
	//将所有的值拼接成企切片,然后取出
	for i,value := range getresp.Kvs{
		fmt.Println(string(value.Key),string(value.Value))
		kvs = append(kvs,string(getresp.Kvs[i].Value))
	}
	return kvs,nil
}
//删除
func  (e * EtcdManage)Delete(key string)error{
	_,err :=  e.Client.Delete(context.TODO(),key)
	return err
}
func (e * EtcdManage)Close()error{
	return e.Client.Close()
}

测试

func InitEtcd(){
	key := "etcd_test"
	em := NewEtcdManage([]string{"192.168.254.172:2379"})
	defer em.Close()
	err := em.Put(key,"this is second test value")
	if err!=nil{
		fmt.Println("put:",err)
		return
	}
	values,err := em.Get(key)
	if err!=nil{
		fmt.Println("get",err)
		return
	}
	fmt.Println(values)
}

watch机制

监测某个key,当key变化时,回调函数
添加一个成员函数

//添加一个watch注册函数,非阻塞
func (e * EtcdManage)Watch(key string,f func(w clientv3.WatchResponse)bool){
	//开始监听key值得变化
	watchchan := e.Client.Watch(context.Background(),key)
	go func(){
		for {
			if event,ok := <- watchchan;!ok{
				break
			}else{
				if !f(event) {
					return
				}
			}
		}
	}()
}

改造测试函数

func InitEtcd(){
	key := "etcd_test"

	em := NewEtcdManage([]string{"192.168.254.172:2379"})
	defer em.Close()
	//实现一个watch函数
	em.Watch(key,func(e clientv3.WatchResponse)bool{
		for _,event := range e.Events{
			fmt.Println(event.Type,string(event.Kv.Key),string(event.Kv.Value))
		}
		return true
	})

	err := em.Put(key,"this is second test value")
	if err!=nil{
		fmt.Println("put:",err)
		return
	}
	values,err := em.Get(key)
	if err!=nil{
		fmt.Println("get",err)
		return
	}
	fmt.Println(values)
	select {
	}
}

Lease 租约

创建一个租约成员函数

//timeout: 多少秒后过期
func (e * EtcdManage)Grant(key,value string,timeout int64)(clientv3.LeaseID,error){
	lrr,err:=e.Client.Grant(context.TODO(),timeout)
	if err!=nil{
		return 0,err
	}
	_,err =e.Client.Put(context.TODO(),key,value,clientv3.WithLease(lrr.ID))
	return lrr.ID,err
}

KeepAlive 续约

keepalive 会让key租约失效,会一直保存下去

func (e * EtcdManage)KeepAlive(lid clientv3.LeaseID)error{
	_,err := e.Client.KeepAlive(context.TODO(),lid)
	return err
}

未完待续…

猜你喜欢

转载自blog.csdn.net/qq_25490573/article/details/107287198