重要特性:
底层储存是按key有序排列的,可以顺序遍历。
因为key是有序的,所以etcd天然支持按目录结构高效遍历
支持复杂事务,提供类似if…then…else…的事务能力
基于租约机制实现key的TTL过期
MVCC多版本控制:同key维护多个历史版本,用于实现watch机制。
监听KV变化。
参考: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
https://gopm.io/download 网站上填入:github.com/etcd-io/etcd
将文件夹解压到$GOPATH/src/githum.com/go.etcd.io/ 目录,并改名为etcd
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)
}
}
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
}
}
}
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))
}
}
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)
}
使用租约机制实现键值自动过期,及续约机制
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)
}
}
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)
}
}
}
}
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)
}
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("任务处理完成")
}