安裝etcd+golang操作etcd


etcd類似java系的zookeeper,是一個高可用鍵值存儲系統,用於共享配置和服務發現,並通過raft算法保證一致性。

1. 安裝

1.1 Windows環境安裝

打開etcd官網,選擇進入github頁面,點擊Releases下載二進制文件,下載到本地,解壓
在這裏插入圖片描述
etcd是服務端程序,雙擊
在這裏插入圖片描述
可以看出etcd監聽了2379端口,是用raft工作
在這裏插入圖片描述
默認是使用v2的api,如果想使用v3 api每次打開窗口都需要敲一行命令

set ETCDCTL_API=3

put、get操作,注意不同版本的api取值也不同
在這裏插入圖片描述

1.2 Centos 7安裝

curl -L https://github.com/coreos/etcd/releases/download/v3.3.2/etcd-v3.3.2-linux-amd64.tar.gz -o etcd-v3.3.2-linux-amd64.tar.gz
tar zxf etcd-v3.3.2-linux-amd64.tar.gz

啓動的 etcd 成員在 localhost:2379 監聽客戶端請求。
通過使用 etcdctl 來和已經啓動的集羣交互:
使用etcdctlv3的版本時,需設置環境變量ETCDCTL_API=3

在`/etc/profile`文件中添加環境變量
vi /etc/profile
...
ETCDCTL_API=3
...
source /etc/profile

2. golang操作etcd

安裝etcd包:

  • 直接從https://github.com/etcd-io/etcd上下載etcd的壓縮包
  • 然後在src目錄下創建go.etcd.io文件目錄,將etcd解壓到該目錄下

2.1 put/get/delete

package main

import (
	"context"
	"fmt"
	"time"

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

func main() {
	var (
		config  clientv3.Config
		client  *clientv3.Client
		err     error
		kv      clientv3.KV
		getResp *clientv3.GetResponse
	)

	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	}

	// 建立一個客戶端
	if client, err = clientv3.New(config); err != nil {
		fmt.Println(err)
		return
	}

	// 用於讀寫etcd的鍵值對
	kv = clientv3.NewKV(client)

	// 寫入
	kv.Put(context.TODO(), "name1", "lesroad")
	kv.Put(context.TODO(), "name2", "haha")

	// 讀取name爲前綴的所有key
	if getResp, err = kv.Get(context.TODO(), "name", clientv3.WithPrefix()); err != nil {
		fmt.Println(err)
		return
	} else {
		// 獲取成功
		fmt.Println(getResp.Kvs)
	}

	// 刪除name爲前綴的所有key
	if _, err = kv.Delete(context.TODO(), "name", clientv3.WithPrevKV()); err != nil {
		fmt.Println(err)
		return
	}
}

2.2 lease

package main

import (
	"context"
	"fmt"
	"time"

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

func main() {
	var (
		config         clientv3.Config
		client         *clientv3.Client
		err            error
		lease          clientv3.Lease
		leaseGrantResp *clientv3.LeaseGrantResponse
		leaseId        clientv3.LeaseID
		putResp        *clientv3.PutResponse
		kv             clientv3.KV
		getResp        *clientv3.GetResponse
	)

	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	}

	// 建立一個客戶端
	if client, err = clientv3.New(config); err != nil {
		fmt.Println(err)
		return
	}

	// 申請一個lease(租約)
	lease = clientv3.NewLease(client)

	// 申請一個10秒的lease
	if leaseGrantResp, err = lease.Grant(context.TODO(), 10); err != nil {
		fmt.Println(err)
		return
	}

	// 拿到租約id
	leaseId = leaseGrantResp.ID

	// 獲得kv api子集
	kv = clientv3.NewKV(client)

	// put一個kv,讓它與租約關聯起來
	if putResp, err = kv.Put(context.TODO(), "name", "lbwnb", clientv3.WithLease(leaseId)); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("寫入成功", putResp.Header.Revision)

	// 定時看下key過期了沒有
	for {
		if getResp, err = kv.Get(context.TODO(), "name"); err != nil {
			fmt.Println(err)
			return
		}
		if getResp.Count == 0 {
			fmt.Println("kv過期")
			break
		}

		fmt.Println("還沒過期", getResp.Kvs)
		time.Sleep(2 * time.Second)
	}
}

2.3 watch

應用場景:

  1. 配置有更新的時候,etcd都會實時通知訂閱者,以此達到獲取最新配置信息的目的。
  2. 分佈式日誌收集,監控應用(主題)目錄下所有信息的變動。
package main

