温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

wal怎么实现日志读写

发布时间:2021-12-18 15:36:08 来源:亿速云 阅读:258 作者:iii 栏目:云计算

这篇文章主要介绍“wal怎么实现日志读写”,在日常操作中,相信很多人在wal怎么实现日志读写问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”wal怎么实现日志读写”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

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核心程序使用)。

wal怎么实现日志读写

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日志可以存储多种类型的数据,具体如下。

wal怎么实现日志读写

wal怎么实现日志读写

  • crcType 每个新的日志文件的第一条记录都会是crcType类型的记录,crcType也只会在每个日志文件的开始时写入,用于记录上一个文件最后的crc是多少

  • metadataType 每个新的日志文件中metadataType紧跟在crcType记录后面,每个日志文件只会出现一次

  • stateType 这种日志类型会在两种情况下加入:

  1. 自动切分日志文件时,新的日志文件中,紧跟在metadataType后面会存入一条stateType的日志

  2. 当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会自动对齐字节)。 日志写入的流程如下。

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。 每个快照具体存储结构如下图:

wal怎么实现日志读写

详细介绍

系统可以有多个快照,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怎么实现日志读写”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    wal
    AI