< 返回版块

roseduan 发表于 2024-04-24 09:57

之前写过一个 Go 语言的 mini-bitcask,实现了一个基于 bitcask 存储模型的极简 KV 存储引擎。 可以结合之前的文章食用:https://mp.weixin.qq.com/s/s8s6VtqwdyjthR6EtuhnUA

这次重新用 Rust 实现了一个版本,代码量和之前的差不多,包含了常用的方法,例如 Set、Get、Delete、Scan、PrefixScan、Merge。 image.png 项目地址:https://github.com/rosedblabs/mini-bitcask-rs

Set

pub fn set(&mut self, key: &[u8], value: Vec<u8>) -> Result<()> {
    let (offset, len) = self.log.write_entry(key, Some(&value))?;
    let value_len = value.len() as u32;
    self.keydir.insert(
        key.to_vec(),
        (offset + len as u64 - value_len as u64, value_len),
    );
    Ok(())
}

Set 逻辑比较直观简洁,写入磁盘日志,并且更新内存索引结构。

Get

Get 则是先从内存中获取索引,再从磁盘中获取 Value。

pub fn get(&mut self, key: &[u8]) -> Result<Option<Vec<u8>>> {
    if let Some((value_pos, value_len)) = self.keydir.get(key) {
        let val = self.log.read_value(*value_pos, *value_len)?;
        Ok(Some(val))
    } else {
        Ok(None)
    }
}

Delete

delete 的逻辑和 Set 类似,只是写入了一个空的值,并且从内存中对应的 key 移除。

pub fn delete(&mut self, key: &[u8]) -> Result<()> {
    self.log.write_entry(key, None)?;
    self.keydir.remove(key);
    Ok(())
}

Scan

scan 功能主要借助了 Rust 自带的内存数据结构 BTreeMap 的迭代器进行实现,非常简洁和方便。

impl<'a> Iterator for ScanIterator<'a> {
    type Item = Result<(Vec<u8>, Vec<u8>)>;

    fn next(&mut self) -> Option<Self::Item> {
        self.inner.next().map(|item| self.map(item))
    }
}

impl<'a> DoubleEndedIterator for ScanIterator<'a> {
    fn next_back(&mut self) -> Option<Self::Item> {
        self.inner.next_back().map(|item| self.map(item))
    }
}

Merge

merge 的逻辑其实也比较简单,将内存中的数据全部重写,并且替换旧的文件即可。

pub fn merge(&mut self) -> Result<()> {
    // 创建一个新的临时用于用于写入
    let mut merge_path = self.log.path.clone();
    merge_path.set_extension(MERGE_FILE_EXT);

    let mut new_log = Log::new(merge_path)?;
    let mut new_keydir = KeyDir::new();

    // 重写数据
    for (key, (value_pos, value_len)) in self.keydir.iter() {
        let value = self.log.read_value(*value_pos, *value_len)?;
        let (offset, len) = new_log.write_entry(key, Some(&value))?;
        new_keydir.insert(
            key.clone(),
            (offset + len as u64 - *value_len as u64, *value_len),
        );
    }

    // 重写完成,重命名文件
    std::fs::rename(new_log.path, self.log.path.clone())?;

    new_log.path = self.log.path.clone();
    // 替换现在的
    self.log = new_log;
    self.keydir = new_keydir;

    Ok(())
}

通过这个简单的项目,可以学习到 Rust 的大多数基础语法,例如:

  • 数据类型,数组、整型等
  • match 表达式
  • 函数
  • 结构体
  • 错误处理
  • 迭代器 Iterator 和 DoubleEndedIterator
  • 文件读写操作
  • BufWriter 和 BufReader
  • 单元测试撰写

项目地址:https://github.com/rosedblabs/mini-bitcask-rs

觉得有帮助的话请不用吝啬你的 Star ⭐️ 哦!

评论区

写评论
asuper 2024-04-25 11:49

文章写的挺好,代码也简练易懂。

有个小点可以改进下,待读入的buf可以用Vec::with_capacity()初始化,读完使用 unsafe{ v.set_len() },这样可以避免多余的赋初值操作,这里的unsafe是很安全的

oywz 2024-04-24 17:14

感谢大佬,果断STAR

1 共 2 条评论, 1 页