import (
	"context"
	"fmt"
	"time"

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

func main() {
	var (
		config             clientv3.Config
		client             *clientv3.Client
		err                error
		kv                 clientv3.KV
		watchStartRevision int64
		watcher            clientv3.Watcher
		watchRespChan      <-chan clientv3.WatchResponse
		watchResp          clientv3.WatchResponse
		event              *clientv3.Event
	)

	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	}

	// 建立一個客戶端
	if client, err = clientv3.New(config); err != nil {
		fmt.Println(err)
		return
	}

	// 用於讀寫etcd的鍵值對
	kv = clientv3.NewKV(client)

	// 模擬etcd中kv的變化
	go func() {
		for {
			kv.Put(context.TODO(), "name", "lesroad")

			kv.Delete(context.TODO(), "name")

			time.Sleep(1 * time.Second)
		}
	}()

	// 創建一個監聽器
	watcher = clientv3.NewWatcher(client)

	// 啓動監聽 5秒後關閉
	ctx, cancelFunc := context.WithCancel(context.TODO())
	time.AfterFunc(5*time.Second, func() {
		cancelFunc()
	})
	watchRespChan = watcher.Watch(ctx, "name", clientv3.WithRev(watchStartRevision))

	// 處理kv變化事件
	for watchResp = range watchRespChan {
		for _, event = range watchResp.Events {
			switch event.Type {
			case mvccpb.PUT:
				fmt.Println("修改爲", string(event.Kv.Value))
			case mvccpb.DELETE:
				fmt.Println("刪除了", string(event.Kv.Key))
			}
		}
	}
}

2.4 op操作替代get、put

package main

import (
	"context"
	"fmt"
	"time"

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

func main() {
	var (
		config clientv3.Config
		client *clientv3.Client
		err    error
		kv     clientv3.KV
		putOp  clientv3.Op
		getOp  clientv3.Op
		opResp clientv3.OpResponse
	)

	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	}

	// 建立一個客戶端
	if client, err = clientv3.New(config); err != nil {
		fmt.Println(err)
		return
	}

	kv = clientv3.NewKV(client)

	// 創建Op :operator
	putOp = clientv3.OpPut("op", "replace")

	// 執行Op 用kv.Do取代 kv.Put kv.Get...
	if opResp, err = kv.Do(context.TODO(), putOp); err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("寫入Revision", opResp.Put().Header.Revision)

	// 創建Op
	getOp = clientv3.OpGet("op")

	// 執行Op
	if opResp, err = kv.Do(context.TODO(), getOp); err != nil {
		fmt.Println(err)
		return
	}

	fmt.Println("數據Revision", opResp.Get().Kvs[0].ModRevision)
	fmt.Println("數據value", string(opResp.Get().Kvs[0].Value))
}

2.5 事務txn實現分佈式鎖

package main

import (
	"context"
	"fmt"
	"time"

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

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

	config = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	}

	// 建立一個客戶端
	if client, err = clientv3.New(config); err != nil {
		fmt.Println(err)
		return
	}

	// lease實現鎖自動過期
	// op操作
	// txn事務: if else then

	// 1 上鎖 創建租約 自動續租 拿着租約去搶佔一個key
	lease = clientv3.NewLease(client)

	// 申請一個5秒的lease
	if leaseGrantResp, err = lease.Grant(context.TODO(), 5); err != nil {
		fmt.Println(err)
		return
	}

	// 拿到租約id
	leaseId = leaseGrantResp.ID

	// 準備一個用於取消自動續租的context
	ctx, cancelFunc = context.WithCancel(context.TODO())

	if keepRespChan, err = lease.KeepAlive(ctx, leaseId); err != nil {
		fmt.Println(err)
		return
	}

	// 確保函數退出後,取消自動續租
	defer cancelFunc()                          // 取消續租
	defer lease.Revoke(context.TODO(), leaseId) // 釋放租約

	// 處理續租應答的協程
	go func() {
		select {
		case keepResp = <-keepRespChan:
			if keepRespChan == nil {
				fmt.Println("租約失效")
				goto END
			} else {
				fmt.Println("收到自動續租應答", keepResp.ID)
			}
		}
	END:
	}()

	// if 不存在key then 設置它 else 搶鎖失敗
	kv = clientv3.NewKV(client)

	// 創建事務
	txn = kv.Txn(context.TODO())

	// 定義事務
	// 如果key不存在
	txn.If(clientv3.Compare(clientv3.CreateRevision("mutex"), "=", 0)).
		Then(clientv3.OpPut("mutex", "yes", clientv3.WithLease(leaseId))).
		Else(clientv3.OpGet("mutex")) // 否則搶鎖失敗

	// 提交事務
	if txnResp, err = txn.Commit(); err != nil {
		fmt.Println(err)
		return
	}

	// 判斷是否搶到了鎖
	if !txnResp.Succeeded {
		fmt.Println("鎖被佔用", string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))
		return
	}

	// 2 處理業務
	fmt.Println("處理任務")
	time.Sleep(5 * time.Second)
	// 在鎖內 很安全

	// 3 釋放鎖 取消自動續租 釋放租約
	// defer 會把租約釋放掉,關聯的kv就被刪除了
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章