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
,可以知道這些東西:
w.Save w.Close
我們看看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就看到這。
- WAL:ofollow,noindex" target="_blank">https://en.wikipedia.org/wiki/Write-ahead_logging