代码获取
LevelDB 的仓库地址为 google/leveldb(github.com)
下载代码的时候最好加上 --recurse-submodules 参数,直接把子模块的内容一并下载下来:
git clone --recurse-submodules https://github.com/google/leveldb.git
编译会依赖子模块里的代码,缺少则无法通过编译。
代码结构
leveldb 代码结构如下,最重要的是 db、table、util 三个文件夹,里面包含了 leveldb 最核心的代码实现,还有 include,包含了使用 leveldb 需要引用的头文件:
编译使用
编译静态库
- 在源代码所在的文件夹(···/leveldb)里执行下面的命令,创建 build 文件夹 && 进入 build 文件夹:
mkdir -p build && cd build
- 用 cmake 命令进行编译:
cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build . // release 版本 cmake -DCMAKE_BUILD_TYPE=Debug .. && cmake --build . // debug 版本
- 编译完成后,可以在 build 目录下看到编译出的静态库 libleveldb.a
demo 代码
#include <iostream>
#include <string>
#include <leveldb/db.h>
using namespace std;
int main() {
leveldb::DB* db;
leveldb::Options opt;
leveldb::Status status;
// 打开 /workspace/leveldb/test/testdb 目录里的数据库,若不存在则新建
opt.create_if_missing = true;
status = leveldb::DB::Open(opt, "/workspace/leveldb/test/testdb",
&db);
if (!status.ok()) {
cout << "Failed to open db! " << status.ToString() << endl;
return 0;
}
// 插入键值对 "test_key_A" - "test_value_a"
string strKey = "test_key_A";
string strVal = "test_value_a";
status = db->Put(leveldb::WriteOptions(), strKey, strVal);
if (!status.ok()) {
cout << "Failed to exec put opt! " << status.ToString() << endl;
delete db;
return 0;
}
// 根据键 "test_key_A" 查询存储的值 "test_value_a"
string strSavedVal;
status = db->Get(leveldb::ReadOptions(), strKey, &strSavedVal);
if (!status.ok()) {
cout << "Failed to find val for key " << strKey << "! "
<< status.ToString() << endl;
delete db;
return 0;
}
cout << "Got saved val[" << strSavedVal << "] for key " << strKey
<< endl;
// 删除键为 "test_key_A" 的键值对
status = db->Delete(leveldb::WriteOptions(), strKey);
if (!status.ok()) {
cout << "Failed to del key-val for key " << strKey << "! "
<< status.ToString() << endl;
delete db;
return 0;
}
// 根据键 "test_key_A" 查询存储的值 "test_value_a"
status = db->Get(leveldb::ReadOptions(), strKey, &strSavedVal);
if (!status.ok()) {
cout << "Failed to find val for key " << strKey << "! "
<< status.ToString() << endl;
delete db;
return 0;
}
cout << "Got saved val[" << strSavedVal << "] for key " << strKey
<< endl;
delete db;
return 0;
}
demo 编译
- 把编译出的静态库 libleveldb.a 复制到 /usr/local/lib/
- 把 leveldb 源码中 include 文件夹里相关的头文件复制到 /usr/local/include/
- 保存 demo 代码为 demo.cpp,编译 demo:
g++ demo.cpp -o demo -lleveldb -lpthread -std=c++11
demo 执行结果
用 Put 插入键值对 "test_key_A" - "test_value_a" 后,可以用 Get 接口,根据键 "test_key_A" 查询到存储的值 "test_value_a"。
用 Delete 删除键为 "test_key_A" 的键值对后,再用 Get 接口,根据键 "test_key_A" 查找键值对会返回失败,错误为 NotFound。
PS:如果是在 wsl 中使用 leveldb,需要升级到 wsl 2,在 wsl 1 中无法启动 leveldb。
数据库文件
按照 demo 实现,会在 /workspace/leveldb/test/testdb 目录下创建一个新的数据库:
数据库文件在上一篇文章里 【存储引擎】LevelDB 概述已经有部分提及。目录里 .ldb 后缀的文件就是上一篇文章里所说的持久化存储在硬盘中的 ssttable。.log 后缀的文件里存储的就是 WAL 日志(先写日志数据,再写用户数据,以保证用户数据的持久化):
目录里,还有 LOG 和 LOG.old 日志文件,记录的是数据库运行过程中的打印出来的日志,也就是通常意义上所说的日志(给人看的,用来调试、定位问题用的),每次重新打开数据库,都会创建新的 LOG,之前的 LOG 文件被重命名为 LOG.old:
目录中的 LOCK 是 leveldb 的文件锁。因为 leveldb 不允许多个进程同时访问数据库目录,所以每次打开数据库,leveldb 都会先获取 LOCK,获取成功才能下行流程,以此避免一个数据库被多个进程打开操作。
MANIFEST 文件包含的是 leveldb 的元信息,每次打开数据库,都会生成一个新的 MANIFEST 文件,新生成的 MANIFEST 文件有更大的版本号。
MANIFEST 文件可能会存在多个,CURRENT 文件记录的就是当前有效的 MANIFEST 的文件名: