LevelDb之manifest檔案

weixin_34146805發表於2017-08-21

從這篇文章開始想寫寫leveldb的小知識。先了解manifest檔案,因為這個檔案關係到例項的讀取。至於這個檔案的內部結構以及如何來的,等會再聊。

if instances[Name], err = leveldb.OpenFile(Dir, instance); err != nil {
        return errors.New(fmt.Sprintf(`leveldbs: err while open %s : %s`, Dir, err.Error()))
}

這段程式碼在manifest檔案損壞的情況下會出現報錯,報錯內容:

ERROR leveldb: manifest corrupted: missing [file=MANIFEST-*******]

報錯原因:leveldb在儲存過程中因為一些其他原因導致程式突然的down掉可能導致manifest檔案損害或者丟失。這樣就只能手動repair下了,打不開一個instance 就沒有辦法執行get命令。repair的業務方程式碼可以這樣寫,這裡以Golang程式碼來說明:

if instances[cfgIns.Name], err = leveldb.OpenFile(Dir, instance); err != nil {
    if instances[cfgIns.Name], err = leveldb.RecoverFile(Dir, instance); err != nil {
        return errors.New(fmt.Sprintf(`err while recoverfile%s : %s`, Dir, err.Error()))
    }
}

RecoverFile 這個API函式的原始碼是:

func RecoverFile(path string, o *opt.Options) (db *DB, err error) {
    stor, err := storage.OpenFile(path)
    if err != nil {
        return
    }
    db, err = Recover(stor, o)
    if err != nil {
        stor.Close()
    } else {
        db.closer = stor
    }
    return
}

manifest檔案修復還是蠻簡單的,說明Google的大神早就想到了這些東西。通過上面的例子先感受下manifest檔案的存在,下面我們學習下manifest檔案結構和檔案的由來。檔案結構如圖:


6138279-72bad2febf65f6ff.png
manifest.png

再配合Golang中定義的struct看看:

type session struct {
    stNextFileNum    uint64 // current unused file number
    stJournalNum     uint64 // current journal file number; need external synchronization
    stPrevJournalNum uint64 // prev journal file number; no longer used; for compatibility with older version of leveldb
    stSeqNum         uint64 // last mem compacted seq; need external synchronization
    stTempFileNum    uint64

    stor     storage.Storage
    storLock util.Releaser
    o        *cachedOptions
    icmp     *iComparer
    tops     *tOps

    manifest       *journal.Writer
    manifestWriter storage.Writer
    manifestFile   storage.File

    stCompPtrs []iKey   // compaction pointers; need external synchronization
    stVersion  *version // current version
    vmu        sync.Mutex
}

manifest檔案存放versionset(版本集合資訊)。versionset是一個連結串列,每個節點就是一個versionversion中主要包含有.sstable檔案大小,檔案編號,有序值的最大值和最小值等等資訊。

type tSet struct {
    level int
    table *tFile
}
type version struct {
    s *session
    tables []tFiles
    cLevel int
    cScore float64
    cSeek unsafe.Pointer
    ref int
    next *version
}
type tFile struct {
    file       storage.File
    seekLeft   int32
    size       uint64
    imin, imax iKey
}

好吧,manifest檔案裡面存放的東西大致上就這些了,下面聊聊manifest檔案的建立和修改過程。這個問題還是有點難度的,這裡還是以Golanggithub.com/syndtr/goleveldb/leveldb這個庫為例來說明。newManifest是建立一個manifest, encode編碼後寫入檔案,最後將manifest檔案的名稱寫入到current檔案中,current只記錄當前的manifest檔案的檔名稱: _, err = fmt.Fprintln(w, f2.name())

func (s *session) newManifest(rec *sessionRecord, v *version) (err error) {
    num := s.allocFileNum()
    file := s.stor.GetFile(num, storage.TypeManifest)
    writer, err := file.Create()
    if err != nil {
        return
    }
    jw := journal.NewWriter(writer)

    if v == nil {
        v = s.version()
        defer v.release()
    }
    if rec == nil {
        rec = &sessionRecord{}
    }
    s.fillRecord(rec, true)
    v.fillRecord(rec)

    defer func() {
        if err == nil {
            s.recordCommited(rec)
            if s.manifest != nil {
                s.manifest.Close()
            }
            if s.manifestWriter != nil {
                s.manifestWriter.Close()
            }
            if s.manifestFile != nil {
                s.manifestFile.Remove()
            }
            s.manifestFile = file
            s.manifestWriter = writer
            s.manifest = jw
        } else {
            writer.Close()
            file.Remove()
            s.reuseFileNum(num)
        }
    }()

    w, err := jw.Next()
    if err != nil {
        return
    }
    err = rec.encode(w)
    if err != nil {
        return
    }
    err = jw.Flush()
    if err != nil {
        return
    }
    err = s.stor.SetManifest(file)
    return
}

一邊學習,一邊記錄,end ~

相關文章