cover

在 Rust 中使用 LevelDB

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

2023-12-02

heading

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 进行合并,以优化读取性能。

heading

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