问题
etcd存储数据的过程中,在状态机里即MVCC模块中,kvIndex和boltdb存储了多个“version”,每个“version”的区别在哪里呢?
解决
实现
可以从代码里看到具体的实现。
当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
}
RangeResponse结构体中,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"`
}
ResponseHeader 中里有 Revision: revision is the key-value store revision when the request was applied。
revision存储的是k-v,每有一个请求,revision都会发生变化。
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"`
}
KeyValue 里有:
CreateRevision:create_revision is the revision of last creation on this key。创建key时的版本信息。
ModRevision:mod_revision is the revision of last modification on this key。key最新修改的版本信息。
Version:version is the version of the key. A deletion resets the version to zero and any modification of the key increases its version。key的版本信息,key删除版本重置为0,key修改,版本增加。
watch时的revision:
for {
rch := watcher.Watch(ctx, path, clientv3.WithRev(revision))
for wresp := range rch {
......
}
}
验证
首先查看当前revision
"revision": 25023542
etcdctl endpoint status -w json | jq
写入数据
etcdctl put /foo bar
etcdctl get /foo -w fields
可以看到key /foo : "Revision" : 25031899,"CreateRevision" : 25031837",ModRevision" : 25031837,"Version" : 1
此时CreateRevision=ModRevision。
修改数据
etcdctl put /foo barr
etcdctl get /foo -w fields
可以看到key /foo : "Revision" : 25033644,"CreateRevision" : 25031837,"ModRevision" : 25033596,"Version" : 2
只有CreateRevision没有变,其他都发生了变化,version自增为2。
添加其他数据
etcdctl put /bar foo
etcdctl get /bar -w fields
可以看到key /bar : "Revision" : 25035136,"CreateRevision" : 25035081,"ModRevision" : 25035081,"Version" : 1
可以看到key /foo : "Revision" : 25035977,"CreateRevision" : 25031837,"ModRevision" : 25033596,"Version" : 2
添加其他数据对原有key /far没有影响,只有Revision发生了变化。
删除数据
etcdctl del /foo
etcdctl put /foo bar
etcdctl get /foo -w fields
可以看到key /foo : "Revision" : 25037033,"CreateRevision" : 25036999,"ModRevision" : 25036999,"Version" : 1
删除数据后,version重置为1,CreateRevision和ModRevision都为新值且相等,Revision一直在变化。
综上,可以得出结论:
- Version:作用域为 key,某一个 key 的修改次数(从创建到删除),与其他 Revision 无关。
- Revision:作用域为集群,逻辑时间戳,全局单调递增,任何 key 修改都会使其自增。
- ModRevision:作用域为 key,等于修改这个 key 时的 Revision,只要这个 key 更新都会改变。
- CreateRevision:作用域为 key,等于创建这个 key 时的 Revision,直到删除前都保持不变。
watch
查看当前版本
当前key /foo : "Revision" : 25037033,"CreateRevision" : 25036999,"ModRevision" : 25036999,"Version" : 1。
开始watch
etcdctl watch /foo -w fields --rev=25036999
从当前版本25036999开始watch,报错“watch was canceled (etcdserver: mvcc: required revision has been compacted”,这是因为这个版本已经被压缩了,需要从最近的revision开始watch。
etcdctl get /foo -w fields
etcdctl watch /foo -w fields --rev=25041798
修改,删除,添加key /foo,添加key /fooo。
etcdctl put /foo barr
etcdctl del /foo
etcdctl put /fooo barr
etcdctl put /foo bar
watch只有key /foo的三条输出,并没有key /fooo。
综上可以得出结论:
- watch 某一个 key 时:想要从历史记录开始就用CreateRevision,最新数据就用ModRevision。但要注意revision是否被compacted。
- watch 某个前缀:必须使用 Revision。