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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM