【原创】HPB之go leveldb数据库的version介绍


#1

之前分享了leveldb的相关操作,其中compaction操作会导致磁盘存储文件的增加与删除,那在查询数据的时候是否会存在被查文件被删掉的可能呢?答案是不会的,本文就来看看leveldb自身的版本控制。

我们从版本的结构,生成,使用,和销毁四个部分来说明。

版本的结构:结构定义如下,我们在查询中重点关注的是 levels []tFiles属性。存储了当前版本所有的文件,以及session属性,session在这里也有一个很重要的属性是fileRef,fileRef是文件的引用次数。

type version struct {

​    s *session

​    levels []tFiles

​    // Level that should be compacted next and its compaction score.

​    // Score < 1 means compaction is not strictly needed. These fields

​    // are initialized by computeCompaction()

​    cLevel int

​    cScore float64

​    cSeek unsafe.Pointer

​    closing  bool

​    ref      int

​    released bool

}

版本的管理主要是围绕着版本version,文件tfiles,文件f引用fileRef来实现的。

版本的生成:之前在将compaction的时候提到过一个spawn函数,具体的实现可以去查看源码。生成新版本的过程简单描述如下:1、获取到当前版本stversion,2、将当前版本stversion中的文件删除掉合并的文件,添加新的文件 3、重新排序文件(level0层的文件根据文件id排序,大于0层的文件根据key的大小排序),排序的目的是为了提高查询效率,4、然后将新生成的version设置为stversion,代码如下:

func (s *session) setVersion(v *version) {

​    s.vmu.Lock()

​    defer s.vmu.Unlock()

​    v.incref()

​    if s.stVersion != nil {

​        // Release current version.

​        s.stVersion.releaseNB()

​    }

​    s.stVersion = v

}

根据以上代码可以看出,在设置新版本的时候,会有一个incref(),该函数将新版本中所有文件引用计数加1,然后释放老的版本,将新版本v的设置为当前版本stversion。所以,stversion中存的一直都是最新的版本。

版本的使用:在Get操作的时候,需要获取到版本信息,可以认为是一个快照版本。前文介绍get操作时,查询内存找不到后,需要查询磁盘文件有一个获取version的动作,如下

v := db.s.version()//version()实现如下
func (s *session) version() *version {
	s.vmu.Lock()
	defer s.vmu.Unlock()
	s.stVersion.incref()
	return s.stVersion
}
value, cSched, err := v.get(auxt, ikey, ro, false)
v.release()

所以,每次查询都获取的是当前最新的版本。注意:当前最新的版本不一定一直是最新的版本,比如v.get还没完成的时候,compaction生成了新的版本的时候。

因此,在实际应用中,可能存在多个版本在使用。既然有多个版本在使用,那新版本生成的时候,需要合并的文件就不能立即从磁盘上删除了。

版本的销毁:每一个版本版本使用完成,比如上述get完成后,compaction新生成版本后,都会进行版本的释放回收,代码如下:

func (v *version) releaseNB() {

​    v.ref--

​    if v.ref > 0 {

​        return

​    } else if v.ref < 0 {

​        panic("negative version ref")

​    }

​    for _, tt := range v.levels {

​        for _, t := range tt {

​            if v.s.addFileRef(t.fd, -1) == 0 {

​                v.s.tops.remove(t)

​            }

​        }

​    }

​    v.released = true

}

以上代码可知,只要文件的引用次数大于0,文件就不会被删除。

举个小例子:

现有版本1,对应a , b ,c 三个文件,三个文件的引用计数均为1

get操作,v = 版本1,此时 a ,b, c 三个文件的引用计数均为2

compaction操作,生成新的版本2: c,d,e 三个文件,其中c的引用是3次,d,e的引用次数是1次。 因为有新版本,所以需要回收版本1,回收之后,a,b的引用次数变为1,c的引用次数变为2,d e的应用次数变为1.

get操作完成后,释放版本v, 此时 ab的引用次数变为0,删掉ab文件,c的引用次数变为1

结束。