在 Rust 中使用 LevelDB
本文的主要内容是简单介绍了 LevelDB,并提供了在 Rust 中使用的一些个人经验。
2023-12-02
LevelDB 是什么?
LevelDB 是一个由 Google 开发的开源 Key-Value 键值对存储库(并不是数据库),提供了高性能、轻量级的数据存储解决方案,适用于本地存储和嵌入式系统,在区块链应用中常被作为默认的状态存储库(如 Hyperledger Fabric 和 Chainmaker 长安链)。
LevelDB 的主要特性有:
-
键值对存储: LevelDB 是一个键值对数据存储库,每个值通过唯一的键进行访问。
-
支持快速读写: 具有高性能的读写操作,适用于需要快速数据检索的应用。
-
单机使用: 主要设计用于单机环境,适用于本地存储需求。
-
排序: 数据被按键排序存储,这有助于范围查询等操作。
-
轻量级: LevelDB 是一个相对轻量级的数据存储库,适用于嵌入式系统和资源受限的环境。
-
原子操作: LevelDB 支持原子操作,确保多个操作的一致性。
-
快照: 提供快照功能,允许在不影响当前数据存储库状态的情况下进行数据读取。
-
可移植性: LevelDB 的实现是可移植的,可以在不同平台上使用。
LevelDB 使用了 LSM Tree(Log-Structured Merge tree),这使得 LevelDB 具有较高的写性能,并且通过后台合并操作使其也具有了不俗的读性能。
在 LSM Tree 执行写入操作时:
-
将新数据追加到 Write-Ahead Log (WAL) 日志文件(用于保证数据的持久化及恢复);
-
将新数据添加 Memtable(内存结构,使用Skip List 跳表实现);
-
在 Memtable 达到阈值后将其写入磁盘中的 SSTable(Sorted String Table),SSTable 是一个不可变的有序文件,其中的数据按键排序;
-
如果有必要,会将现有的 SSTable 进行合并,以优化读取性能。
在 Rust 中使用 LevelDB
LevelDB 是一个 Key-Value 存储库,支持的 CRUD 操作有:
- :将某个 Value 放到 Key 的位置去,CU 的区别仅在于 Value 的具体内容是什么;
put(key, value)
- :读取某个 Key 对应的值;
get(key)
- :删除某个 Key 对应的值;
delete(key)
- :遍历存储库,支持通过 Key 的前缀匹配定位到特定位置(因为 LevelDB 中的 Key 是有序的)。
iterate()
下面是在 Rust 基于 rusty_leveldb 库操作 LevelDB 的示例:
// cargo add temdir rusty-leveldb
use rusty_leveldb::{DBIterator, LdbIterator, Options, DB};
use tempdir::TempDir;
// helper 打印 Key-Value
fn print_kv(key: Vec<u8>, value: Vec<u8>) {
println!(
"{:?} -> {:?}",
String::from_utf8(key).unwrap(),
String::from_utf8(value).unwrap()
);
}
// helper, 读取 iterator 当前的值
fn read_current(iterator: &mut DBIterator) -> (Vec<u8>, Vec<u8>) {
let mut key: Vec<u8> = vec![];
let mut value: Vec<u8> = vec![];
iterator.current(&mut key, &mut value);
return (key, value);
}
fn main() {
// 创建一个临时目录
let tempdir = TempDir::new("demo").unwrap();
let path = tempdir.path();
// 从文件中打开 LevelDB(或新建)
let mut options = Options::default();
options.create_if_missing = true;
let mut db = DB::open(path, options).expect("Unable to open the database");
// 插入数据
db.put(b"state-schema1-1", b"1").unwrap();
db.put(b"state-schema2-1", b"1").unwrap();
db.put(b"state-schema2-2", b"2").unwrap();
db.put(b"state-schema2-3", b"3").unwrap();
db.put(b"state-schema2-4", b"4").unwrap();
// 在插入后使用 flush 方法将数据写入磁盘
db.flush().unwrap();
// 读取数据
if let Some(value) = db.get(b"state-schema1-1") {
println!("key exists: {}", String::from_utf8(value).unwrap());
}
// 遍历数据
let mut iterator = db.new_iter().unwrap();
// 使用 seek 定位到 state-schema2 这个 Key
// Key 是有序的,因此使用 iterator 后续的 Key 将是 state-schema2-[1 至 4]
// 注意:如果保存了 state-schema3-*,那么 state-schema3-* 也会被读取到
// 如果只想要读取 state-schema2-* Key 的值,则需要在 while 循环中验证 Key 的前缀
iterator.seek("state-schema2".as_bytes());
// 首先读取当前的值,如果直接开始遍历,会跳过当前位置的 Key,即 state-schema2-1
let (key, value) = read_current(&mut iterator);
print_kv(key.clone(), value.clone());
while iterator.advance() {
let (key, value) = read_current(&mut iterator);
print_kv(key.clone(), value.clone());
}
// 删除数据
db.delete(b"state-schema1-1").expect("Unable to delete key");
// 在删除后使用 flush 方法将数据写入磁盘
db.flush().unwrap();
if let Some(value) = db.get(b"state-schema1-1") {
println!("key exists: {}", String::from_utf8(value).unwrap());
} else {
println!("key deleted");
};
}