RocksDB有哪些好的文章和资料(markDY)?

RocksDB有哪些好的文章和资料(markDY)? - 知乎 https://www.zhihu.com/question/270732348

一点感悟:

c/c++ 还是不可替换
c++ 11 反反复复在那里纠结内存要不要拷贝一次,还是直接赋值
“完全转发”啥的
真正操作计算机,还得 c/c++


LevelDB以下简称LD,RocksDB以下简称RD。

引用 的话:

N次阅读rocksdb和leveldb源码后,我对它们的简答粗暴概括理解如下:

跟leveldb学习LSM-Tree及C++(C98)实践
跟rocksdb学习存储引擎的实现

RocksDB的多线程流水线效果写入的逻辑组织,可以看 ——

张友东前辈的 RocksDB 写入流程详解 , 注意WAL处事多线程写入,且第一个线程所创建的Writer类会成为leader; memtable支持并发无锁写入。

LevelDB和RocksDB内存跳表数据结构和代码实现,可以看——

大神的  RocksDB——内联跳跃表

或 我的修订 - https://zhuanlan.zhihu.com/p/35191545

【转载并修改】RocksDB中的memtable之一——内联跳跃表(Inline Skip List)

郑重声明:转载自UncP 大神并修改

摘要:

LevelDB使用Skip List

RocksDB使用Inline Skip List

这篇文章介绍RocksDB中的memtable之一——内联跳跃表(Inline Skip List)。

RocksDB是Facebook基于LevelDB研发的键值存储引擎,它不但对LevelDB的不足之处进行了优化(比如多线程压缩**),而且引入了很多新特性(比如事务)。

如果你对LevelDB有所了解的话那应该对跳跃表不会陌生,跳跃表在LevelDB中用于存储内存数据,在LevelDB中被称作Memtable。

如果你并不熟悉跳跃表的话,这里有篇博客: Skip Lists: Done Right · Ticki's blog。这篇博客不仅介绍了跳跃表的工作原理而且介绍了一些优化策略。

我们首先介绍一下LevelDB和RocksDB中两种跳跃表的相同之处:

  • 都支持一写多读**  支持一写多读的原因是写都在跳表最下层进行,读在可以在上层进行吗? UncP 为什么艾特不到UncP大神
  • 对顺序插入进行了优化,LevelDB中维护了一组prev_指针,RocksDB中使用了Splice在每一层维护了一对指针

内联跳跃表是对于LevelDB中的跳跃表的优化,原理很简单:**

通过更紧凑的内存安排
1. 减少了内存的使用
2. 提供了更好的局部性

比较下两个跳跃表在节点所使用的数据结构差异。

class Node {  // LevelDB
  const Key          key_;
  std::atomic<Node*> next_[1];
}

class Node {  // RocksDB
  std::atomic<Node*>  next_[1];
}

你可能会有以下疑问:

  • 为什么指针只有一个,跳跃表的每个节点应该保存若干个节点指针来维护结构的完整性
  • 内联跳跃表中的key去哪了

这其实都和C/C++的语言特性有关,C/C++提供裸内存,所以你可以通过内存hack来对程序进行很多的优化,有些时候你看到的结构体的大小并不等于其真实大小,下面这张图可以帮助你很好理解这两个结构体的内存布局到底是怎样的。

在LevelDB中,key_可能是一个指针,但是在RocksDB中,key_存放实际值,它的内存在创建新节点时一起分配,因此节省了一个指针的内存(这么说不是特别准确,因为内联跳跃表的节点在分配时需要对齐,所以会浪费小于一个指针的内存,总的来说还是减少了内存的使用)。同时内联跳跃表将各层的next_指针移到了next_域的前面,并且通过next_[-n]这种方式来访问,这是一种C/C++中一种指针使用的小技巧。

其实内联跳跃表的数据结构还可以使用柔型数组,因为现在如果我们要访问key_,需要采用这种方法:

char *key = reinterpret_cast<char*>(&next_[1]);

改变后的结构体是这样的:

class Node {  // RocksDB
  std::atomic<Node*>  next_[1];
  char key_[0];
}

如果你仔细的话你会发现这两个结构体都没有height域,也就是说你无法知道next节点的数量,这是它们的另一个特点,那就是不完整,只有在具体调用的函数中你才能知道它们的next节点数量。其实这个特点也可以用来解释next_[-n]这样的存在,如果使用next_[n]的话我们需要height参数来知道具体的key_所在的位置。更过分的是RocksDB在new新节点时会把height写在next_[0]的前4个字节处,然后将这个节点插入的时候再读出来,这时候next_[0]又变成了一个指针。这种做法是不是很刺激?

以上就是RocksDB内联跳跃表在优化方面的介绍。感觉一般人这么写很容易被贴上“不规范编程”和“代码可读性差”的标签。源代码在RocksDB的memtable/skiplist.h和memtable/inlineskiplist.h这两个头文件中。

 

不要太刺激

 
   

LevelDB使用Skip List

RocksDB使用Inline Skip List

RocksDB是Facebook基于LevelDB研发的键值存储引擎,它不但对LevelDB的不足之处进行了优化(比如多线程压缩**),而且引入了很多新特性(比如事务)。

如果你对LevelDB有所了解的话那应该对跳跃表不会陌生,跳跃表在LevelDB中用于存储内存数据,在LevelDB中被称作Memtable。

