etcd 笔记(06)— Client 结构定义、客户端(初始化、KV存储Get、Put、事务 Txn、压缩 Compact、Watch、Lease

1. Client 定义

Client 定义如下:

type Client struct {
    Cluster
    KV
    Lease
    Watcher
    Auth
    Maintenance
    // 认证的用户名
    Username string
    // 认证的密码
    Password string
}

这里显示的都是可导出的模块结构字段,代表了客户端能够使用的几大核心模块,具体功能介绍如下。

  • Cluster:向集群里增加 etcd 服务端节点之类,属于管理员操作;
  • KV:我们主要使用的功能,即操作 K-V
  • Lease:租约相关操作,比如申请一个 TTL=10 秒的租约;
  • Watcher:观察订阅,从而监听最新的数据变化;
  • Auth:管理 etcd 的用户和权限,属于管理员操作;
  • Maintenance:维护 etcd,比如主动迁移 etcdleader 节点,属于管理员操作;

2. gRPC 服务

etcd v3 的通信基于 gRPCproto文件是定义服务端和客户端通信接口的标准。包括:

  • 客户端该传什么样的参数
  • 服务端该返回什么参数
  • 客户端该怎么调用
  • 是阻塞还是非阻塞
  • 是同步还是异步

gRPC 推荐使用 proto3 消息格式,proto3 是原有 Protocol Buffer 2(被称为 proto2)的升级版本,删除了一部分特性,优化了对移动设备的支持。

发送到 etcd 服务器的每个 API 请求都是一个 gRPC 远程过程调用。etcd 中的 RPC 接口定义根据功能分类到服务中。

处理 etcd 键值的重要服务包括:

  • KV Service:创建、更新、获取和删除键值对;
  • Watch Service:监视键的更改;
  • Lease Service:实现键值对过期,客户端用来续租、保持心跳;
  • Lock Serviceetcd 提供分布式共享锁的支持;
  • Election Service:暴露客户端选举机制;

3. 请求和响应

3.1 请求

etcd3 中的所有 RPC 都遵循相同的格式。每个 RPC 都有一个函数名,该函数将 NameRequest 作为参数并返回 NameResponse 作为响应。例如,这是 Range RPC 描述:

service KV {
  Range(RangeRequest) returns (RangeResponse)
  ...
}

3.2 响应头

etcd API 的所有响应都有一个附加的响应标头,其中包括响应的集群元数据:

message ResponseHeader {
  uint64 cluster_id = 1;
  uint64 member_id = 2;
  int64 revision = 3;
  uint64 raft_term = 4;
}

其中:

  • Cluster_ID:产生响应的集群的 ID
  • Member_ID:产生响应的成员的 ID;

应用服务可以通过 Cluster_IDMember_ID 字段来确保当前与之通信的正是预期的那个集群或者成员。

  • Revision:产生响应时键值存储的修订版本号;

应用服务可以使用修订号字段来获得当前键值存储库最新的修订号。应用程序指定历史修订版以进行查询,如果希望在请求时知道最新修订版,此功能特别有用。

  • Raft_Term:产生响应时,成员的 Raft 称谓。

应用服务可以使用 Raft_Term 来检测集群何时完成一个新的 leader 选举。

4. etcd clientv3 客户端

4.1 初始化

我们根据指定的 etcd 节点,建立客户端与 etcd 集群的连接:

config := clientv3.Config{
    Endpoints:[]string{"localhost:2379", "localhost:2379"},
    DialTimeout: 5 * time.Second,
}
client, err := clientv3.New(config)

// etcd clientv3 >= v3.2.10, grpc/grpc-go >= v1.7.3
if client == nil || err == context.DeadlineExceeded {
	// handle errors
	fmt.Println(err)
	panic("invalid connection!")
}
// 客户端断开连接
defer client.Close()

如上的代码实例化了一个 client,这里需要传入两个参数。

  • Endpointsetcd 的多个节点服务地址;

  • DialTimeout:创建 client 的首次连接超时时间,这里传了 5 秒,如果 5 秒都没有连接成功就会返回 err

需要注意的是,一旦 client 创建成功,我们就不用再关心后续底层连接的状态了,client 内部会重连。

4.2 KV 存储

KV 对象的实例获取通过如下的方式:

kv := clientv3.NewKV(client)

KV 接口的具体定义:

type KV interface {
    Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)
    // 检索 keys
    Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)
    // 删除 key,可以使用 WithRange(end), [key, end) 的方式
    Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)
    // 压缩给定版本之前的 KV 历史
    Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error)
    // 指定某种没有事务的操作
    Do(ctx context.Context, op Op) (OpResponse, error)
    // Txn 创建一个事务
    Txn(ctx context.Context) Txn
}

