本文简要总结了etcd中的snap与wal的存储格式。snap是快照(snapshot), wal是Write Ahead Log。本文是基于etcd 3.4.15的代码进行分析的。
snap存储格式
Snap文件位于${etcd}/member/snap目录下,是后缀为.snap的文件。snap的格式主要是定义在snap.proto文件中:
https://github.com/etcd-io/etcd/blob/v3.4.15/etcdserver/api/snap/snappb/snap.proto
复制
每个.snap文件其实就是存储的下面这个结构体的内容:
type Snapshot struct {
Crc uint32 `protobuf:"varint,1,opt,name=crc" json:"crc"`
Data []byte `protobuf:"bytes,2,opt,name=data" json:"data,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
复制
对应的源代码:
https://github.com/etcd-io/etcd/blob/v3.4.15/etcdserver/api/snap/snappb/snap.pb.go#L38-L42
复制
用一个简单的图来表示,就是下面这个样子。
Crc是内容的crc校验和,Data是snap的具体内容。其中Data的存储的其实又是下面这个结构体:
type Snapshot struct {
Data []byte `protobuf:"bytes,1,opt,name=data" json:"data,omitempty"`
Metadata SnapshotMetadata `protobuf:"bytes,2,opt,name=metadata" json:"metadata"`
XXX_unrecognized []byte `json:"-"`
}
复制
源代码位置:
https://github.com/etcd-io/etcd/blob/v3.4.15/raft/raftpb/raft.pb.go#L284-L288
复制
用一个简单的图来表示,就是下面这样:
其中Data存储的又是下面这个结构体,
type store struct {
Root *node
WatcherHub *watcherHub
CurrentIndex uint64
Stats *Stats
CurrentVersion int
ttlKeyHeap *ttlKeyHeap need to recovery manually
worldLock sync.RWMutex stop the world lock
clock clockwork.Clock
readonlySet types.Set
}
复制
源代码位置:
https://github.com/etcd-io/etcd/blob/v3.4.15/etcdserver/api/v2store/store.go#L74-L84
复制
其实存储的就是结构体store的json序列化之后的字符串而已。除了序列化和反序列化结构体store是etcd自己实现的之外,其它所有的存储/解析都是借助于protobuf自动生成的代码完成的。
其实snap存储的核心内容就是这个结构体store。要讲清楚这个结构体,不是一两句话的事,回头再单独写文章说。
如果你打开一个.snap文件,你会发现除了开头的几个字节是不可见字符之外,后面的都是可见字符,因为就是直接存储的json格式序列化之后字符串。例如:
用一个完整的图来表示snap的存储结构:
下面的代码片段可以理解snap的存储结构:
https://github.com/etcd-io/etcd/blob/v3.4.15/etcdserver/api/snap/snapshotter.go#L189-L230
https://github.com/etcd-io/etcd/blob/v3.4.15/etcdserver/api/v2store/store.go#L767-L780
复制
wal存储格式
wal文件位于{etcd}/member/wal目录下,是后缀为.wal的文件。每个wal文件是由一条条的record组成的,用一个简单的图表示:
每条record的格式如下:
从其中length字段可以分别解析出后面的record和pad的实际长度。其中低56位存储record bytes的具体长度。高8位可以解析出后面的pad bytes的具体长度。具体见下图描述:
对应的源码:
https://github.com/etcd-io/etcd/blob/v3.4.15/wal/decoder.go#L122-L131
复制
pad bytes是填充字节,忽略即可。而record bytes具体又是存储的下面这个结构体:
type Record struct {
Type int64 `protobuf:"varint,1,opt,name=type" json:"type"`
Crc uint32 `protobuf:"varint,2,opt,name=crc" json:"crc"`
Data []byte `protobuf:"bytes,3,opt,name=data" json:"data,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
复制
源码位置:
https://github.com/etcd-io/etcd/blob/v3.4.15/wal/walpb/record.pb.go#L39-L44
复制
用一个简单的图表示如下:
Type具体有下面几种类型:
metadataType int64 = iota + 1
entryType
stateType
crcType
snapshotType
复制
源码:
https://github.com/etcd-io/etcd/blob/v3.4.15/wal/wal.go#L39-L43
复制
根据不同的Type,实际存储的数据格式各不相同,这里就不一一列举各种具体的格式了。直接给出wal文件的总体存储结构:
下面的代码片段可以理解wal的存储结构:
https://github.com/etcd-io/etcd/blob/v3.4.15/wal/decoder.go#L67-L120
https://github.com/etcd-io/etcd/blob/v3.4.15/wal/wal.go#L448-L493
复制
另外,wal文件名的格式必须为"%016x-%016x.wal",参考代码:
https://github.com/etcd-io/etcd/blob/v3.4.15/wal/util.go#L118
复制
小结
本文只是简单总结了snap和wal文件的存储格式,并没有深入讲述每个字段的具体的含义,比如并没有讲述snap中结构体store。
etcd在启动的时候,只需要加载最后(最新)一个snap文件,并加载snap之后的所有的wal文件,这里主要是通过index来判断。
本文的源码都是基于3.4.15分析的,最新的master上的代码结构做了一定的调整,例如将目录etcdserver放到目录sever下面去了。但总体的代码逻辑是相同的。
另外,在${etcd}/member/snap目录下,还有一个db文件,这个是按BoltDB格式存储的。具体可参考我去年总结的几篇文章:
--END--