【存储】lotusdb的原理及实现

news2024/11/23 15:33:21

最近看了lotusdb的源码。lotusdb是一个golang实现的嵌入式的持久化kv存储。

从整体设计上看,lotusdb采用了类似LSM树的架构,并采用了针对SSD的优化,将key和value分开存储。在此基础上,lotusdb将LSM树中存储key的SST使用B+树或者hash table的索引替换。lotusdb作者认为该设计消除了多级SST带来的读放大问题,使lotusdb的读性能更加稳定。这确实没有问题,这样的设计使lotusdb平衡了B+树和LSM树的缺点,同时也平衡了两者的优点,使得lotusdb变得中庸。(中庸不一定不好,实际生产的效果如何还是要看在具体场景下的性能数据)另外,lotusdb还有几个缺陷:

  • 目前并没有提供范围查询的接口;
  • 没有事务的保证;
  • 在容灾恢复方面有所缺陷;
    总的来说,博主认为lotusdb还是一个相对比较稚嫩但挺有意思的项目,如果感兴趣,不妨一读。

文章目录

  • 整体架构
    • 写入流程
    • 后台flush
    • 读取流程
    • 压缩
  • 实现原理
  • 具体实现
    • Index
      • B+树
      • Hash table
    • 容灾恢复
  • 总结

整体架构

lotusdb的整体架构图如下。lotusdb通过memtable的来缓存用户的写入。其维护多个memtable,最新的一个为active memtable,可以写入;其余为immutable memtable,只读。memtable将写入的key和value维护在内存中的跳表中,并通过wal来保证持久性。immutable memtable会定期的在后台刷入持久化存储。

lotusdb采用了针对ssd的优化,将key和value分开存储。key存储在Index中,value存储在value log中。value log比较简单,是多分片的wal;key的部分,Index为接口,lotusdb中没有提供经典LSM树中多层级SST的实现,只提供了B+树(boltdb)和hash table的实现。

写入流程

写入时,将数据写入active memtable即可返回。memtable由wal和内存态的跳表组成。写入时先将数据写入wal,以保证数据的持久性,再将数据插入跳表。active memtable数据满了后,会创建新的memtable,并将当前active memtable转变为immutable memtable,在后台刷入持久化存储。

后台flush

active memtable在写满以后会转变为immutable memtable,并在后台刷入。首先会将对应表中的内容写入value log。value log的实现比较简单,采用多分片的wal,所以value log的写入为批量的追加写。在value log写入完成后,得到key及对应的值在vlaue log中的位置,将key和position的键值对写入Index。对于Index的写入,无论是B+树还是hash表,这里的写入都是批量的随机写。

LSM树的设计就是通过牺牲读性能来提升写性能。lotus的这一揉杂了B+树和LSM树,其读取性能会比LSM好,但会比B+树差;写入性能会比B+树好,但是会比LSM树差(单纯从设计上分析,实际效果得看具体场景下的数据)。这就是文章开头提到的,会使lotusdb变得中庸。当然中庸并不是个贬义词。

读取流程

在读取时,会依次读取memtable。如果获取到数据则直接返回,否则去查询Index,根据postion信息去value log中获取值并返回。

压缩

在LSM树中,多级SST的设计保证了批量追加写来优化写入性能,但同时带来了读放大和冗余数据的问题。除此之外,SST的compact也是一个复杂度比较高的问题。第一是后台的compact会极大的影响性能,博主曾经就遇到过因为es后台compact消耗大量资源导致写入超时的问题。另一个是compact本身的实现复杂度就相对较高,基于LSM的存储有各种不同的compact策略,感兴趣可以自己去查阅,这里不做展开。

在lotusdb中,Index没有提供SST的实现,而是提供了B+树或者hash table的实现,所以key的部分不需要考虑compact。但是value log的是完全追加写,还是需要compact来消除冗余数据。目前lotusdb仅是提供了compact方法供上层调用,但是没有提供具体的compact策略,这对上层来说是不够友好的。

实现原理

原理部分,主要基于WiscKey: Separating Keys from Values
in SSD-conscious Storage,会单独开一篇进行讲解。

具体实现

lotusdb的主要实现如下。和整体架构部分介绍的一样,主要有三大部分组成:

  • memtables。分为active memtable和immutable memtable。
  • index。实现Index接口的索引,目前lotusdb提供了B+树和hash table的实现,用来存储key以及对应value在value log中的位置。
  • vlog。用来存储实际的值。
