马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
目录
一:RocksDB的简介
二:编译和压缩库
三:基本接口
四:LSM-Tree
五:高度分层结构
Memtable
Immutable MemTable
SST
落盘操作
WAL
BlockCache 块缓存
LRU 缓存
六:写入和读取流程
1:写入流程
2:读取流程
七:LSM-Tree 三大问题
读放大
空间放大
写放大
RocksDB compaction
一:RocksDB的简介
在我们之前使用过的数据库中,我们都发现了,读取速率可以变快,但是对于写入速率,不停是一个问题,当大量写入进来的时候,我们写入的速率会变得很慢,因此FaceBook开发了一套可以发挥高速存储硬件性能的高效数据库系统。是一个C++的库,而且可以恣意存储二进制的KV数据,而且支持原子读的操作。
他的应用场景还是很广泛的:支持Redis协议的pika数据库,采用RocksDB持久化Redis支持的数据结构。尚有MySQL中支持可插拔的存储引擎。
二:编译和压缩库
- git clone https://github.com/facebook/rocksdb.git
- cd rocksdb
- # 编译成调试模式
- make
- # 编译成发布模式
- make static_lib
- ############################ 使用 cmake
- ######################
- mkdir build
- cd build
- cmake ..
- # zlib
- sudo apt-get install zlib1g-dev
复制代码 三:基本接口
虽然这个数据库的接口很少,但是并不代表这个数据库简单,这个数据库的每个接口是需要包罗一些我们自定义的参数的,因此难的地方是在调参的过程。
- Status Open(const Options& options, const std::string& dbname, DB** dbptr);
- Status Get(const ReadOptions& options, const Slice& key, std::string* value);
- Status Get(const ReadOptions& options,ColumnFamilyHandle* column_family, const Slice& key,std::string* value);
- Status Put(const WriteOptions& options, const Slice& key, const Slice& value);
- Status Put(const WriteOptions& options,ColumnFamilyHandle* column_family, const Slice& key,const Slice& value);
- // fix read-modify-write 将 读取、修改、写入封装到一个接口
- Status Merge(const WriteOptions& options, const Slice& key, const Slice& value);
- Status Merge(const WriteOptions& options,ColumnFamilyHandle* column_family, const Slice& key,const Slice& value);
- // 标记删除,具体在 compaction 中删除
- Status Delete(const WriteOptions& options, const Slice& key);
- Status Delete(const WriteOptions& options,ColumnFamilyHandle* column_family, const Slice& key,const Slice& ts);
- // 针对从来不修改且已经存在的key; 这种情况比 delete 删除的快;
- Status SingleDelete(const WriteOptions& options, const Slice& key);
- Status SingleDelete(const WriteOptions& options,ColumnFamilyHandle* column_family,const Slice& key);
- // 迭代器会阻止 compaction 清除数据,使用完后需要释放;
- Iterator* NewIterator(const ReadOptions& options);
- Iterator* NewIterator(const ReadOptions& options,ColumnFamilyHandle* column_family)
复制代码 四:LSM-Tree
RocksDB采用的数据结构是LSM-Tree。我们可以回顾一下MySQL采用的结构是B+数结构,多路平衡搜索树。在Redis中我们采用的是hashtable。
LSM-Tree的焦点思想是利用顺序写来提升性能,他并不是一种树状数据结构,仅仅是一种存储结构,是为了写麋集型的特定场景而提出的解决方案。
大概讲一下下面这张图的各个部件:首先有两个大的部件,一个是Memory一个Disk,也就是一个是内存一个是磁盘。我们客户端向RocksDB数据库写入数据的时候,会写入到内存数据库中的MemTable表中,我们在内存中可以含有多个MemTable表。当我们一个MemTable表写满的时候,我们就讲这个表刷入到Disk磁盘中去。我们在磁盘中有一个叫WAL的东西,它只有一个。它是用来监督内存中的MemTable的,确保它写入的数据完整。
当写入的MemTable刷入到磁盘中去的时候,我们在磁盘中含有七层的结构。我们将上面的数据刷下来的时候,是刷入到Level 0 层,每写完一个MemTable的时候,就将数据刷下来。这个时候会出现一种环境,我们分两次写的数据,写到了不同的MemTable表中去了,那不就造成数据冗余了吗:好比第一个表写king,第二个表写Mark,这两个数据都是同一个Key,写入到了两个不同的表中去,那不就数据冗余了吗,确实是冗余了,但是我们在刷到第一层后,当第一层的表也满了,我们会将这两个表举行重合操作,将冗余的操作举行重合,并放入到下一层。这时候我们发现只有第0层的数据是有冗余环境的,但是对于其他层是没有这个环境的。
五:高度分层结构
RocksDB是一个可以存储恣意二进制KV数据的嵌入式存储,他按着顺序构造所有数据。RocksDB有三种基本的数据结构:memtable,sstfile和logfile。
memtable:是一种内存数据结构,所有写入的请求都会写入到memtable,然后选择性的进入logfile。
logfile:是一个在存储上顺序写入的文件。当memtable被填满的时候,它会被刷到sstfile文件并存储起来,然后相干的logfile会在之后被安全的删除。
sstfile:它内部的数据都是排序好的,以便于根据key快速搜索。
Memtable
MemTable 是一个内存数据结构,他保存了落盘到 SST 文件前 的数据。他同时服务于读和写——新的写入总是将数据插入到 memtable,读取在查询 SST 文件前总是要查询 memtable,由于 memtable 里面的数据总是更新的。一旦一个 memtable 被 写满,他会变成不可修改的,并被一个新的 memtable 更换。 一个后台线程会把这个 memtable 的内容落盘到一个 SST 文 件,然后这个 memtable 就可以被烧毁了。 默认的 memtable 实现是基于 skiplist 的。 影响 memtable 大小的选项:
write_buffer_size: 一个 memtable 的大小;
db_write_buffer_size: 多个列族的 memtable 的大小总和;这可以用来管理 memtable 使用的总内存数;
max_write_buffer_number: 内存中可以拥有刷盘到 SST 文件 前的最大 memtable 数;
Immutable MemTable
Immutable MemTable 也是存储在内存中的数据,不外是只读的内存数据; 当 MemTable 写满后,会被置为只读状态,变成 Immutable MemTable。
然后会创建一块新的 MemTable 来提供写入操 作;Immutable MemTable 将异步 flush 到 SST 中;
SST
SST (Sorted String Table) 有序键值对集合,是 LSM-Tree 在 磁盘中的数据结构;
可以通过创建 key 的索引以及布隆过滤器来加快 key 的查询;
LSM-Tree 会将所有的 DML 操作记载保存在内存中,继而批量顺序写到磁盘中;
这与 B+ Tree 有很大不 同,B+ Tree 的数据更新直接需要找到原数据地点页并修改对应值;
而 SM-Tree 是直接 append 的方式写到磁盘;
虽然后面会通过归并的方式去除冗余无效的数据;
留意:布隆过滤器一定不会错过数据,但是偶然候会误查找到数据,但是这个概率很低。
落盘操作
Memtable 的大小在一次写入后凌驾 write_buffer_size。
所有列族中的 memtable 大小凌驾 db_write_buffer_size 了,或者 write_buffer_manager 要求落盘。在这种场景,最 大的 memtable 会被落盘;
WAL 文件的总大小凌驾 max_total_wal_size。在这个场景, 有着最老数据的 memtable 会被落盘,这样才答应携带有跟这 个 memtable 相干数据的 WAL 文件被删除。
WAL
RocksDB 中的每个更新操作都会写到两个地方:
1. 一个内存数据结构,名为 memtable (后面会被刷盘到SST 文件)
2. 写到磁盘上的 WAL 日志。 在出现崩溃的时候,WAL 日志可以用于完整的规复 memtable 中的数据,以保证数据库能规复到原来的状态。
在默认配置的环境下,RocksDB 通过在每次写操作后对 WAL 调用 fflush来保 证一致性。 WAL 创建时机: 1. db打开的时候;2. 一个列族落盘数据的时候;(新的创建、旧的延迟删除)
BlockCache 块缓存
是 RocksDB 在内存中缓存数据以用于读取的地方。用户可以带上一个盼望的空间大小,传一个 Cache 对象给 RocksDB 实例。一个缓存对象可以在同一个历程的多个 RocksDB 实例之间共享,这答应用户控制总的缓存大小块缓存存储未压缩过的块。用户也可以选择设置另一个块缓存,用来存储压缩后的 块。
读取的时候会先拉去未压缩的数据块的缓存,然后才拉取压缩数据块的缓存。在打开直接 IO 的时候压缩块缓存可以替代 OS 的页缓存。 RocksDB 里面有两种实现方式,分别叫做 LRUCache 和 ClockCache。两个范例的缓存都通过分片来减轻锁冲突。容量 会被均匀的分配到每个分片,分片之间不共享空间。默认环境 下,每个缓存会被分片到 64 个分片,每个分片至少有 512 B 空间。 用户可以选择将索引和过滤块缓存在 BlockCache 中;默认环境 下,索引和过滤块都在 BlockCache 外面存储;
LRU 缓存
默认环境下,RocksDB 会使用 LRU 块缓存实现,空间为 8MB。每个缓存分片都维护自己的 LRU 列表以及自己的查找哈 希表。通过每个分片持有一个互斥锁来实现并发。不管是查找 还是插入,都需要申请该分片的互斥锁。用户可以通过调用 NewLRUCache 创建一个 LRU 缓存;
六:写入和读取流程
1:写入流程
1. 写入位于磁盘中的 WAL(Write Ahead Log)里。
2. 写入 memtable。
3. 当大小达到一定阈值后,原有的 memtable冻结变成 immutable。后续的写入交接给新的。
4. 后台开启 Compaction 线程,开始将 memtable和WAL。 immutable落库变成一个 L0 层的 SSTable,写入成功后开释掉以前的 WAL。
5. 若插入新的 SSTable后,当前层( Li)的总文件大小超出了阈 值,会从Li中挑选出一个文件,和 Li+1层的重叠文件继续合 并,直到所有层的大小都小于阈值。归并过程中,会保证L1以 后,各 SSTable的Key不重叠。
2:读取流程
1. FindFiles。从SST文件中查找,假如在 L0,那么每个文件都得读,由于 L0 不保证Key不重叠;假如在更深的层,那么Key保 证不重叠,每层只需要读一个 SST 文件即可。L1 开始,每层可以在内存中维护一个 SST的有序区间索引,在索引上二分查找即 可;
2. LoadIB + FB。IB 和 FB 分别是 block 的缩写。 index block 和 filter index block是SST内部划分出的block的索 引; filter block 则是一个布隆过滤器(Bloom Filter),可 以快速排除 Key 不在的环境,因此首先加载这两个结构;
3. SearchIB。二分查找 index block,找到对应的 block;
4. SearchFB。用布隆过滤器过滤,假如没有,则返回;
5. LoadDB。则把这个 block加载到内存;
6. SearchDB。在这个 block中继续二分查找;
7. ReadValue。找到 Key后读数据,假如考虑 WiscKey KV分离的 环境,还需要去 vLog 中读取;
七:LSM-Tree 三大问题
读放大
用来形貌数据库必须物理读取的字节数相较于返回的字节数之 比。和数据分层存放,读取操作也需要分层依次查找,直到找 到对应数据;这个过程可能涉及多次 IO;在 range query 的时 候,影响更大;
空间放大
用来形貌磁盘上存储的数据字节数相较于数据库包罗的逻辑字 节数之比;所有的写入操作都是顺序写,而不是就地更新,无 效数据不会马上被清理掉;
写放大
用来形貌实际写入磁盘的数据大小和步伐要求写入的数据大小 之比;为了减小读放大和空间放大,RocksDB 采用后台线程合 并数据的方式来解决;这些归并过程中会造成对同一条数据多 次写入磁盘;
RocksDB compaction
为了在读放大、空间放大、以及写放大之间举行取舍,以此适 应不同的业务场景;所以需要选择不同的归并算法;rocksdb 默认采用 leveled compaction(leveled & tiered);
https://github.com/0voice
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |