go etcd

etcd 是由Go语言编写的 key-value 存储, 主要用途是共享配置和服务发现

分布式系统之间必然要做到数据共享, 需要依赖一个可靠的共享存储服务, 而etcd能够提供这样的服务

etcd类似的项目: zookeeper和consul

etcd常见的两个版本v2和v3, 它们是两个独立的应用

文档建议使用v3版本, 下载: https://github.com/coreos/etcd/releases

解压后:

go etcd_第1张图片

 启动服务:etcd.exe (双击即可开启服务)

客户端:etcdctl.exe(在当前目录下cmd打开命令行)

在当前命令行下存储:

go etcd_第2张图片

etcdctl version             查看版本(包括API的版本)

etcdctl put "键" "值"     放入键值对(如果已存在该key则会覆盖掉原来的值)

etcdctl get "键"            通过键获取值

etcdctl del "键"            通过键删除

 

golang中使用etcd:

1.首先要安装etcd: 可以手动下载, 地址栏输入 -> https://github.com/etcd-io/etcd.git

2.下载以后, 要根据导入包的格式创建目录, 如下:

package xx
import (
    "go.etcd.io/etcd/clientv3"
)

在 %gopath%/src 下创建目录go.etcd.io/etcd/  --> %gopath%/src/go.etcd.io/etcd

3.将下载的项目放在创建的目录下面, 如下创建go.etcd.io目录

D:\golang\workspace\project\src\

注: 使用go get方式不一定能下载成功, 当不能成功下载时, 可以使用如上的手动方式

使用手动方式, 在创建目录的时候要注意, 它根据导包时(import  "go.etcd.io/etcd/clientv3")的格式创建 

意思是: 第一个目录是go.etcd.io, 第二个目录是etcd, 最后一个是包名(不需要创建)

所以这里要创建目录为go.etcd.io/etcd           -->将下载的内容放在etcd目录下面

 

1.创建client:

要使用etcd, 首先就是创建client, 这需要根据配置

这个配置是一个结构体类型的实例, 配置中包括主机, 超时等字段

conf := clientv3.Config {
     Endpoints : []string {"localhost:2380"},
     DialTimeout : 3 * time.Second,
}
client := clientv3.New(conf)

client代表etcd客户端, 它是*Client类型

Client代表了整个客户端, 它是结构体类型, 包括如下核心字段:

  • KV:我们主要使用的K-V操作, iterface
  • Lease:租约相关操作, iterface
  • Watcher:监听数据变化, interface

2.获取KV:

字段KV是我们主要使用的键值操作

KV是一个interface, 无法直接使用

但是etcd提供了一个函数直接获取字段KV的实例, 如下:

kv := clientv3.NewKV(client)

现在得到字段KV的实例后, 就可以做kv操作了:

_, err := kv.Put(context.TODO(), key, value)        // 存入
resp, err := kv.Get(context.TODO(), key)            // 获取

3.解析resp

如果是向etcd中存入值, 则返回*GetResponse

如果是从etcd中获取值, 则返回*PutResponse

如果是从etcd中删除, 则返回*DeleteResponse

上面是get操作, 所以resp是*GetResponse类型的实例 (put/delete操作也是类似的)

使用resp获取所有的"孩子" --> res.Kvs (Kvs是GetResponse里面的一个字段, 它是一个切片)

循环res.Kvs如下

for _,val := range resp.Kvs {   // val即表示单个"孩子"
    val := string(val.Value)    // 获取Value, 但是它是字节数组, 所以转换为string
    fmt.Println(val)
}

它即使存取中文也没有问题

4.更多:

.使用像目录结构这种形式的string 作为key

虽然用法和上面的没有区别, 但是它代表目录层级的意思

putResp, err := kv.Put(context.TODO(),"/test/a", "something")

.Get/put/delete行为

如上面的get操作, 它是根据key获取value, 可以通过参数来影响get的行为

getResp, err := kv.Get(context.TODO(), "/test/", clientv3.WithPrefix())
putResp, err := kv.Put(context.TODO(), "/test/", clientv3.WithPrevKV())
delResp, err := kv.Delete(context.TODO(), "/test/", clientv3.WithPrevKV())

clientv3.WithPrefix(), 表示以"/test/"开始查找, 不再是完全匹配

clientv3.WithPrevKV(), 表示需要返回put时覆盖的值或 删除的值

 

5.获取Lease:

上面获取了Client的KV字段, 现在获取Client的Lease字段, 与获取KV类似

lease := clientv3.NewLease(client)

 Lease也是接口, 提供如下功能:

type Lease interface {
    Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error)
    Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error)
    TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error)
    Leases(ctx context.Context) (*LeaseLeasesResponse, error)
    KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error)
    KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error)
    Close() error
}

Grant 分配租约 

Revoke 释放租约 

TimeToLive 获取剩余存活时间(TTL)

Leases 获取所有租约

KeepAlive 续约租约 

KeepAliveOnce 为租约续约一次

Close 关闭所有租约

 

7.创建租约并根据租约存入值