type DB struct {
    activeMem *memtable      // Active memtable for writing.
    immuMems  []*memtable    // Immutable memtables, waiting to be flushed to disk.
    index     Index          // index is multi-partition indexes to store key and chunk position.
    vlog      *valueLog      // vlog is the value log.
    fileLock  *flock.Flock   // fileLock to prevent multiple processes from using the same database directory.
    flushChan chan *memtable // flushChan is used to notify the flush goroutine to flush memtable to disk.
    flushLock sync.Mutex     // flushLock is to prevent flush running while compaction doesn't occur
    mu        sync.RWMutex
    closed    bool
    options   Options
    batchPool sync.Pool // batchPool is a pool of batch, to reduce the cost of memory allocation.
}

Index

Index接口定义如下。上面也多次提到了,对于Index,lotus提供了B+树和hash table的实现。

type Index interface {
    // PutBatch put batch records to index
    PutBatch([]*KeyPosition) error

    // Get chunk position by key
    Get([]byte) (*KeyPosition, error)

    // DeleteBatch delete batch records from index
    DeleteBatch([][]byte) error

    // Sync sync index data to disk
    Sync() error

    // Close index
    Close() error
}

B+树

B+树的实现如下,可以看到其底层采用了多个blotdb来实现。
关于boltdb,前面我们介绍过,这里就不多介绍,可以参见【存储】etcd的存储是如何实现的(3)-blotdb。

// BPTree is the BoltDB index implementation.
type BPTree struct {
    options indexOptions
    trees   []*bbolt.DB
}

Hash table

Hash table的实现同样采用了多个分片的hash表。

type HashTable struct {
    options indexOptions
    tables  []*diskhash.Table
}

hash表的实现如下。

该hash表要求value的长度固定,所以适应的场景有限,大多数适合来防止metadata等。每个hash表持有两个文件,分布为primary file和overflow file。其中主要的数据结构为bucket,每个bucket中含有31个slot及一个offset,slot中存储了key及value,offset指向overflow的bucket来解决hash冲突的问题。

  • 读取时,首先根据key来得到primary file中的bucket的index,根据index可以计算出该bucket的offset(每个bucket大小固定)。然后遍历该bucket中的slot,如果没有匹配,则根据offset继续遍历overflow bucket,直到匹配或者遍历结束。
  • 写入时,同样根据key来得到primary file中bucket的offset,遍历该bucket及其overflow bucket中的slot,找到匹配的slot或者空的slot。如果有匹配的slot,则进行update,如果有空的slot,则进行insert。如果既没有匹配的slot,也没有空的slot,则会创建新的overflow bucket并进行写入。
  • 每次写入成功后,会根据load factor来判断是否需要分裂。如果当前负载大于load factor,则会在primary中创建新的bucket并进行rebalance。

容灾恢复

博主认为lotusdb作为一个持久化存储,最有问题的地方在于其容灾恢复方面。

  • 下面是后台刷新的过程,可以看到当flushMemtable方法出错中断,也就是一个immutable写入index和vlog出错了,并没有做任何的补救措施。而是继续刷新过程,这个过程就可能会导致数据的丢失。
func (db *DB) listenMemtableFlush() {
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, os.Interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
    for {
       select {
       case table := <-db.flushChan:
          db.flushMemtable(table)
       case <-sig:
          return
       }
    }
}

func (db *DB) flushMemtable(table *memtable) {
    db.flushLock.Lock()
    sklIter := table.skl.NewIterator()
    var deletedKeys [][]byte
    var logRecords []*ValueLogRecord

    // iterate all records in memtable, divide them into deleted keys and log records
    for sklIter.SeekToFirst(); sklIter.Valid(); sklIter.Next() {
       key, valueStruct := y.ParseKey(sklIter.Key()), sklIter.Value()
       if valueStruct.Meta == LogRecordDeleted {
          deletedKeys = append(deletedKeys, key)
       } else {
          logRecord := ValueLogRecord{key: key, value: valueStruct.Value}
          logRecords = append(logRecords, &logRecord)
       }
    }
    _ = sklIter.Close()

    // write to value log, get the positions of keys
    keyPos, err := db.vlog.writeBatch(logRecords)
    if err != nil {
       log.Println("vlog writeBatch failed:", err)
       db.flushLock.Unlock()
       return
    }

    // sync the value log
    if err := db.vlog.sync(); err != nil {
       log.Println("vlog sync failed:", err)
       db.flushLock.Unlock()
       return
    }

    // write all keys and positions to index
    if err := db.index.PutBatch(keyPos); err != nil {
       log.Println("index PutBatch failed:", err)
       db.flushLock.Unlock()
       return
    }
    // delete the deleted keys from index
    if err := db.index.DeleteBatch(deletedKeys); err != nil {
       log.Println("index DeleteBatch failed:", err)
       db.flushLock.Unlock()
       return
    }
    // sync the index
    if err := db.index.Sync(); err != nil {
       log.Println("index sync failed:", err)
       db.flushLock.Unlock()
       return
    }

    // delete the wal
    if err := table.deleteWAl(); err != nil {
       log.Println("delete wal failed:", err)
       db.flushLock.Unlock()
       return
    }

    // delete old memtable kept in memory
    db.mu.Lock()
    if len(db.immuMems) == 1 {
       db.immuMems = db.immuMems[:0]
    } else {
       db.immuMems = db.immuMems[1:]
    }
    db.mu.Unlock()

    db.flushLock.Unlock()
}

