Loading

Etcd 中 Revision, CreateRevision, ModRevision, Version 的含义

结论

etcd mvcc 中的 Version, Revision, ModRevision, CreateRevision 到底都是什么意思?如果服务 watch etcd 订阅消息,该如何使用呢?

实验部分不想看的,可以只看结论:

  1. Revision
    作用域为集群,逻辑时间戳,全局单调递增,任何 key 的增删改都会使其自增
  2. CreateRevision
    作用域为 key, 等于创建这个 key 时集群的 Revision, 直到删除前都保持不变
  3. ModRevision
    作用域为 key, 等于修改这个 key 时集群的 Revision, 只要这个 key 更新都会自增
  4. Version
    作用域为 key, 这个key刚创建时Version为1,之后每次更新都会自增,即这个key从创建以来更新的总次数。

关于 watch 哪个版本:

  1. watch 某一个 key 时,想要从历史记录开始就用 CreateRevision,最新一条(这一条直接返回) 开始就用 ModRevision
  2. watch 某个前缀,就必须使用 Revision。如果要watch当前前缀后续的变化,则应该从当前集群的 Revision+1 版本开始watch。

版本都有哪些?

我们都知道 etcd 支持 mvcc来实现高并发,比如我们 Get 某个 key 时返回结果是一个 RangeResponse

type RangeResponse struct {
    Header *ResponseHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
    // kvs is the list of key-value pairs matched by the range request.
    // kvs is empty when count is requested.
    Kvs []*mvccpb.KeyValue `protobuf:"bytes,2,rep,name=kvs,proto3" json:"kvs,omitempty"`
    // more indicates if there are more keys to return in the requested range.
    More bool `protobuf:"varint,3,opt,name=more,proto3" json:"more,omitempty"`
    // count is set to the number of keys within the range when requested.
    Count                int64    `protobuf:"varint,4,opt,name=count,proto3" json:"c
}

返回结构体中,Header
是响应头,里面有很多系统信息,Kvs
是我们获得的 kv 数组,里面有我们想要的数据。

type ResponseHeader struct {
    // cluster_id is the ID of the cluster which sent the response.
    ClusterId uint64 `protobuf:"varint,1,opt,name=cluster_id,json=clusterId,proto3" json:"cluster_id,omitempty"`
    // member_id is the ID of the member which sent the response.
    MemberId uint64 `protobuf:"varint,2,opt,name=member_id,json=memberId,proto3" json:"member_id,omitempty"`
    // revision is the key-value store revision when the request was applied.
    // For watch progress responses, the header.revision indicates progress. All future events
    // recieved in this stream are guaranteed to have a higher revision number than the
    // header.revision number.
    Revision int64 `protobuf:"varint,3,opt,name=revision,proto3" json:"revision,omitempty"`
    // raft_term is the raft term when the request was applied.
    RaftTerm             uint64   `protobuf:"varint,4,opt,name=raft_term,json=raftTerm,proto3" json:"raft_term,omitempty"`
}
type KeyValue struct {
    // key is the key in bytes. An empty key is not allowed.
    Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
    // create_revision is the revision of last creation on this key.
    CreateRevision int64 `protobuf:"varint,2,opt,name=create_revision,json=createRevision,proto3" json:"create_revision,omitempty"`
    // mod_revision is the revision of last modification on this key.
    ModRevision int64 `protobuf:"varint,3,opt,name=mod_revision,json=modRevision,proto3" json:"mod_revision,omitempty"`
    // version is the version of the key. A deletion resets
    // the version to zero and any modification of the key
    // increases its version.
    Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"`
    // value is the value held by the key, in bytes.
    Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
    // lease is the ID of the lease that attached to key.
    // When the attached lease expires, the key will be deleted.
    // If lease is 0, then no lease is attached to the key.
    Lease                int64    `protobuf:"varint,6,opt,name=lease,proto3" json:"lease,omitempty"`
}

可以看到 header 里有一个 Revision, KeyValue 里有三个:CreateRevision, ModRevisionVersion, 所以这几个都是什么意思?