KV 对象的定义我们可知,它就是一个接口对象,包含以下几个主要的 KV 操作方法。

4.2.1 Put

Put 的定义如下:

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

其中的参数

  • ctxContext 包对象,用来跟踪上下文,比如超时控制;
  • key:存储对象的 key
  • val:存储对象的 value
  • opts:可变参数,额外选项;

使用示例:

putResp, err := kv.Put(context.TODO(),"aa", "hello-world!")

4.2.2 Get

Get 的定义如下:

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

OpOption 为可选的函数传参:

  • 传参为 WithRange(end) 时,Get 将返回 [key,end) 范围内的键;
  • 传参为 WithFromKey() 时,Get 返回大于或等于 key 的键;
  • 当通过 rev> 0 传递 WithRev(rev) 时,Get 查询给定修订版本的键;
  • 如果压缩了所查找的修订版本,则返回请求失败,并显示 ErrCompacted
  • 传递 WithLimit(limit) 时,返回的 key 数量受 limit 限制;
  • 传参为 WithSort 时,将对键进行排序;

对应的使用方法如下:

getResp, err := kv.Get(context.TODO(), "aa")

从上面可以看出,Put 返回 PutResponseGet 返回 GetResponse。注意:不同的 KV 操作对应不同的 Response 结构,定义如下:

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

下面我们分别来看一看 PutResponseGetResponse 映射的 RangeResponse 结构的定义:

type PutResponse struct {
    Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`

    // 请求中如有 prev_kv,响应时也会携带 prev_kv 
    PrevKv *mvccpb.KeyValue `protobuf:"bytes,2,opt,name=prev_kv,json=prevKv" json:"prev_kv,omitempty"`
}
//Header 里保存的主要是本次更新的 revision 信息
type RangeResponse struct {
    Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`

    // kvs 是一个匹配 range 请求的键值对列表
    Kvs []*mvccpb.KeyValue `protobuf:"bytes,2,rep,name=kvs" json:"kvs,omitempty"`

    // more 用以分页 
    More bool `protobuf:"varint,3,opt,name=more,proto3" json:"more,omitempty"`

    // count 表示 range 的键值对数量
    Count int64 `protobuf:"varint,4,opt,name=count,proto3" json:"count,omitempty"`
}

Kvs 字段,保存了本次 Get 查询到的所有 KV 对,我们继续看一下 mvccpb.KeyValue 对象的定义:

