共计 3917 个字符,预计需要花费 10 分钟才能阅读完成。
这篇文章主要介绍“wal 怎么实现日志读写”,在日常操作中,相信很多人在 wal 怎么实现日志读写问题上存在疑惑,丸趣 TV 小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”wal 怎么实现日志读写”的疑惑有所帮助!接下来,请跟着丸趣 TV 小编一起来学习吧!
etcd raft 介绍
etcd raft 是目前使用最广泛的 raft 库,etcd raft 在 etcd, Kubernetes, Docker Swarm, Cloud Foundry Diego, CockroachDB, TiDB, Project Calico, Flannel 等分布式系统中都有应用,在生成环境得到了验证。传统 raft 库的实现都是单体设计 (集成了存储层、消息序列化、网络层等), etcd raft 继承了简约的设计理念,只实现了最核心的 raft 算法, 这样更加的灵活。etcd 将网络、日志存储、快照等功能分开,通过独立的模块实现,用户可以在需要时调用。etcd 自身实现了自己的一套 raft 配套库:etcd-wal(用于存储日志),snap(用于存储快照),MemoryStorage(用于存储当前日志、快照、状态等信息以供 raft 核心程序使用)。
etcd wal 介绍
WAL 是 write ahead log 的缩写,etcd 使用 wal 模块来完成 raft 日志的持久化存储,etcd 对 wal 的所有实现都放在 wal 目录中。
wal 数据结构
type WAL struct {
lg *zap.Logger
dir string // the living directory of the underlay files
// dirFile is a fd for the wal directory for syncing on Rename
dirFile *os.File
metadata []byte // metadata recorded at the head of each WAL
state raftpb.HardState // hardstate recorded at the head of WAL
start walpb.Snapshot // snapshot to start reading
decoder *decoder // decoder to decode records
readClose func() error // closer for decode reader
mu sync.Mutex
enti uint64 // index of the last entry saved to the wal
encoder *encoder // encoder to encode records
locks []*fileutil.LockedFile // the locked files the WAL holds (the name is increasing)
fp *filePipeline
}
上述为 wal 的数据结构,通过用 wal.go 文件中的 Create() 方法来获取 wal 的实例。wal 首先会创建一个临时目录并初始化相关变量,并创建和初始化第一个 wal 文件,等所有的操作都初始化完成后直接更改临时目录的名字完成 wal 实例的初始化。
文件组织
wal 的所有日志放在一个指定目录下,日志的文件名以 .wal 作为结尾,格式为 -.wal,seq 和 index 的格式都为 %016x, 例如:0000000000000001-0000000000000001.wal。index 代表这个文件中第一条 raft 日志的 index,seq 是这个文件的序列号 (依次递增)。
每个文件的大小默认为 64M,当文件大于 64M 时,wal 会自动生成新的日志文件用于存储日志。每个日志文件都会使用 flock 锁定文件,参数为 LOCK_EX,这是一把独有锁,同一时间只能有一个进程可以操作这个日志文件,所以当 wal 占有这个文件时,通过进程是无法删除这个文件的。
日志逻辑组织
wal 日志可以存储多种类型的数据,具体如下。
crcType 每个新的日志文件的第一条记录都会是 crcType 类型的记录,crcType 也只会在每个日志文件的开始时写入,用于记录上一个文件最后的 crc 是多少
metadataType 每个新的日志文件中 metadataType 紧跟在 crcType 记录后面,每个日志文件只会出现一次
stateType 这种日志类型会在两种情况下加入:
自动切分日志文件时,新的日志文件中,紧跟在 metadataType 后面会存入一条 stateType 的日志
当 raft 核心程序 ready 中返回 hard state 时也会存储该类型的日志
snapshotType wal 日志中只会存储 snapshot 的 term 和 index,具体的数据存储在专门的 snapshot 中,每次存储快照都会在 wal 日志中存储一个 wal 的快照。当存储快照时,会将快照之前 index 的日志文件都释放掉。wal 中存储的 snapshot 主要用于检查快照是否正确。
日志读写
wal 通过封装的 encoder 和 decoder 模块来实现日志读写。
写日志
encoder 模块把会增量的计算 crc 和数据一起写入到 wal 文件中。下面为 encoder 数据结构
type encoder struct {
mu sync.Mutex
bw *ioutil.PageWriter
crc hash.Hash42
buf []byte // 缓存空间,默认为 1M,降低数据分配的压力
uint64buf []byte
}
wal 通过 encoder 实现写日志,在这个模块中会完成 crc 的计算。wal 为了更好的管理数据,日志中的每条数据都会以 8 字节对齐 (wal 会自动对齐字节)。日志写入的流程如下。
图中的 crc 是增量计算,以之前的所有日志数据为增量基础。wal 只关注写入日志,不会校验日志的 index 是否重复,但是如果重启这个 Node 的话,系统会自动过滤掉中间混杂的日志。
日志切分
wal 实现了日志自动切分,当日志数据大于默认的 64M 时就会生成新的文件写入日志,日志的切分通过 wal.go 文件中的 cut 方法来实现。cut 方法只会在调用 wal 中的 Save 方法才会触发调用,新文件的第一条记录就是上一个 wal 文件最后的 crc。
日志 compact
wal 没有实现日志的自动 compact,系统只提供了 MemoryStorage 的日志 compact 方法(需要用户主动调用)。
file_pipeline 模块
wal 新建新的文件时都是先新建一个 tmp 文件,当所有操作都完成后再重命名这个文件。wal 使用 file_pipeline 这个模块在后台启动一个协程时刻准备一个临时文件以供使用,从而避免临时创建文件的开销。
etcd snap 介绍
etcd raft 自带了 go.etcd.io/etcd/etcdserver/api/snap 模块来实现快照的存储。
文件组织
在 snap 模块中一个快照用一个后缀名为.snap 的文件存储,文件格式为 -.snap, term 和 index 分别代表快照日志所处的 term 和 index。每个快照具体存储结构如下图:
详细介绍
系统可以有多个快照,snap 模块使用 Snapshotter 结构统一管理快照。
type Snapshotter struct {
lg *zap.Logger
dir string
}
上面 snapshotter 的结构代码,snapshotter 主要用于存储和读取快照。
快照具体存储的内容需要用户来指定,例如在 raft 的官方例子中直接将当时的 kv 数据 Marshal 之后存储到快照中。
func (s *kvstore) getSnapshot() ([]byte, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return json.Marshal(s.kvStore)
}
何时打快照
在 etcd-raft 中用户可以选择何时打快照,在 etcd 的官方案例中打快照的方法是 maybeTriggerSnapshot(),这个方法在节点的 Ready() 方法返回时调用,当前提交的 index 值与上一次大快照的 index 值大于 10000 时会打新的快照。
etcd MemoryStorage 介绍
MemoryStorage 用于存储 raft 节点临时的数据,包括 entrys、快照等。用户将数据存储到 memoryStorage 中,raft 节点也会使用这些数据。包括 entrys 的传递、快照的发送等都是从 memoryStorage 中发送。
// MemoryStorage implements the Storage interface backed by an
// in-memory array.
type MemoryStorage struct {
// Protects access to all fields. Most methods of MemoryStorage are
// run on the raft goroutine, but Append() is run on an application
// goroutine.
sync.Mutex
hardState pb.HardState
snapshot pb.Snapshot
// ents[i] has raft log position i+snapshot.Metadata.Index
ents []pb.Entry
}
memoryStorage 会存储最新的 entrys(包括哪些没有 commit)、快照和状态,用户在收到其它节点发送的相关数据时需要将数据存储到 memorystorage 中。
到此,关于“wal 怎么实现日志读写”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注丸趣 TV 网站,丸趣 TV 小编会继续努力为大家带来更多实用的文章!