etcd clientv3 全api文档 之 KV

无论是新使用etcd还是需要更深一点了解etcd的人可能都会遇到我曾经遇到的问题:为什么文档这么少?为什么官方文档都不全?这个api怎么用,是什么?网上的教程简略到只有基础方法,各个网页还都是千篇一律的复制粘贴,让当初在使用etcd的我很痛苦。正好在我想要开源一个etcd的web panel的时候,我决定写出一篇文档,从源码下手,对每个api的用法讲解出来。不仅有助于我开源项目的理解,也可以帮助到对etcd不熟悉的小伙伴们。在学习etcd之前,有一个有意思的点可以帮助你理解etcd,就是将所有的key看成水平分布的key,而不是树形结构的key,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
/a
/b
/d
/e
/c
/f
看做
/a
/a/b
/a/b/d
/a/b/e
/a/c
/a/c/f

这样在使用如withPrefix方法时可以更清楚的知道你到底查找、删除了哪一系列的key。

关于clientv3的讲解不涉及具体的etcd源码分析,具体的源码分析会在后面写出。

before started

本文依照的是etcd 版本 3.5.1,etcd的v2与v3区别很大,改变了很多原理,如auth的方式等等;而v3中 3.1 3.2 3.3 3.4 3.5也各不相同,但改动不大,在观看之前需要看清楚版本。具体各个版本的区别再后面会单独写一章。

get started

在etcd全api文档的第一篇,我决定先从最常用的KV下手,包括了存取删除和事务等,基本是新手开始使用etcd最需要知道的几个方法。后面的文章也会以文件为分割来讲述etcd的源码及api使用方法。

KV struct

1
2
3
4
type kv struct {
remote pb.KVClient
callOpts []grpc.CallOption
}

第一步,创建一个连接

首先,你要先创建一个连接,然后才能进行后续的操作

1
2
3
4
5
6
7
8
cli, err := clientv3.New(clientv3.Config{
Endpoints: "example.endpoint",
DialTimeout: 5 * time.Second
})
if err := nil {
return err
}
defer cli.Close()

注意点

  • 你应该在使用完连接后立即关闭连接,未关闭的连接会使goroutine泄露
  • 虽然使用defer非常方便,也防止你后面忘记关闭连接。但是我更喜欢在操作完方法之后手动调用cli.Close()而不是使用defer,因为每个人的业务场景不一样,方法并不一定会在短时间内结束,可能一次bug导致这个方法一直存活着,你的defer也就一直得不到调用。

NewKV

使用kv与直接使用cli连接调用的区别不大,kv内可以调用的方法与cli一样,具体为调用NewKV会将cli身上的callOpts带到kv上

1
2
3
4
5
6
7
8
func NewKV(c *Client) KV {
api := &kv{remote: RetryKVClient(c)}
if c != nil {
api.callOpts = c.callOpts
}

return api
}

Put

将一个key-value键值对推入etcd

put方法

1
func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)

put的调用(使用kv)

1
2
3
kv := clientv3.NewKV(cli)

resp, err := kv.Put(context.Background(), key, val, ...opts)

put的具体实现

1
2
3
4
func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) {
r, err := kv.Do(ctx, OpPut(key, val, opts...))
return r.put, toErr(ctx, err)
}

关于Do方法

Get

取回keys,默认会返回你传入key的value,如果传入

  • WithRange(end), get会返回[key, end)范围内的结果
  • WithFromKey(), get会返回比你传入key大或者相等的key
  • WithRev(rev) 且 rev > 0,get会返回传入key指定版本的value,如果指定的版本(rev)被压缩(compacted)了,会返回error: ErrCompacted。
  • WithLimit(limit),会返回指定数量内的keys
  • WithSort(), 会对返回的keys进行过滤

get方法

1
Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)

get的调用(使用cli)

1
resp, err := cli.Put(context.Background(), key, ...opts)

get的具体实现

1
2
3
4
func (kv *kv) Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) {
r, err := kv.Do(ctx, OpGet(key, opts...))
return r.get, toErr(ctx, err)
}

关于Do方法

Delete

删除一个key,或者

  • 使用WithRange(end)来删除[key, range)范围的key
  • 使用WithPrefix()来删除以key开头的key

