Rocksdb/Leveldb - Must Know

378 阅读2分钟

Introduction

rockesdb是facebook基于LevelDB打造的单机存储引擎, 字节的Abae/ByteKV/ByteGraph/Mysql都支持或使用了rockesdb

leveldb 是线程安全的

LSM-Tree

LSM is not just a Tree, while it's a data structure:

  1. 数据写入内存前, 先写入log, 这样不用担心crash数据丢失, 在内存达到阈值后再一起刷入磁盘, 提高写入性能
  2. 每一批落盘的数据集合为一个level, 这样随着时间线会依次在磁盘有多个新旧level
  3. level达到一定阈值, 会对多个level进行merge操作, 并将key按顺序排序, 已节省硬盘空间

Architecture

rockesdb存储思想采用的便是LSM-Tree

image.png

  1. memtable达到阈值时, 便会转为immutable memtable
  2. 后台线程定时将immutable memtable dump成sstable, sstable内的数据也是有序的
  3. 只有level0是由immutable memtable直接dump的, 其他level都是由上一层levelcompaction得到的
  4. manifest文件存储的是各sstabel在磁盘的位置以及最大最小key

Process

Write

write includes insert and delete

命令Put

Concurent

对于多线程并发「写写」问题, leveldb的解决方案是采用一个queue和一个条件变量pthread_cond_t实现的

条件变量可参考这篇文章 juejin.cn/post/715843…

  1. 多线程写只有拿到锁mutex_才能将w(k-v对)数据加到队列Writers_
  2. 然后当前线程判断w的标记位是否donew是否在队首
  3. 如果都不是, 则调用pthread_cond_wait 阻塞线程并解锁mutex_
  4. 如果是在队首, 则从Writers_中顺序读取多个w, 然后解锁mutex_
  5. 此时, 由于解锁了, 所以其他线程又可以继续取锁并把写了(把w加到Writers_中)
  6. 接着第4步, 将多个w依次写入logmemtable(如果memtable达到阈值会在这里转成immutable memtable)
  7. 然后再取锁加锁, 从Writers_中依次取出这些w然后将其的标记位设置为done(对应第2步)并且对应的线程调用pthread_cond_signal条件变量通知(但是这些线程还是会阻塞, 因为mutex_还被这个线程占着)
  8. 如果Writers_中还有w则再调用一次pthread_cond_signal唤醒下一个队首
  9. 最后释放锁mutex_

关于pthread_cond_wait: 当收到信号后本质上会将线程从cond_wait队列移动到mutex_wait队列, 也就是上面提到的即使收到信号也要取到锁才能执行

Read

命令Read

对于多线程并发「读读」问题, leveldb的解决方案是采用原子指针(底层用的是内存屏障)来保证的:

skip list插入数据简单来说分为两步:

  1. node指向后一个node
  2. 前一个node指向这个新node

第1步不会影响到读, 第2步采用的是原子指针所以不会取到中间态(指向nil)

image.png

Memtable

memtable有这么几种数据结构:skiplist、hash-skiplist、hash-linklist

Log

Ref

  1. https://byte_dance.fei_shu.cn/wiki/wikcnIWmHoB5oRcn0lGyWLlQXAe
  2. https://tech.byte_dance.net/articles/6969885972225851428
  3. blog.csdn.net/weixin_4266…