文章目錄
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
應用場景:
- 配置有更新的時候,etcd都會實時通知訂閱者,以此達到獲取最新配置信息的目的。
- 分佈式日誌收集,監控應用(主題)目錄下所有信息的變動。
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就被刪除了
}