type KeyValue struct {

    Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`

    // create_revision 是当前 key 的最后创建版本
    CreateRevision int64 `protobuf:"varint,2,opt,name=create_revision,json=createRevision,proto3" json:"create_revision,omitempty"`

    // mod_revision 是指当前 key 的最新修订版本
    ModRevision int64 `protobuf:"varint,3,opt,name=mod_revision,json=modRevision,proto3" json:"mod_revision,omitempty"`

    // key 的版本,每次更新都会增加版本号
    Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"`

    Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`

    // 绑定了 key 的租期 Id,当 lease 为 0 ,则表明没有绑定 key;租期过期,则会删除 key
    Lease int64 `protobuf:"varint,6,opt,name=lease,proto3" json:"lease,omitempty"`
}

至于 RangeResponse.MoreCount,当我们使用 withLimit() 选项进行 Get 时会发挥作用,相当于分页查询。

通过一个特别的 Get 选项,获取 aa 目录下的所有子目录:

rangeResp, err := kv.Get(context.TODO(), "/aa", clientv3.WithPrefix())

WithPrefix() 用于查找以 /aa为前缀的所有 key,因此可以模拟出查找子目录的效果。我们知道 etcd 是一个有序的 KV 存储,因此 /aa 为前缀的 key 总是顺序排列在一起。

WithPrefix 实际上会转化为范围查询,它根据前缀 /aa 生成了一个 key range[“/aa/”, “/aa0”),这是因为比 /大的字符是 0,所以以 /aa0 作为范围的末尾,就可以扫描到所有的 /aa/ 打头的 key 了。

4.3 事务 Txn

Txn 方法在单个事务中处理多个请求。Txn 请求增加键值存储的修订版本,并为每个完成的请求生成带有相同修订版本的事件,etcd 不容许在一个 Txn 中多次修改同一个 key

Txn 接口定义如下:

rpc Txn(TxnRequest) returns (TxnResponse) {}

4.4 Compact

Compact 方法压缩 etcd 键值对存储中的事件历史。键值对存储应该定期压缩,否则事件历史会无限制地持续增长。

Compact 接口定义如下:

rpc Compact(CompactionRequest) returns (CompactionResponse) {}

请求的消息体是 CompactionRequestCompactionRequest 压缩键值对存储到给定修订版本,所有修订版本比压缩修订版本小的键都将被删除。

4.5 Watch

Watch API 提供了一个基于事件的接口,用于异步监视键的更改。etcd 监视程序通过给定的修订版本(当前版本或历史版本)持续监视 key 更改,并将 key 更新流回客户端。

rpc.protoWatch Service 定义如下:

service Watch {
  rpc Watch(stream WatchRequest) returns (stream WatchResponse) {}
}

Watch 观察将要发生或者已经发生的事件。输入和输出都是流,输入流用于创建和取消观察,而输出流发送事件。一个观察 RPC 可以一次性在多个 key 范围上观察,并为多个观察流化事件。整个事件历史可以从最后压缩修订版本开始观察。Watch Service 只有一个 Watch 方法。

4.6 Lease Service

Lease Service 提供租约的支持。Lease 是一种检测客户端存活状况的机制。集群授予客户端具有生存时间的租约。如果 etcd 集群在给定的 TTL 时间内未收到 keepAlive,则租约到期。

为了将租约绑定到键值存储中,每个 key 最多可以附加一个租约。当租约到期或被撤销时,该租约依附的所有 key 都将被删除,每个过期的密钥都会在事件历史记录中生成一个删除事件。

rpc.protoLease Service 定义的接口如下:

service Lease {
  rpc LeaseGrant(LeaseGrantRequest) returns (LeaseGrantResponse) {}
  rpc LeaseRevoke(LeaseRevokeRequest) returns (LeaseRevokeResponse) {}
  rpc LeaseKeepAlive(stream LeaseKeepAliveRequest) returns (stream LeaseKeepAliveResponse) {}
  rpc LeaseTimeToLive(LeaseTimeToLiveRequest) returns (LeaseTimeToLiveResponse) {}
}

其中:

  • LeaseGrant 创建一个租约;
  • LeaseRevoke 撤销一个租约;
  • LeaseKeepAlive 用于维持租约;
  • LeaseTimeToLive 获取租约信息;

4.7 Lock Service

Lock Service 提供分布式共享锁的支持。Lock ServicegRPC 接口的方式暴露客户端锁机制。

v3lock.protoLock Service 定义如下:

service Lock {
  rpc Lock(LockRequest) returns (LockResponse) {}
  rpc Unlock(UnlockRequest) returns (UnlockResponse) {}
}

其中:

  • Lock 方法,在给定命令锁上获得分布式共享锁;
  • Unlock 使用 Lock 返回的 key 并释放对锁的持有;

参考:
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=613#/detail/pc?id=6403

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wohu007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值