总结

就如文章开始所讲,博主认为lotusdb还是一个相对比较稚嫩但挺有意思的项目,能够反映出作者的一些有意思的想法。其中的问题,随着迭代也会慢慢完善。


如果觉得本文对您有帮助,可以请博主喝杯咖啡~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1148110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进阶JAVA篇-了解 File 文件的常用API

&#x1f525;博客主页&#xff1a; 小扳_-CSDN博客 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 1.0 File 文件的说明 2.0 如何创建 File 类的对象 2.1 需要注意的事项 3.0 File 类的常用 API 3.1 如何创建文件或文件夹 3.2 如何查询文件和文件夹的信息 3.3 如何删除文件和…

鸿蒙系统、澎湃系统和安卓系统的区别一看就懂

前言 最近看了小米澎湃OS的发布会&#xff0c;这是继华为鸿蒙OS脱离Android OS后&#xff0c;国内发布的另一个重量级的操作系统。 依稀记得&#xff0c;当初鸿蒙OS问世的时候&#xff0c;很多人都质疑它是Android OS的套壳&#xff0c;对鸿蒙系统提出了诸多质疑和否定。 现趁着…

ActiveMQ

ActiveMQ 安装 下载网址&#xff1a;ActiveMQ 一定要和自己安装的jdk版本匹配&#xff0c;不然会报错 下载到本地之后解压缩 有可能端口号被占用 解除端口号占用&#xff0c;参考&#xff1a;Windows_端口被占用 打开cmd 查询所有的端口号 netstat -nao查询指定端口号 n…

基于STM32的示波器信号发生器设计

**单片机设计介绍&#xff0c;基于STM32的示波器信号发生器设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序文档 六、 文章目录 一 概要 基于STM32的示波器信号发生器是一种高性能的电子仪器&#xff0c;用于测试和分析电路中的电信号。在该系统中&a…

ZKP7.1 Polynomial Commitments Based on Error-correcting Codes (Background)

ZKP学习笔记 ZK-Learning MOOC课程笔记 Lecture 7: Polynomial Commitments Based on Error-correcting Codes (Yupeng Zhang) Recall: common paradigm for efficient SNARK A polynomial commitment scheme A polynomial interactive oracle proof (IOP) SNARK for gene…

AD9371 官方例程HDL详解之JESD204B RX侧格式配置及各层主要功能

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 采样率和各个时钟之间的关系 &#xff1a; AD9371 官方例程HDL详解之JESD204B TX侧时钟生成 &#xff08;三&#xff09; 参考资料&#xff1a; UltraScale Architecture G…

Jenkins git 克隆代码超时问题解决

目录 一、问题描述二、解决方案方式一&#xff1a;手动配置超时时间方式二&#xff1a;浅克隆&#xff08;推荐&#xff09; 一、问题描述 在使用 Jenkins 首次进行服务部署的时候&#xff0c;如果我们项目的 .git 文件夹太大&#xff0c;可能会导致 git clone 失败。 在 Jen…

当『后设学习』碰上『工程学思维』

只要我成为一个废物&#xff0c;就没人能够利用我&#xff01; 雷猴啊&#xff0c;我是一只临期程序猿。打过几年工&#xff0c;写过几行代码。但今天我不想聊代码&#xff0c;我们聊聊学习这件事。 技术年年更新&#xff0c;尤其是前端框架&#xff0c;很多时候觉得学习速度都…

leetcode-栈与队列

C中stack 是容器么&#xff1f; 栈&#xff0c;队列往往不被归类为容器&#xff0c;而被归类为container adapter(容器适配器)。因为由底层的容器实现&#xff0c;同时使用适配器模式的设计模式&#xff0c;封装了一层。 我们使用的stack是属于哪个版本的STL&#xff1f;SGI ST…