另外我们在 watch etcd 时,WithRev这个 option 里面的 revision到底选哪一个?

for {
    rch := watcher.Watch(ctx, path, clientv3.WithRev(revision))
    for wresp := range rch {
        ......
    }
 }

验证案例

搭建 etcd 环境。

vi etcd.sh

#!/usr/bin/env bash
ETCD_NAME="etcd"
ETCD_VERSION="v3.3.1"
ETCD_PORT_CLIENT=2379
ETCD_PORT_NODE=2380
docker run -d \
  -p ${ETCD_PORT_CLIENT}:2379 \
  -p ${ETCD_PORT_NODE}:2380 \
  --name ${ETCD_NAME} quay.io/coreos/etcd:${ETCD_VERSION} \
  /usr/local/bin/etcd \
  --data-dir=/etcd-data --name node1 \
  --initial-advertise-peer-urls http://0.0.0.0:2380 --listen-peer-urls http://0.0.0.0:2380 \
  --advertise-client-urls http://0.0.0.0:2379 --listen-client-urls http://0.0.0.0:2379 \
  --initial-cluster node1=http://0.0.0.0:2380

启动 etcd。

chmod +x etcd.sh
./etcd.sh

验证各种版本的含义

过程

写入一组数据后 (key为/students/class1/zhangsan,value为 {age:11,gender:man}),

Revision, CreateRevision, ModRevision 均为 150,而 Version 为 1。

[root@192 ~]# etcdctl put "/students/class1/zhangsan" "{age:11,gender:man}"
OK
[root@192 ~]# etcdctl get "/students/class1/zhangsan" --write-out="fields"
"ClusterID" : 11588568905070377092
"MemberID" : 128088275939295631
"Revision" : 150
"RaftTerm" : 8
"Key" : "/students/class1/zhangsan"
"CreateRevision" : 150
"ModRevision" : 150
"Version" : 1
"Value" : "{age:11,gender:man}"
"Lease" : 0
"More" : false
"Count" : 1

修改该数据后,发现CreateRevision 不变,还是150,而 RevisonModRevision 自增为151,Version 自增为2。

[root@192 ~]# etcdctl put "/students/class1/zhangsan" "{age:11,gender:man}"
OK
[root@192 ~]# etcdctl get "/students/class1/zhangsan" --write-out="fields"
"ClusterID" : 11588568905070377092
"MemberID" : 128088275939295631
"Revision" : 151
"RaftTerm" : 8
"Key" : "/students/class1/zhangsan"
"CreateRevision" : 150
"ModRevision" : 151
"Version" : 2
"Value" : "{age:11,gender:man}"
"Lease" : 0
"More" : false
"Count" : 1

再次写入一组新的数据后 (key为/students/class1/lisi,value为 {age:13,gender:woman}),

发现集群的 Revision 自增为了152。

[root@192 ~]# etcdctl put "/students/class1/lisi" "{age:11,gender:woman}"
OK
[root@192 ~]# etcdctl get "/students/class1/lisi" --write-out="fields"
"ClusterID" : 11588568905070377092
"MemberID" : 128088275939295631
"Revision" : 152
"RaftTerm" : 8
"Key" : "/students/class1/lisi"
"CreateRevision" : 152
"ModRevision" : 152
"Version" : 1
"Value" : "{age:11,gender:woman}"
"Lease" : 0
"More" : false
"Count" : 1

删除zhangsan这组数据,发现集群的 Revison 自增为了153,说明删除操作也会使集群的 Revison 自增。

[root@192 ~]# etcdctl del "/students/class1/zhangsan" --write-out="fields"
"ClusterID" : 11588568905070377092
"MemberID" : 128088275939295631
"Revision" : 153
"RaftTerm" : 8
"Deleted" : 1

再次写入一条 zhangsan 数据,Revision, CreateRevision, ModRevision 均为 154,而 Version 为 1。