delete方法

1
Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)

delete的调用方式

1
resp, err = cli.Delete(ctx, key, ...opts)

delete的具体实现

1
2
r, err := kv.Do(ctx, OpDelete(key, opts...))
return r.del, toErr(ctx, err)

关于Do方法

Do

Do这个方法为你可以将多种/次操作融合到一个方法内,并在之后(重复)调用。具体如何使用除了官方方法外,就要看具体的业务需求。如你可以将put->get->delete这一套流程融合到一个Do里。通过源码可以知道,官方的get、put等也是通过Do方法来实现,然后在Do内实现具体的get、put的etcd调用,区分于事务级别的调用。

Do方法

1
Do(ctx context.Context, op Op) (OpResponse, error)

Do的调用方式

1
2
3
4
5
6
7
8
9
10
ops := []clientv3.Op{
clientv3.OpPut("put-key", "123"),
clientv3.OpGet("put-key"),
clientv3.OpPut("put-key", "456")}

for _, op := range ops {
if _, err := cli.Do(context.TODO(), op); err != nil {
log.Fatal(err)
}
}

Do的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
var err error
switch op.t { // 对传入的op遍历
case tRange:
var resp *pb.RangeResponse
resp, err = kv.remote.Range(ctx, op.toRangeRequest(), kv.callOpts...)
if err == nil {
return OpResponse{get: (*GetResponse)(resp)}, nil
}
case tPut:
var resp *pb.PutResponse
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease}
resp, err = kv.remote.Put(ctx, r, kv.callOpts...)
if err == nil {
return OpResponse{put: (*PutResponse)(resp)}, nil
}
case tDeleteRange:
var resp *pb.DeleteRangeResponse
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}
resp, err = kv.remote.DeleteRange(ctx, r, kv.callOpts...)
if err == nil {
return OpResponse{del: (*DeleteResponse)(resp)}, nil
}
case tTxn:
var resp *pb.TxnResponse
resp, err = kv.remote.Txn(ctx, op.toTxnRequest(), kv.callOpts...)
if err == nil {
return OpResponse{txn: (*TxnResponse)(resp)}, nil
}
default:
panic("Unknown op")
}
return OpResponse{}, toErr(ctx, err)
}

Txn

txn用来创建一个事务,将后续操作以事务进行, 事务有四种子方法

  • If
  • Then
  • Else
  • Commit

Txn方法

1
Txn(ctx context.Context) Txn

txn的调用例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kvc := clientv3.NewKV(cli)

_, err = kvc.Put(context.TODO(), "key", "xyz")
if err != nil {
log.Fatal(err)
}

ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
_, err = kvc.Txn(ctx).
// 事务的值是字典类型的比较
If(clientv3.Compare(clientv3.Value("key"), ">", "abc")).
// "xyz" > "abc" 时运行
Then(clientv3.OpPut("key", "XYZ")).
// else运行
Else(clientv3.OpPut("key", "ABC")).
Commit()

txn的源码

1
2
3
4
5
6
7
func (kv *kv) Txn(ctx context.Context) Txn {
return &txn{
kv: kv,
ctx: ctx,
callOpts: kv.callOpts,
}
}

Compact

compact,压缩key-value,为了防止历史记录越来越大。传入指定的rev,会压缩rev之后的key-value历史

compact方法

1
Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error)

compact的调用方式

1
2
3
4
5
6
7
8
9
resp, err := cli.Get(ctx, "foo")
cancel()
if err != nil {
log.Fatal(err)
}
compRev := resp.Header.Revision // 具体你要压缩哪个版本之前的key-val

ctx, cancel = context.WithTimeout(context.Background(), requestTimeout)
_, err = cli.Compact(ctx, compRev)

compact的具体实现

1
2
3
4
5
6
7
func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) {
resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest(), kv.callOpts...)
if err != nil {
return nil, toErr(ctx, err)
}
return (*CompactResponse)(resp), err
}

end

这是关于clientv3 api讲解的第一章,如果你有什么建议、想法、编写错误的指出或者好的想法,欢迎通过email或issue的方式来告诉我。

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2019-2021 Jayj
  • 访问人数: | 浏览次数:

buy me a cup of coffee?

支付宝
微信