cover

在 Rust 中使用 LevelDB

本文的主要内容是简单介绍了 LevelDB,并提供了在 Rust 中使用的一些个人经验。

2023-12-02

LevelDB 是什么?

LevelDB 是一个由 Google 开发的开源 Key-Value 键值对存储库(并不是数据库),提供了高性能、轻量级的数据存储解决方案,适用于本地存储和嵌入式系统,在区块链应用中常被作为默认的状态存储库(如 Hyperledger Fabric 和 Chainmaker 长安链)。

LevelDB 的主要特性有:

  1. 键值对存储: LevelDB 是一个键值对数据存储库,每个值通过唯一的键进行访问。

  2. 支持快速读写: 具有高性能的读写操作,适用于需要快速数据检索的应用。

  3. 单机使用: 主要设计用于单机环境,适用于本地存储需求。

  4. 排序: 数据被按键排序存储,这有助于范围查询等操作。

  5. 轻量级: LevelDB 是一个相对轻量级的数据存储库,适用于嵌入式系统和资源受限的环境。

  6. 原子操作: LevelDB 支持原子操作,确保多个操作的一致性。

  7. 快照: 提供快照功能,允许在不影响当前数据存储库状态的情况下进行数据读取。

  8. 可移植性: LevelDB 的实现是可移植的,可以在不同平台上使用。

LevelDB 使用了 LSM Tree(Log-Structured Merge tree),这使得 LevelDB 具有较高的写性能,并且通过后台合并操作使其也具有了不俗的读性能。

在 LSM Tree 执行写入操作时:

  1. 将新数据追加到 Write-Ahead Log (WAL) 日志文件(用于保证数据的持久化及恢复);

  2. 将新数据添加 Memtable(内存结构,使用Skip List 跳表实现);

  3. 在 Memtable 达到阈值后将其写入磁盘中的 SSTable(Sorted String Table),SSTable 是一个不可变的有序文件,其中的数据按键排序;

  4. 如果有必要,会将现有的 SSTable 进行合并,以优化读取性能。

在 Rust 中使用 LevelDB

LevelDB 是一个 Key-Value 存储库,支持的 CRUD 操作有:

  1. put(key, value):将某个 Value 放到 Key 的位置去,CU 的区别仅在于 Value 的具体内容是什么;
  2. get(key):读取某个 Key 对应的值;
  3. delete(key):删除某个 Key 对应的值;
  4. iterate():遍历存储库,支持通过 Key 的前缀匹配定位到特定位置(因为 LevelDB 中的 Key 是有序的)。

下面是在 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");
    };
}