golang 操作etcd数据库

etcd数据库使用

重要特性:

  • 底层储存是按key有序排列的,可以顺序遍历。

  • 因为key是有序的,所以etcd天然支持按目录结构高效遍历

  • 支持复杂事务,提供类似if…then…else…的事务能力

  • 基于租约机制实现key的TTL过期

  • MVCC多版本控制:同key维护多个历史版本,用于实现watch机制。

  • 监听KV变化。

一 集群部署(docker):

参考:https://blog.csdn.net/ffzhihua/article/details/83503173

二 命令行模式使用:

  • put

    $ ETCDCTL_API=3  ./etcdctl put "name" "lin"
    OK
    
  • get

    $ ETCDCTL_API=3  ./etcdctl get "name" 
    name
    lin
    
  • del

    $ ETCDCTL_API=3  ./etcdctl del "name"
    1
    
  • get --prefix

    $ ETCDCTL_API=3  ./etcdctl  put "/names/name1" "lin"
    OK
    $ ETCDCTL_API=3  ./etcdctl  put "/names/name2" "lin2"
    OK
    $ ETCDCTL_API=3  ./etcdctl  get "/names/" --prefix
    /names/name1
    lin
    /names/name2
    lin2
    
  • watch

    $ ETCDCTL_API=3  ./etcdctl  watch  "/names/" --prefix
    PUT
    /names/name1/
    lin1
    $ ETCDCTL_API=3  ./etcdctlput "/names/name1/" "lin1"
    OK
    

三 golang开发:

1 下载工具包
https://gopm.io/download 网站上填入:github.com/etcd-io/etcd
将文件夹解压到$GOPATH/src/githum.com/go.etcd.io/ 目录,并改名为etcd
2 建立链接
var (
	config clientv3.Config
	client *clientv3.Client
	err error
	)
func main(){

	// 客户端配置
	config = clientv3.Config{
		Endpoints:[]string{"127.0.0.1:2379","127.0.0.1:2479","127.0.0.1:2579"},
		DialTimeout: 5*time.Second, // 超时
	}

	// 建立链接
	if client,err = clientv3.New(config);err != nil{
		log.Fatal(err)
	}
}
3 写入键值并返回上次的键值
var Prev string
func main(){
	//用于读写etcd 的键值对
	kv = clientv3.NewKV(client)
	if Prev ,err = put(kv,"/cron/jobs/job1","test2"); err != nil {
		log.Fatal(err)
	}
	fmt.Println("Prev",Prev)
}
// etcd put 方法写入键值
func put(kv clientv3.KV, key string,v string)(Prev string,err error){
	var (
		putRes *clientv3.PutResponse
	)
	if putRes,err = kv.Put(context.TODO(),key,v,clientv3.WithPrevKV()) ; err != nil{
		return "",err
	}else {
		// 获取put前的value
		if putRes.PrevKv != nil {
			return string(putRes.PrevKv.Value),nil
		}else {
			return "",nil
		}
	}
}
4 获取键值与相同前缀的键值
func main(){
	get(kv,"/names/name1")
	getPrex(kv,"/names/")
	}
//  获取键值
func get(kv clientv3.KV,key string) {
	var (
		getRes *clientv3.GetResponse
		err error
	)
	if getRes,err = kv.Get(context.TODO(),key); err !=nil{
		log.Fatal(err)
	}
	for k,v := range getRes.Kvs {
		fmt.Println(k,v)
		fmt.Println(string(v.Value))
	}
}
//  获取相同前缀的键值
func getPrex(kv clientv3.KV,key string) {
	var (
		getRes *clientv3.GetResponse
		err error
	)
	if getRes,err = kv.Get(context.TODO(),key,clientv3.WithPrefix()); err !=nil{
		log.Fatal(err)
	}
	for k,v := range getRes.Kvs {
		fmt.Println(k,string(v.Value))
	}
}
5 删除键值与相同前缀的键值
func main(){
	del(kv,"/cron/jobs/job1")
	delPrex(kv,"/names/")
}
//  删除键值
func del(kv clientv3.KV,key string) {
	var (
		delRes *clientv3.DeleteResponse
		err error
	)
	if delRes,err = kv.Delete(context.TODO(),key); err !=nil{
		log.Fatal(err)
	}
	fmt.Println(delRes.Deleted)
}
//  删除相同前缀的键值
func delPrex(kv clientv3.KV,key string) {
	var (
		delRes *clientv3.DeleteResponse
		err error
	)
	if delRes,err = kv.Delete(context.TODO(),key,clientv3.WithPrefix()); err !=nil{
		log.Fatal(err)
	}
	fmt.Println(delRes.Deleted)
}