[root@192 ~]# etcdctl put "/students/class1/zhangsan" "{age:11,gender:man}"
OK
[root@192 ~]# etcdctl get "/students/class1/zhangsan" --write-out="fields"
"ClusterID" : 11588568905070377092
"MemberID" : 128088275939295631
"Revision" : 154
"RaftTerm" : 8
"Key" : "/students/class1/zhangsan"
"CreateRevision" : 154
"ModRevision" : 154
"Version" : 1
"Value" : "{age:11,gender:man}"
"Lease" : 0
"More" : false
"Count" : 1

总结

此时我们可以得出结论:

  1. Revision
    作用域为集群,逻辑时间戳,全局单调递增,任何 key 的增删改都会使其自增
  2. CreateRevision
    作用域为 key, 等于创建这个 key 时集群的 Revision, 直到删除前都保持不变
  3. ModRevision
    作用域为 key, 等于修改这个 key 时集群的 Revision, 只要这个 key 更新都会自增
  4. Version
    作用域为 key, 这个key刚创建时Version为1,之后每次更新都会自增

现在梳理清了各个版本的意义与概念,那么问题来了,watch 时选择哪一个呢?

watch 时应该选择哪个版本

watch 一个具体的 key

查看当前的各种版本的值,这里只测试 zhangsan 的情况,其Revison 为 162, CreateReVision 为156, ModRevison 为160,Version 为3。

[root@192 ~]# etcdctl get "/students/class1/zhangsan" --write-out="fields"
"ClusterID" : 11588568905070377092
"MemberID" : 128088275939295631
"Revision" : 162
"RaftTerm" : 8
"Key" : "/students/class1/zhangsan"
"CreateRevision" : 156
"ModRevision" : 160
"Version" : 3
"Value" : "{age:13,gender:man}"
"Lease" : 0
"More" : false
"Count" : 1
[root@192 ~]# etcdctl get "/students/class1/lisi" --write-out="fields"
"ClusterID" : 11588568905070377092
"MemberID" : 128088275939295631
"Revision" : 162
"RaftTerm" : 8
"Key" : "/students/class1/lisi"
"CreateRevision" : 152
"ModRevision" : 162
"Version" : 2
"Value" : "{age:13,gender:man}"
"Lease" : 0
"More" : false
"Count" : 1

分别 watch "/students/class1/zhangsan" 的不同版本测试一下。

首先从 CreateRevision (这里是156)开始 watch,可以看到结果中返回了3条 zhangsan 的记录。其实也就是其 Version 的值。从 CreateRevison 这个版本开始watch,可以看到其所有的更新记录。

[root@192 ~]# etcdctl watch "/students/class1/zhangsan" --rev=156
PUT
/students/class1/zhangsan
{age:11,gender:man}
PUT
/students/class1/zhangsan
{age:12,gender:man}
PUT
/students/class1/zhangsan
{age:13,gender:man}

ModRevision (这里是160)开始 watch,可以看到结果中返回了1条 zhangsan 的记录,即此key最近更新的那条记录。

[root@192 ~]# etcdctl watch "/students/class1/zhangsan" --rev=160
PUT
/students/class1/zhangsan
{age:13,gender:man}

watch 一个前缀

下面对 "/students/class1" 这个前缀进行 watch 测试。

Revision (这里是162)开始 watch,可以看到结果中返回了1条记录,即此集群中最近更新的那条记录。

[root@192 ~]# etcdctl watch "/students/class1/" --prefix --rev=162
PUT
/students/class1/lisi
{age:13,gender:man}

也就是说,如果从 Revision+1 开始 watch,就可以watch当前前缀下的所有key的后续变化。

总结

关于 watch 哪个版本:

  1. watch 某一个 key 时,想要从历史记录开始就用 CreateRevision,最新一条(这一条直接返回) 开始就用 ModRevision
  2. watch 某个前缀,就必须使用 Revision。如果要watch当前前缀后续的变化,则应该从当前集群的 Revision+1 版本开始watch。



本文参考:etcd 中让人头大的 version, revision, createRevision, modRevision - 墨天轮 (modb.pro)

posted @ 2022-04-17 17:12  拾月凄辰  阅读(2766)  评论(0编辑  收藏  举报