etcd mysql 多层_etcd源码阅读与分析(三):wal

etcd源码阅读与分析(三):wal

今天来看看WAL(Write-Ahead Logging)。这是数据库中保证数据持久化的常用技术,即每次真正操作数据之前,先往磁盘上追加一条日志,由于日志

是追加的,也就是顺序写,而不是随机写,所以写入性能还是很高的。这样做的目的是,如果在写入磁盘之前发生崩溃,那么数据肯定是没有写入

的,如果在写入后发生崩溃,那么还是可以从WAL里恢复出来。

首先看一下 wal 里有什么:

$ tree

.

├── decoder.go

├── doc.go

├── encoder.go

├── file_pipeline.go

├── file_pipeline_test.go

├── metrics.go

├── record_test.go

├── repair.go

├── repair_test.go

├── util.go

├── wal.go

├── wal_bench_test.go

├── wal_test.go

└── walpb

├── record.go

├── record.pb.go

└── record.proto

1 directory, 16 files

我们先阅读 doc.go,可以知道这些东西:

WAL这个抽象的结构体是由一堆的文件组成的

每个WAL文件的头部有一部分数据,是metadata

使用 w.Save 保存数据

使用完成之后,使用 w.Close 关闭

WAL中的每一条记录,都有一个循环冗余校验码(CRC)

WAL是只能打开来用于读,或者写,但是不能既读又写

我们看看 Save 的实现:

func (w *WAL) Save(st raftpb.HardState, ents []raftpb.Entry) error {

w.mu.Lock()

defer w.mu.Unlock()

// short cut, do not call sync

if raft.IsEmptyHardState(st) && len(ents) == 0 {

return nil

}

mustSync := raft.MustSync(st, w.state, len(ents))

// TODO(xiangli): no more reference operator

for i := range ents {

if err := w.saveEntry(&ents[i]); err != nil {

return err

}

}

if err := w.saveState(&st); err != nil {

return err

}

curOff, err := w.tail().Seek(0, io.SeekCurrent)

if err != nil {

return err

}

if curOff < SegmentSizeBytes {

if mustSync {

return w.sync()

}

return nil

}

return w.cut()

}

可以看出来,Save 做的事情,就是写入一条记录,然后调用 w.sync,而 w.sync 做的事情就是:

func (w *WAL) sync() error {

if w.encoder != nil {

if err := w.encoder.flush(); err != nil {

return err

}

}

start := time.Now()

err := fileutil.Fdatasync(w.tail().File)

took := time.Since(start)

if took > warnSyncDuration {

if w.lg != nil {

w.lg.Warn(

"slow fdatasync",

zap.Duration("took", took),

zap.Duration("expected-duration", warnSyncDuration),

)

} else {

plog.Warningf("sync duration of %v, expected less than %v", took, warnSyncDuration)

}

}

walFsyncSec.Observe(took.Seconds())

return err

调用了 fileutil.Fdatasync,而 fileutil.Fdatasync 就是调用了 fsync 这个系统调用保证数据会被写到磁盘。

而快照也是类似的,写入一条记录,然后同步。

func (w *WAL) SaveSnapshot(e walpb.Snapshot) error {

b := pbutil.MustMarshal(&e)

w.mu.Lock()

defer w.mu.Unlock()

rec := &walpb.Record{Type: snapshotType, Data: b}

if err := w.encoder.encode(rec); err != nil {

return err

}

// update enti only when snapshot is ahead of last index

if w.enti < e.Index {

w.enti = e.Index

}

return w.sync()

}

WAL更多的是对多个WAL文件进行管理,WAL文件的命名规则是 $seq-$index.wal。第一个文件会是 0000000000000000-0000000000000000.wal,

此后,如果文件大小到了64M,就进行一次cut,比如,第一次cut的时候,raft的index是20,那么文件名就会变成 0000000000000001-0000000000000021.wal。

WAL就看到这。

etcd源码阅读与分析系列文章

关注公众号,获得及时更新

更多文章

加载评论

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值