第 2 章 · 编译安装与语言绑定
第 2 章 · 编译安装与语言绑定
2.1 环境准备
在编译 LevelDB 之前,需要安装以下依赖:
Ubuntu / Debian
sudo apt-get update
sudo apt-get install -y \
build-essential \
cmake \
git \
libgflags-dev \
libsnappy-dev \
zlib1g-dev \
libbz2-dev \
liblz4-dev \
libzstd-dev
CentOS / RHEL
sudo yum groupinstall -y "Development Tools"
sudo yum install -y cmake3 git gflags-devel snappy-devel \
zlib-devel bzip2-devel lz4-devel libzstd-devel
macOS
brew install cmake gflags snappy lz4 zstd
依赖说明
| 依赖 | 用途 | 必需? |
|---|---|---|
| CMake | 构建系统 | ✅ 是 |
| gflags | 命令行参数解析(db_bench) | 可选 |
| Snappy | 默认压缩算法 | 推荐 |
| zlib | 替代压缩算法 | 可选 |
| bzip2 | 替代压缩算法 | 可选 |
| lz4 | 替代压缩算法 | 可选 |
| zstd | 替代压缩算法 | 可选 |
2.2 源码编译安装
获取源码
git clone --depth 1 --branch 1.23 \
https://github.com/google/leveldb.git
cd leveldb
git submodule update --init --recursive
💡 提示:使用
--depth 1只克隆最新提交,加快下载速度。
编译
mkdir -p build && cd build
# Release 模式(推荐生产使用)
cmake -DCMAKE_BUILD_TYPE=Release \
-DLEVELDB_BUILD_BENCHMARKS=ON \
-DLEVELDB_BUILD_TESTS=ON \
..
# 并行编译(使用所有 CPU 核心)
cmake --build . -j$(nproc)
CMake 常用选项
| 选项 | 默认值 | 说明 |
|---|---|---|
CMAKE_BUILD_TYPE | Debug | Release / Debug / RelWithDebInfo |
CMAKE_INSTALL_PREFIX | /usr/local | 安装路径 |
LEVELDB_BUILD_BENCHMARKS | ON | 是否编译 db_bench |
LEVELDB_BUILD_TESTS | ON | 是否编译测试 |
HAVE_SNAPPY | 自动检测 | 启用 Snappy 压缩 |
HAVE_ZLIB | 自动检测 | 启用 zlib 压缩 |
HAVE_BZIP2 | 自动检测 | 启用 bzip2 压缩 |
HAVE_LZ4 | 自动检测 | 启用 lz4 压缩 |
HAVE_ZSTD | 自动检测 | 启用 zstd 压缩 |
运行测试
# 运行所有测试
ctest --output-on-failure
# 运行特定测试
./leveldb_tests --gtest_filter="DBTest.PutGet"
安装
sudo cmake --install .
安装后文件位置:
/usr/local/
├── include/
│ └── leveldb/
│ ├── db.h # 核心 API
│ ├── options.h # 配置选项
│ ├── write_batch.h # 批量写入
│ ├── comparator.h # 自定义比较器
│ ├── filter_policy.h # Bloom Filter
│ ├── cache.h # 缓存接口
│ ├── snapshot.h # 快照
│ ├── iterator.h # 迭代器
│ ├── status.h # 状态码
│ └── ...
├── lib/
│ ├── libleveldb.a # 静态库
│ └── libleveldb.so # 动态库
└── bin/
└── db_bench # 性能测试工具
验证安装
# 检查头文件
ls /usr/local/include/leveldb/db.h
# 检查库文件
ls /usr/local/lib/libleveldb.*
# 检查 db_bench
which db_bench
2.3 第一个 LevelDB 程序(C++)
hello_leveldb.cpp
#include <cassert>
#include <iostream>
#include <string>
#include "leveldb/db.h"
int main() {
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true; // 数据库不存在时自动创建
// 打开数据库
leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
if (!status.ok()) {
std::cerr << "打开数据库失败: " << status.ToString() << std::endl;
return 1;
}
// 写入数据
leveldb::WriteOptions write_opts;
status = db->Put(write_opts, "name", "LevelDB");
assert(status.ok());
status = db->Put(write_opts, "version", "1.23");
assert(status.ok());
// 读取数据
leveldb::ReadOptions read_opts;
std::string value;
status = db->Get(read_opts, "name", &value);
assert(status.ok());
std::cout << "name = " << value << std::endl;
// 删除数据
status = db->Delete(write_opts, "version");
assert(status.ok());
// 遍历所有键值对
leveldb::Iterator* it = db->NewIterator(read_opts);
for (it->SeekToFirst(); it->Valid(); it->Next()) {
std::cout << it->key().ToString() << " -> "
<< it->value().ToString() << std::endl;
}
assert(it->status().ok());
delete it;
delete db; // 关闭数据库
return 0;
}
编译运行
# 编译
g++ -std=c++17 -O2 -o hello_leveldb hello_leveldb.cpp \
-lleveldb -lpthread -lsnappy
# 运行
./hello_leveldb
# 输出:
# name = LevelDB
# name -> LevelDB
CMakeLists.txt 方式
cmake_minimum_required(VERSION 3.10)
project(hello_leveldb)
set(CMAKE_CXX_STANDARD 17)
find_package(leveldb REQUIRED)
add_executable(hello_leveldb hello_leveldb.cpp)
target_link_libraries(hello_leveldb leveldb::leveldb)
2.4 Docker 部署
Dockerfile
FROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y \
build-essential cmake git \
libgflags-dev libsnappy-dev zlib1g-dev \
libbz2-dev liblz4-dev libzstd-dev \
&& rm -rf /var/lib/apt/lists/*
# 克隆并编译 LevelDB
RUN git clone --depth 1 --branch 1.23 \
https://github.com/google/leveldb.git /opt/leveldb
WORKDIR /opt/leveldb
RUN mkdir build && cd build && \
cmake -DCMAKE_BUILD_TYPE=Release .. && \
cmake --build . -j$(nproc)
# 运行阶段 - 最小镜像
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
libsnappy1v5 libgflags2.2 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /opt/leveldb/build/libleveldb.so* /usr/local/lib/
COPY --from=builder /opt/leveldb/include/leveldb/ /usr/local/include/leveldb/
RUN ldconfig
VOLUME ["/data"]
EXPOSE 8080
CMD ["echo", "LevelDB library installed. Link it in your application."]
构建与运行
# 构建镜像
docker build -t leveldb:1.23 .
# 运行容器
docker run -v /host/data:/data leveldb:1.23
⚠️ 注意:LevelDB 是嵌入式库,不是独立服务。Docker 中通常需要配合自己的应用服务器一起使用。详见 第 13 章 · Docker 部署。
2.5 Go 语言绑定(goleveldb)
安装
go get github.com/syndtr/goleveldb/leveldb
完整示例
package main
import (
"fmt"
"log"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/opt"
"github.com/syndtr/goleveldb/leveldb/util"
)
func main() {
// 打开数据库
db, err := leveldb.OpenFile("/tmp/goleveldb", nil)
if err != nil {
log.Fatal(err)
}
defer db.Close()
// ========== 基本操作 ==========
// Put - 写入
err = db.Put([]byte("user:1001:name"), []byte("张三"), nil)
if err != nil {
log.Fatal(err)
}
db.Put([]byte("user:1001:email"), []byte("[email protected]"), nil)
db.Put([]byte("user:1002:name"), []byte("李四"), nil)
db.Put([]byte("user:1002:email"), []byte("[email protected]"), nil)
// Get - 读取
data, err := db.Get([]byte("user:1001:name"), nil)
if err != nil {
log.Fatal(err)
}
fmt.Printf("user:1001:name = %s\n", data)
// Delete - 删除
err = db.Delete([]byte("user:1002:email"), nil)
if err != nil {
log.Fatal(err)
}
// ========== 批量写入 ==========
batch := new(leveldb.Batch)
batch.Put([]byte("user:1003:name"), []byte("王五"))
batch.Put([]byte("user:1003:email"), []byte("[email protected]"))
batch.Delete([]byte("user:1002:name"))
err = db.Write(batch, nil)
if err != nil {
log.Fatal(err)
}
// ========== 迭代器 ==========
// 前缀扫描:查询所有 user:1001 的字段
fmt.Println("\n--- user:1001 的所有字段 ---")
iter := db.NewIterator(util.BytesPrefix([]byte("user:1001:")), nil)
for iter.Next() {
key := string(iter.Key())
value := string(iter.Value())
fmt.Printf(" %s = %s\n", key, value)
}
iter.Release()
// 范围扫描
fmt.Println("\n--- 所有 user ---")
iter = db.NewIterator(nil, nil)
for iter.Next() {
fmt.Printf(" %s = %s\n", iter.Key(), iter.Value())
}
iter.Release()
if err := iter.Error(); err != nil {
log.Fatal(err)
}
// ========== Snapshot(快照)==========
snap, err := db.GetSnapshot()
if err != nil {
log.Fatal(err)
}
defer snap.Release()
// 在快照上读取(不受后续写入影响)
snapData, _ := snap.Get([]byte("user:1001:name"), nil)
fmt.Printf("\n快照读取: user:1001:name = %s\n", snapData)
}
Go 高级配置
opts := &opt.Options{
// 块缓存大小(默认 8MB)
BlockCacheCapacity: 64 * opt.MiB,
// 块大小(默认 4KB)
BlockSize: 4 * opt.KiB,
// 写缓冲区大小(默认 4MB)
WriteBuffer: 8 * opt.MiB,
// 最大打开文件数
OpenFilesCacheCapacity: 500,
// Bloom Filter 位数(每个 key 10 bits)
Filter: filter.NewBloomFilter(10),
// 压缩类型
Compression: opt.SnappyCompression,
// Level 0 文件数触发 Compaction
CompactionL0Trigger: 4,
// Level 0 慢写触发阈值
WriteL0SlowdownTrigger: 8,
WriteL0PauseTrigger: 12,
}
db, err := leveldb.OpenFile("/tmp/goleveldb-advanced", opts)
2.6 Python 绑定(Plyvel)
安装
pip install plyvel
完整示例
import plyvel
import json
# 打开数据库
db = plyvel.DB('/tmp/pyldb', create_if_missing=True)
# ========== 基本操作 ==========
# 写入(注意:key 和 value 必须是 bytes)
db.put(b'user:1001:name', '张三'.encode('utf-8'))
db.put(b'user:1001:age', b'30')
db.put(b'user:1002:name', '李四'.encode('utf-8'))
db.put(b'user:1002:age', b'25')
# 读取
name = db.get(b'user:1001:name')
print(f"user:1001:name = {name.decode('utf-8')}")
# 删除
db.delete(b'user:1002:age')
# ========== 批量写入 ==========
with db.write_batch() as wb:
wb.put(b'user:1003:name', '王五'.encode('utf-8'))
wb.put(b'user:1003:age', b'35')
wb.put(b'user:1003:email', b'[email protected]')
# ========== 迭代器 ==========
# 前缀扫描
print("\n--- 前缀扫描 user:1001 ---")
for key, value in db.iterator(prefix=b'user:1001:'):
print(f" {key.decode()} = {value.decode()}")
# 范围扫描
print("\n--- 范围扫描 [user:1001, user:1003) ---")
for key, value in db.iterator(start=b'user:1001', stop=b'user:1003'):
print(f" {key.decode()} = {value.decode()}")
# 反向遍历
print("\n--- 反向遍历 ---")
for key, value in db.iterator(reverse=True):
print(f" {key.decode()} = {value.decode()}")
# ========== Snapshot ==========
snap = db.snapshot()
db.put(b'user:1001:name', '张三改名'.encode('utf-8'))
# 快照读取的是旧值
old_name = snap.get(b'user:1001:name')
new_name = db.get(b'user:1001:name')
print(f"\n快照值: {old_name.decode()}")
print(f"当前值: {new_name.decode()}")
# 关闭数据库
db.close()
高级配置
import plyvel
db = plyvel.DB(
'/tmp/pyldb_advanced',
create_if_missing=True,
max_open_files=500,
write_buffer_size=8 * 1024 * 1024, # 8MB
max_file_size=2 * 1024 * 1024, # 2MB
block_size=8 * 1024, # 8KB
bloom_filter_bits=10, # Bloom Filter
compression='snappy', # 压缩方式
)
2.7 其他语言绑定
| 语言 | 库 | 安装命令 | 备注 |
|---|---|---|---|
| Rust | rusty_leveldb | cargo add rusty-leveldb | 纯 Rust 实现 |
| Rust | leveldb | cargo add leveldb | C++ 绑定 |
| Java | leveldbjni | Maven: leveldbjni-all | JNI 绑定 |
| Node.js | level | npm install level | 基于 C++ 绑定 |
| Ruby | leveldb-ruby | gem install leveldb-ruby | FFI 绑定 |
| Erlang | eleveldb | GitHub 克隆 | Riak 使用 |
2.8 常见编译问题
问题一:找不到 libleveldb
error: libleveldb.so: cannot open shared object file
解决:
# 方法 1:设置 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# 方法 2:更新动态链接器缓存
sudo ldconfig
# 方法 3:静态链接
g++ -o app app.cpp /usr/local/lib/libleveldb.a -lpthread -lsnappy
问题二:CMake 找不到 LevelDB
# 方法 1:指定路径
set(leveldb_DIR "/usr/local/lib/cmake/leveldb")
find_package(leveldb REQUIRED)
# 方法 2:手动链接
include_directories(/usr/local/include)
link_directories(/usr/local/lib)
target_link_libraries(app leveldb)
问题三:Snappy 相关链接错误
undefined reference to 'snappy::...'
解决:确保安装了 Snappy 开发包并链接:
# Ubuntu
sudo apt-get install libsnappy-dev
# 编译时加 -lsnappy
g++ -o app app.cpp -lleveldb -lsnappy -lpthread
2.9 本章小结
| 内容 | 要点 |
|---|---|
| 编译工具 | CMake 3.10+、GCC 7+ 或 Clang 6+ |
| 必需依赖 | 无(Snappy 推荐安装) |
| C++ 使用 | #include "leveldb/db.h",链接 -lleveldb |
| Go 绑定 | github.com/syndtr/goleveldb,纯 Go 实现 |
| Python 绑定 | plyvel,Cython 实现 |
| Docker | 多阶段构建,运行时保留最小依赖 |
扩展阅读
- LevelDB CMake 源码:
leveldb/CMakeLists.txt,理解构建配置 - goleveldb 文档:GoDoc
- Plyvel 文档:ReadTheDocs
- Snappy 压缩库:GitHub
← 第 1 章 · 简介与历史 | 第 3 章 · 架构总览 →