var kv = clientv3.NewKV(client)            // 获取KV
lease := clientv3.NewLease(client)         // 获取Lease
grantResp , err := lease.Grant(context.TODO(), 10)    // 创建一个10秒钟的租约
if err!=nil {
    return
}

// 根据租约put一个会过期的值(如果没有第四个参数, 则put的是永久的值)
kv.Put(context.TODO(), "/test/t", "value_test", clientv3.WithLease(grantResp.ID))

8.op操作 (在事务中得使用op操作)

使用op操作 增 删 查, 可以代替前面使用的kv.Put/Get 以及Delete

它是先包装好要使用的方式(get/put/delete), 再统一使用do来操作

直接使用kv操作和使用op操作, 它们其实是一样的

 

op := clientv3.OpGet("p") 
opResp, err := kv.Do(context.TODO(), op) 
if err != nil { 
    return 
} 
for _, val := range opResp.Get().Kvs {     // 调用Get()
    fmt.Println(string(val.Value)) 
} 

----------------------- 

op := clientv3.OpPut("x2", "xxx", clientv3.WithPrevKV()) 
// 第三个参数用于返回的值 
opResp, err := kv.Do(context.TODO(), op) if err != nil { 
    return 
} 
// 它返回的是被覆盖的value值 
// 如果之前里面没有该key, 则表示没有该key对应的value, 返回nil 
// 所以在此情况下要考虑到空指针的情况 
preKV := opResp.Put().PrevKv             // 调用Put()
if preKV == nil { 
    fmt.Println("没有返回值") 
} else { 
    fmt.Println(string(preKV.Value)) 

} 

----------------------- 

op := clientv3.OpDelete("x2", clientv3.WithPrevKV()) 
opResp, err := kv.Do(context.TODO(), op) 
if err != nil { 
    return 
} 
value := opResp.Del().PrevKvs[0].Value     // 调用Del()
fmt.Println(string(value))

它们的返回值是OpResponse, 根据包装的方式调用Get() / Put() / Del() 

得到对应的*GetResponse / *PutResponse / *DeleteResponse, 其结构如下:

type OpResponse struct {
   put *PutResponse
   get *GetResponse
   del *DeleteResponse
   txn *TxnResponse
}

9.事务txn:

事务一般就是为了保持原子性操作

开启事务 --> txn := kv.Txn(context.TODO())

事务的使用  -->  If(条件). Then(执行若干操作).Else(执行若干操作).Commit()

.If -- 事务的条件判断, 可以有多个条件

        clientv3.Compare(clientv3.Value(key), "=", this_value)

.Then -- 当If为true时执行的操作

       clientv3.OpGet(key)

.Else -- 当If的false时执行的操作

       clientv3.OpPut(key, value)

.Commit -- 提交事务

func main() {
    kv := clientv3.NewKV(client)

    txn := kv.Txn(context.TODO())   // 开启事务

    txnResp, err := txn.If(clientv3.Compare(clientv3.Value("abc"), "=", "abc1234")).     
    Then(clientv3.OpGet("a"), clientv3.OpGet("b")).              
    Else(clientv3.OpPut("abc", "abc123", clientv3.WithPrevKV())).
    Commit()

    if err != nil {
        fmt.Println("err: ", err)
        return
    }

    // 判断If操作是否成功
    if txnResp.Succeeded { 
        // 和前面使用KV.Do()相似, 都是获取OpResponse
        // 再调GetResponseRange()获取GetResponse(别名, 见下面) 或 调用GetResponsePut()获取PutResponse
        fmt.Println(txnResp.Responses[0].GetResponseRange().Kvs) 
        
        // 在获取到Kvs时其实应该判断它的长度是否为0
        if len(txnResp.Responses[1].GetResponseRange().Kvs) == 0 {      // GetResponseRange
            fmt.Println("没有放入kv")
            return
        }
        fmt.Println(string(txnResp.Responses[1].GetResponseRange().Kvs[0].Value))
    } else { 
        // 判断*KeyValue是否为空
        if txnResp.Responses[0].GetResponsePut().PrevKv == nil {     // GetResponsePut
            fmt.Println("nil")
            return
        }
        
        fmt.Println(string(txnResp.Responses[0].GetResponsePut().PrevKv.Value))
    }
}

Txn的结构如下:

type Txn interface {
    If(cs ...Cmp) Txn
    Then(ops ...Op) Txn
    Else(ops ...Op) Txn
    Commit() (*TxnResponse, error)
}

注: 它这时里有别名的使用

GetResponse -- RangeResponse

PutResponse -- PutResponse(这个是原样)

再看上面代码 txnResp.Responses[0].GetResponseRange().Kvs 就好理解了

type (
    CompactResponse pb.CompactionResponse
    PutResponse pb.PutResponse
    GetResponse pb.RangeResponse
    DeleteResponse pb.DeleteRangeResponse
    TxnResponse pb.TxnResponse
)

方法名称真不好区分, 记住中转站: OpResponse, 通过它来获取返回类型

put -- GetResponsePut  

get -- GetResponseRange  

delete -- GetResponseDeleteRange

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