Introduction
rockesdb是facebook基于LevelDB打造的单机存储引擎, 字节的Abae/ByteKV/ByteGraph/Mysql都支持或使用了rockesdb
leveldb 是线程安全的
LSM-Tree
LSM is not just a Tree, while it's a data structure:
- 数据写入内存前, 先写入log, 这样不用担心crash数据丢失, 在内存达到阈值后再一起刷入磁盘, 提高写入性能
- 每一批落盘的数据集合为一个
level, 这样随着时间线会依次在磁盘有多个新旧level - 当
level达到一定阈值, 会对多个level进行merge操作, 并将key按顺序排序, 已节省硬盘空间
Architecture
rockesdb存储思想采用的便是LSM-Tree
memtable达到阈值时, 便会转为immutable memtable- 后台线程定时将
immutable memtabledump成sstable,sstable内的数据也是有序的 - 只有
level0是由immutable memtable直接dump的, 其他level都是由上一层levelcompaction得到的 manifest文件存储的是各sstabel在磁盘的位置以及最大最小key
Process
Write
write includes insert and delete
命令
Put
Concurent
对于多线程并发「写写」问题, leveldb的解决方案是采用一个queue和一个条件变量pthread_cond_t实现的
条件变量可参考这篇文章 juejin.cn/post/715843…
- 多线程写只有拿到锁
mutex_才能将w(k-v对)数据加到队列Writers_中 - 然后当前线程判断
w的标记位是否done或w是否在队首 - 如果都不是, 则调用
pthread_cond_wait阻塞线程并解锁mutex_ - 如果是在队首, 则从
Writers_中顺序读取多个w, 然后解锁mutex_ - 此时, 由于解锁了, 所以其他线程又可以继续
取锁并把写了(把w加到Writers_中) - 接着第
4步, 将多个w依次写入log和memtable(如果memtable达到阈值会在这里转成immutable memtable) - 然后再取锁
加锁, 从Writers_中依次取出这些w然后将其的标记位设置为done(对应第2步)并且对应的线程调用pthread_cond_signal条件变量通知(但是这些线程还是会阻塞, 因为mutex_还被这个线程占着) - 如果
Writers_中还有w则再调用一次pthread_cond_signal唤醒下一个队首 - 最后释放锁
mutex_
关于
pthread_cond_wait: 当收到信号后本质上会将线程从cond_wait队列移动到mutex_wait队列, 也就是上面提到的即使收到信号也要取到锁才能执行
Read
命令
Read
对于多线程并发「读读」问题, leveldb的解决方案是采用原子指针(底层用的是内存屏障)来保证的:
skip list插入数据简单来说分为两步:
- 新
node指向后一个node - 前一个
node指向这个新node
第1步不会影响到读, 第2步采用的是原子指针所以不会取到中间态(指向nil)
Memtable
memtable有这么几种数据结构:skiplist、hash-skiplist、hash-linklist
Log
Ref
- https://byte_dance.fei_shu.cn/wiki/wikcnIWmHoB5oRcn0lGyWLlQXAe
- https://tech.byte_dance.net/articles/6969885972225851428
- blog.csdn.net/weixin_4266…