6 lease(租约机制)

使用租约机制实现键值自动过期,及续约机制

var (
	config clientv3.Config
	client *clientv3.Client
	err error
	kv clientv3.KV
	//putResp *clientv3.PutResponse
	getResp *clientv3.GetResponse
	lease clientv3.Lease
	leaseResp *clientv3.LeaseGrantResponse
	leaseKeepAliveResponsChan	<-chan *clientv3.LeaseKeepAliveResponse
	leaseKeepAliveResponse *clientv3.LeaseKeepAliveResponse
)

func main(){
	// 客户端配置
	config = clientv3.Config{
		Endpoints:[]string{"127.0.0.1:2379","127.0.0.1:2479","127.0.0.1:2579"},
		DialTimeout: 5*time.Second,  // 超时
	}

	// 建立链接
	if client,err = clientv3.New(config);err != nil{
		log.Fatal(err)
	}

	//用于读写etcd 的键值对
	kv = clientv3.NewKV(client)
	// 创建新的租约
	lease = clientv3.NewLease(client)
	// 实现键值10s过期
	leaseResp,err = lease.Grant(context.TODO(),10)
	// 续约机制
	//if _ ,err = kv.Put(context.TODO(),"/lease/lock","",clientv3.WithLease(leaseResp.ID));err != nil{
	//	log.Fatal(err)
	//}
	//leaseKeepAliveResponsChan,err = lease.KeepAlive(context.TODO(),leaseResp.ID)
	//go func() {
	//	for {
	//		leaseKeepAliveResponse = <- leaseKeepAliveResponsChan
	//		fmt.Println(leaseKeepAliveResponse)
	//	}
	//}()

	// 获取kv,查看是否过期(每两秒查看是否过期,如果上面没有进行续约,则10s后结束进程)
	for {
		if getResp,err = kv.Get(context.TODO(),"/lease/lock");err != nil{
			log.Fatal(err)
		}
		if getResp.Count == 0{
			fmt.Println("kv 已经过期了")
			break
		}
		fmt.Println("kv 没有过期:" , getResp.Kvs)
		time.Sleep(time.Second * 2)
	}

}

7 watch 监听键值变化机制

package main

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

var (
	config clientv3.Config
	client *clientv3.Client
	err error
	kv clientv3.KV
	Prev string
	getResp *clientv3.GetResponse
	watchStartRevision  int64
	watcher clientv3.Watcher
	watchChan clientv3.WatchChan
	watchResp clientv3.WatchResponse
	event *clientv3.Event
)

func main(){

	// 客户端配置
	config = clientv3.Config{
		Endpoints:[]string{"127.0.0.1:2379","127.0.0.1:2479","127.0.0.1:2579"},
		DialTimeout: 5*time.Second,  // 超时
	}

	// 建立链接
	if client,err = clientv3.New(config);err != nil{
		log.Fatal(err)
	}

	//用于读写etcd 的键值对
	kv = clientv3.NewKV(client)
	// 每秒进行写入与删除操作
	go func(){
		for {
			_, _ = kv.Put(context.TODO(), "/cron/jobs/job7", "i am job7")
			time.Sleep(time.Second)
			_, _ = kv.Delete(context.TODO(), "/cron/jobs/job7")
		}
	}()

	getResp,err = kv.Get(context.TODO(),"/cron/jobs/job7")
	if err!= nil{
		log.Fatal(err)
	}
	if len(getResp.Kvs) < 1 {
		log.Fatal("key do not have value")
	}
	// 从哪个版本进行监听
	watchStartRevision = getResp.Kvs[0].CreateRevision + 1

	// create watcher
	watcher = clientv3.NewWatcher(client)

	// 监听
	watchChan = watcher.Watch(context.TODO(),"/cron/jobs/job7",clientv3.WithRev(watchStartRevision))
	for watchResp = range watchChan{
		for _,event = range watchResp.Events{
			switch event.Type {
			case mvccpb.PUT:
				fmt.Println("change to:",string(event.Kv.Value),"Revision:",event.Kv.CreateRevision,event.Kv.ModRevision)
			case mvccpb.DELETE:
				fmt.Println("deleted, ","Revision:",event.Kv.ModRevision)

			}
		}
	}
}