LD不支持Transaction, but RD supports Transaction. You can read ——

我转载的

少看不良信息:【转载并修正补充】RocksDB事务实现TransactionDB分析​zhuanlan.zhihu.com

 一般情况下,数据库的隔离级别为 “一致性非锁定读(读可以并发)”。

RocksDB的一个事物操作,是通过事物内部申请一个WriteBatch实现的,所有commit之前的读都优先读该WriteBatch(保证了同一个事务内可以看到该事务之前的写操作),写都直接写入该事务独有的WriteBatch中,提交时在依次写入WAL和memtable,依赖WriteBatch的原子性和隔离性实现了ACID。

RocksDB的事务实现技术有 (1)、每个KV都有一个LogSequenceNumber;(2)snaphot,实际存储lsn; 

同一个WriteBatch中的lsn都相等吗?

注意独占写锁和写冲突。

注意TreadLocal的优化。

 
   
   

The basic knowledge of Transaction, you can read my article:

少看不良信息:数据库-理论基础- 事务 ACID 脏读 不可重复读 幻读 隔离级别​zhuanlan.zhihu.com

The key points include 

1、 WriteBatch is a transaction unit.

About RocksDB‘s Read Optimization,you can read ——

大神的【rocksdb源码分析】使用PinnableSlice减少Get时的内存拷贝 ,核心在于使用PinnableSlice* 替换std::string* 来减少一次内存拷贝 

  1. 数据交换:get_context->SaveValue()
    1. 老版本会在这里把Block中的数据拷贝到用户传进来的std::string*中
    2. 新版本则是直接将Block中的数据地址赋给用户传进来的PinnableSlice*中,也就是说用户最终拿到的值的地址其实就在这个Block中。

这样就减少了一次数据拷贝,不过有一个疑问:用户拿到的值就是Block中的值,而这个Block是缓存在block_cache中的,如果后来这个Block被淘汰,那岂不是用户拿到的值被清除了?如果用引用计数来避免这个问题,那么具体怎么做呢?这个问题就是本篇的重点。

 
   

如果定义这么一个基类Cleannable,它有如下特性:

  1. 可以通过RegisterCleanup方法来注册CleanupFunction,这个CleanupFunction用户自定义,一般是完成Cleannable对象申请的外部资源释放,例如释放某块之前申请的内存
  2. 当其对象被析构时,会依次调用所有之前注册的CleanupFunction,来完成外部资源释放
  3. 两个Cleannable子类对象A,B,可以通过A->DelegateCleanupsTo(B),将A注册的所有CleanupFunction委托给B,这样A在析构时就不会释放它申请的外部资源,而是等到B析构时才释放

有了这么一个(Cleanable)基类,如果将BlockIter和PinnableSlice都继承它,那么就可以BlockIter资源释放的任务委托给PinnableSlice,使得BlockIter内的资源生命周期延续至用户的PinnableSlice

 

注:这里为了简化问题,假设block_cache的Handle在Release时一定会释放其指向的Block,实际这里会有引用计数,直到为0是才会真正释放Block

 

Cleannable基类还是挺好玩的,适合需要延长某个对象内部资源生命周期的场景。


 
 

About LD’s and RocksDB‘s Write Optimization,you can read ——

【rocksdb源码分析】写优化之JoinBatchGrou,核心在于leveldb和rocksdb都支持多线程,不过对于Write是单写者的实现,这就需要使用类似队列的东西将上层多线程的多个Writer进行排列,每次只允许队列头的Writer写db,队头Writer写完后再唤醒队列其他的Writer。leveldb在这里有一个优化,就是队头Writer在写db时,并不是只将自己的WriteBatch写完就拉倒,而是在写之前先将自己和它之后正在等待的其他Writer的WriteBatch一起打包成一个更大的WriteBatch,然后一起再写,这样,当它写完db唤醒其他Writer后,有一部分Writer会发现自己的活已经被做完了,直接返回。这样的实现可以提高写入速度,算是一个不小的优化。当然rocksdb基于这之上还能做很多优化。

一个Data Corruption案例, you can read ——

ZoeyZhai:How we Hunted a Data Corruption bug in RocksDB​zhuanlan.zhihu.com

我感觉Data Corruption那部分挺有意思,挺有意义的,有兴趣,知友们可以自己搞个中文版的出来看看

 


TerarkDB = terark rocksdb + terark-zip-rocksdb

  • terark rocksdb 是我们修改版的 RocksDB,完全开源并且您可以自行编译
  • terark-zip-rocksdb 开源但您无法自行编译(依赖于个别私有库)

terark-zip-rocksdb 使用 Terark 特有技术实现了 RocksDB 的  MemTable 和 SSTable

TerarkZipTable 使用 Terark 的可检索压缩技术达到了以下效果:

  • 更高的随机读性能
  • 更高的压缩率 (磁盘文件更小)
  • 更低的内存使用 (对压缩文件使用 mmap, 没有双缓存问题)

我们修改了 原版的 RocksDB,称为 Terark RocksDB, 以方便更好的支持我们特有的 MemTable 和 SSTable(由 terark-zip-rocksdb 实现).

使用 TerarkDB 您现在可以在 磁盘/SSD 上存储更多的数据(比 snappy 算法多三倍以上),同时可以加载更多数据进入内存(10倍以上),显著地提升随机读性能。


原文地址:https://www.cnblogs.com/cx2016/p/12926160.html