LevelDB 源码阅读笔记·初探 [0x01]
构建
首先 clone 代码:
1 | |
由于 leveldb 使用了 googletest 和 benchmark,所以 clone 的时候要加上 --recurse-submodules
接下来我直接用 vscode 打开了 leveldb 目录,然后选定工具链, CMake 插件自动地完成了配置。
不得不说现在的 vscode 用起来非常顺手,构建、测试一步到位。
代码阅读方面我使用了 Sourcetrail,这是个很好的工具,可惜停更了,目前 Github 仓库已经归档了。
在读代码的时候还是需要对照着文档(leveldb/doc/index.md),这样便于理解函数的作用。
开始
首先来看一下核心的 leveldb/include/leveldb/db.h
开头 inlucde 了一些头文件
1 | |
这里 export.h 包含一些宏定义,这些都是常规操作,但作为初学者,我还是将这些记录下来
1 | |
这样做防止了 LEVELDB_EXPORT 宏重复定义,确保函数和类成员只导出一次。
iterator.h 定义了迭代器,用于访问 Table 和 DB 对象。大概作用应该和 STL 里的迭代器有相似性。Iterator 中的 const 方法是线程安全的。
我们先不去管这个具体是怎么实现的,先放一放。
options.h 看名字就知道里面是一些选项,我们先不去管它。
回到 db.h,这里定义了几个类和结构体。
Snapshot: 大概是用于保存快照的,不过现在也不知道如何去用。
Range: 看不懂,跳过。
DB:这个类是 leveldb 库的核心,它定义了 DB 对象及一系列接口。这些会在下一节进行介绍。
最后是两个独立的函数 DestroyDB 和 RepairDB,分别用于销毁 DB 对象和抢救打不开的 DB。
这里 DestroyDB 没有设计成 DB 类的方法,我想,这是因为这个函数比较危险,在使用时需要小心,如果设计成 DB 类的方法,可能一不小心就删库了。而作为一个独立的函数,增加了调用时的复杂度和明显度,这也是对用户的提醒与保护。
注意因为兼容性的原因,就算是 DestroyDB 方法没有成功,也可能会返回 Status::OK(),所以需要额外检查下错误信息。
DB 类的方法
1 | |
首先是 Open 方法,用于打开数据库对象。值得注意的是,这里用到了双重指针,会在 dbptr 指针指向的地方创建一个指针,指向实际的数据库对象。我暂时还不知道这种设计有什么作用,先放一放,等之后了解全貌,这么设计的理由大概就会浮出水面。
接下来是 DB 类的构造函数和析构函数
1 | |
DB() = default 是设置了一个默认的构造函数,这样符合 C++11 的规范。
DB 类在使用的时候,应当是独占访问的,不应该随意复制,所以将拷贝构造函数、拷贝赋值构造函数和都删除了。
1 | |
然后是经典的增删改查,我准备留到具体实现的时候来介绍。
1 | |
这个方法用于生成迭代器以访问 DB 对象,用起来和 STL 类似之处。这里贴出文档里的示例以供理解。
1 | |
迭代器要及时 delete,按照注释里的说法,迭代器要在 db 对象 delete 之前进行 delete。像这段示例,在迭代器用完之后就 delete 了。
1 | |
两个跟 Snapshot 有关的函数,先放一放。
1 | |
这个方法用于获取数据库的属性,比如版本号,文件大小等。
1 | |
这个方法用于获取数据库的文件大小,可能不包含最近写入的内容。
1 | |
最后一个方法,用于压缩数据库,也留到具体实现的时候介绍。
关于纯虚函数
在阅读代码的时候,可以发现,db.h 中大多数函数都是纯虚函数,DB 类实际上是一个接口,具体的实现都在 DBImpl 类当中。
这是用到了多态性,在代码中使用的时候,使用 DB 的接口就可以,只需要
1 | |
就可以得到一个 DB 对象,而不用关心 DB 对象的具体实现。
这里 leveldb 用到了工厂模式,隐藏了创建 DB 对象的过程。
这种方式有不少好处,一个是使用起来简单,不需要关注具体实现;还可以便于后期扩展,甚至说你哪怕底层根本不是 leveldb,代码里反正认的是这个接口,替换掉也不会影响已有代码。
小结
本节中我们了解了 db.h 中的一些接口,对 leveldb 的功能有了一个大致的了解。
接下来,我们将开始看 leveldb 的具体实现,了解它的具体实现。难度会比较大,估计要花上很长时间。