8 OP 机制抽象执行动作

package main

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

var (
	config clientv3.Config
	client *clientv3.Client
	err error
	kv clientv3.KV
	Prev string
	putOp clientv3.Op
	opResp clientv3.OpResponse
)



func main() {

	// 客户端配置
	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379", "127.0.0.1:2479", "127.0.0.1:2579"},
		DialTimeout: 5 * time.Second, // 超时
	}

	// 建立链接
	if client, err = clientv3.New(config); err != nil {
		log.Fatal(err)
	}

	//用于读写etcd 的键值对
	kv = clientv3.NewKV(client)
	// 将要执行的动作抽象成op操作,最后统一用DO进行操作
	putOp = clientv3.OpPut("/corn/jobs/job9","1")
	if opResp,err = kv.Do(context.TODO(),putOp);err != nil{
		log.Fatal(err)
	}
	fmt.Println(opResp.Put().Header.Revision)
}

9 实现分布式锁机制(txn事务操作)

package main

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

var (
	config clientv3.Config
	client *clientv3.Client
	err error
	kv clientv3.KV
	Prev string
	lease clientv3.Lease
	leaseResp *clientv3.LeaseGrantResponse
	leaseId clientv3.LeaseID
	keepRespChan <-chan *clientv3.LeaseKeepAliveResponse
	keepResp *clientv3.LeaseKeepAliveResponse
	ctx context.Context
	cancelFunc   context.CancelFunc
	txn clientv3.Txn
	txnResp *clientv3.TxnResponse
)



func main() {

	// 客户端配置
	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379", "127.0.0.1:2479", "127.0.0.1:2579"},
		DialTimeout: 5 * time.Second, // 超时
	}

	// 建立链接
	if client, err = clientv3.New(config); err != nil {
		log.Fatal(err)
	}

	//用于读写etcd 的键值对
	kv = clientv3.NewKV(client)

	// 1 上锁(创建租约,自动续租,拿着租约去抢占一个key)
	lease = clientv3.NewLease(client)
	leaseResp,err = lease.Grant(context.TODO(),5)
	leaseId = leaseResp.ID

	// 准备一个用于取消自动续租的context
	ctx,cancelFunc = context.WithCancel(context.TODO())
	// 确保函数退出后续约会自动停止
	defer cancelFunc()
	defer lease.Revoke(context.TODO(),leaseId)
	// 自动续约
	if keepRespChan,err = lease.KeepAlive(ctx,leaseId);err != nil{
		log.Fatal(err)
	}
	// 处理续约应答
	go func() {
		for {
			select {
			 case keepResp =<- keepRespChan:
			 if keepResp == nil {
			 	log.Println("租约已经失效")
			 	goto END
			 }else {
			 	log.Println("收到续租应答:",keepResp.ID)
			 }
			}
		}
		END:
	}()
	// if 不存在key,then
	txn = kv.Txn(context.TODO())
	txn.If(clientv3.Compare(clientv3.CreateRevision("/cron/lock/job11"),"=",0)).
		Then(clientv3.OpPut("/cron/lock/job11","1",clientv3.WithLease(leaseId))).
		Else(clientv3.OpGet("/cron/lock/job11"))
	if txnResp,err = txn.Commit();err != nil{
		log.Fatal(err)
	}
	if !txnResp.Succeeded{
		log.Fatal("锁被占用:",string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))
	}

	log.Println("处理任务")
	time.Sleep(time.Second * 6)
	log.Println("任务处理完成")
}

你可能感兴趣的:(go,etcd)