【Java 进阶篇】解决Java Web应用中请求参数中文乱码问题

在Java Web应用开发中&#xff0c;处理请求参数时经常会遇到中文乱码的问题。当浏览器向服务器发送包含中文字符的请求参数时&#xff0c;如果不正确处理&#xff0c;可能会导致乱码问题&#xff0c;使得参数无法正确解析和显示。本文将详细探讨Java Web应用中请求参数中文乱码…

微信小程序 php java nodejs python课堂学生考勤签到系统zng1p

课堂考勤也是学校的核心&#xff0c;是必不可少的一个部分。在学校的整个教学行业中&#xff0c;学生担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类基于微信小程序也在不断改进。本课题所设计的基于微信小程序的课堂考勤系统&#xff0c;使用微信开发者与…

shell中的运算

目录 1.运算符号 2.运算指令 练习 1.运算符号 运算符号意义加法-减法*乘法/除法%除法后的余数**乘方自加一- -自减一<小于<小于等于>大于>大于等于等于ji ->jji*j*i->jj*i/j/i->jj/i%j%i->jj%i 2.运算指令 (()) //((a12))let //let a12 …

JavaScript_Pig Game保存当前分数

上个文章我们基本上完成了摇色子和切换当前玩家的功能。 现在我们开始写用户选择不再摇骰子的话&#xff0c;我们将用户的当前分数存入到持有分数中&#xff01; ● 首先我们应该利用一个数组去存储两个用户的分数 const scores [0, 0];● 接着我们利用数组来对分数进行累…

SHCTF2023 山河CTF Reverse方向[Week1]全WP 详解

文章目录 [WEEK1]ez_asm[WEEK1]easy_re[WEEK1]seed[WEEK1]signin[WEEK1]easy_math[WEEK1]ez_apk [WEEK1]ez_asm 从上往下读&#xff0c;第一处是xor 1Eh&#xff0c;第二处是sub 0Ah&#xff1b;逆向一下先加0A后异或1E 写个EXP data "nhuo[M7mc7uhc$7midgbTf7$7%#ubf7 …

nginx请求时找路径问题

nginx请求时找路径问题 你是否遇到过这样的情况&#xff1a; 当你安装了nginx的时候&#xff0c;为nginx配置了如下的location&#xff0c;想要去访问路径下面的内容&#xff0c;可是总是出现404&#xff0c;找不到文件&#xff0c;这是什么原因呢&#xff0c;今天我们就来解…

OpenAI : GPT-4 发布更新,整合了画图、插件、代码等能力

本心、输入输出、结果 文章目录 OpenAI : GPT-4 发布更新,整合了画图、插件、代码等能力前言GPT-4 的复合能力更新中的 automatic (自动的)获取天气我们看看讯飞星火的表现放大后内容并不是我们想要的我们看看百度文心一言的表现弘扬爱国精神OpenAI : GPT-4 发布更新,整合…

CSS3中的字体和文本样式

CSS3优化了CSS 2.1的字体和文本属性&#xff0c;同时新增了各种文字特效&#xff0c;使网页文字更具表现力和感染力&#xff0c;丰富了网页设计效果&#xff0c;如自定义字体类型、更多的色彩模式、文本阴影、生态生成内容、各种特殊值、函数等。 1、字体样式 字体样式包括类…

画个哆啦A梦吧

可自定义名字 源代码 #!/usr/bin/python # -*- coding:utf-8 -*-# from turtle import * import turtle as t# 无轨迹跳跃 def my_goto(x, y):t.penup()t.goto(x, y

多个相同地址的I2C设备,如何挂载在同一条总线上

前言 &#xff08;1&#xff09;如果有嵌入式企业需要招聘湖南区域日常实习生&#xff0c;任何区域的暑假Linux驱动实习岗位&#xff0c;可C站直接私聊&#xff0c;或者邮件&#xff1a;zhangyixu02gmail.com&#xff0c;此消息至2025年1月1日前均有效 &#xff08;2&#xff0…

提高抖音小店用户黏性和商品销量的有效策略

抖音小店是抖音平台上的电商模式&#xff0c;用户可以在抖音上购买各类商品。要提高用户黏性和商品销量&#xff0c;四川不若与众帮你整理了需要注意以下几个方面。 首先&#xff0c;提供优质的商品和服务。在抖音小店中&#xff0c;用户会通过观看商品展示视频和用户评价来选…