东莞网站优化是什么,有关网站升级建设的申请书,舆情app,wordpress 登录文章目录 简介编译安装RocksDB压缩库UbuntuCentos 基本接口高度分层架构LSM-Tree关于访问速度 MemTable落盘策略 WALRocksDB 中的每个更新操作都会写到两个地方#xff1a;WAL 创建时机#xff1a;重要参数 Immutable MemTableSSTBlockCacheLRU 缓存Clock缓存 写入流程读取流… 文章目录 简介编译安装RocksDB压缩库UbuntuCentos 基本接口高度分层架构LSM-Tree关于访问速度 MemTable落盘策略 WALRocksDB 中的每个更新操作都会写到两个地方WAL 创建时机重要参数 Immutable MemTableSSTBlockCacheLRU 缓存Clock缓存 写入流程读取流程LSM-Tree 三大问题读放大空间放大写放大 列族column family事务悲观事务TransactionDB乐观事务OptimisticTransactionDB 简介
RocksDB 是 Facebook 的一个实验项目目的是希望能开发一套能在服务器压力下真正发挥高速存储硬件性能的高效数据库系统。这是一个 C 库允许存储任意长度二进制 KV 数据。支持原子读写操作。RocksDB 依靠大量灵活的配置使之能针对不同的生产环境进行调优包括直接使用内存使用 Flash使用硬盘或者HDFS。支持使用不同的压缩算法并且有一套完整的工具供生产和调试使用。RocksDB 大量复用了 levedb 的代码并且还借鉴了许多HBase 的设计理念。原始代码从 leveldb 1.5 上fork 出来。同时RocksDB 也借用了一些 Facebook 之前就有的理念和代码。RocksDB 应用场景非常广泛比如支持 redis 协议的 pika 数据库采用 RocksDB 持久化 Redis 支持的数据结构MySQL 中支持可插拔的存储引擎Facebook 维护的 MySQL 分支中支持RocksDB
编译安装RocksDB
git clone https://github.com/facebook/rocksdb.git
cd rocksdb
# 编译成调试模式
make
# 编译成发布模式
make static_lib
############################ 使用 cmake
######################
mkdir build
cd build
cmake ..压缩库
Ubuntu
# rocksdb 支持多种压缩模式
# gflags
sudo apt-get install libgflags-dev
# snappy
sudo apt-get install libsnappy-dev
# zlib
sudo apt-get install zlib1g-dev
# bzip2
sudo apt-get install libbz2-dev
# lz4
sudo apt-get install liblz4-dev
# zstandard
sudo apt-get install libzstd-devCentos
# gflags
git clone https://github.com/gflags/gflags.git
cd gflags
git checkout v2.0
./configure make sudo make install
# snappy
sudo yum install snappy snappy-devel
# zlib
sudo yum install zlib zlib-devel
# bzip2
sudo yum install bzip2 bzip2-devel
# lz4
sudo yum install lz4-devel
# ASAN (optional for debugging)
sudo yum install libasan
# zstandard
sudo yum install libzstd-devel基本接口
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)高度分层架构
RocksDB 是一种可以存储任意二进制KV数据的嵌入式存储。RocksDB 按顺序组织所有数据他们的通用操作是 Get(key) , NewIterator() , Put(key, value) , Delete(Key) 以及SingleDelete(key)。 RocksDB 有三种基本的数据结构memtablesstfile 以及logfile。memtable 是一种内存数据结构——所有写入请求都会进入 memtable然后选择性进入 logfile。logfile 是一个在存储上顺序写入的文件。当 memtable 被填满的时候他会被刷到 sstfile 文件并存储起来然后相关的 logfile 会在之后被安全地删除。sstfile 内的数据都是排序好的以便于根据 key 快速搜索。RocksDB 是基于 LSM-Tree (log structured merge - tree) 实现
LSM-Tree
LSM-Tree 的核心思想是利用顺序写来提升写性能LSM-Tree不是一种树状数据结构仅仅是一种存储结构**LSM-Tree 是为了写密集型的特定场景而提出的解决方案**如日志系统、海量数据存储、数据分析LO 层数据重复文件间无序文件内部有序L1 ~ LN 每层数据没有重复跨层可能有重复文件间是有序 的
关于访问速度
磁盘访问时间寻道时间 旋转时间 传输时间 寻道时间8ms~12ms 旋转时间7200转/min半周 4ms 传输时间50M/s约0.3ms磁盘随机 IO 磁盘顺序 IO 内存随机 IO 内存顺序 IO
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 数
落盘策略
Memtable 的大小在一次写入后超过 write_buffer_size。所有列族中的 memtable 大小超过 db_write_buffer_size了或者 write_buffer_manager 要求落盘。在这种场景最大的 memtable 会被落盘WAL 文件的总大小超过 max_total_wal_size。在这个场景有着最老数据的 memtable 会被落盘这样才允许携带有跟这个 memtable 相关数据的 WAL 文件被删除。
WAL
RocksDB 中的每个更新操作都会写到两个地方
一个内存数据结构名为 memtable (后面会被刷盘到SST文件)写到磁盘上的 WAL 日志。在出现崩溃的时候WAL 日志可以用于完整的恢复 memtable中的数据以保证数据库能恢复到原来的状态。在默认配置的 情况下RocksDB 通过在每次写操作后对 WAL 调用 fflush来保证一致性。
WAL 创建时机
db打开的时候一个列族落盘数据的时候新的创建、旧的延迟删除
重要参数
DBOptions::max_total_wal_size 如果希望限制 WAL 的大小RocksDB 使用 DBOptions::max_total_wal_size 作为列族落盘的触发器。一旦 WAL 超过这个大小RocksDB 会开始强制列族落盘以保证删除最老的 WAL 文件。这个配置在列族以不固定频率更新的时候非常有用。如果没有大小限制如果这个WAL中有一些非常低频更新的列族的数据没有落盘用户可能会需要保存非常老的WAL 文件。DBOptions::WAL_ttl_seconds , DBOptions::WAL_size_limit_MB这两个选项影响 WAL 文件删除的时间。非0参数表示时间和硬盘空间的阈值超过这个阀值会触发删除归档的WAL文件。
Immutable MemTable
Immutable MemTable 也是存储在内存中的数据不过是**只读**的内存数据当 MemTable 写满后会被置为只读状态变成 ImmutableMemTable。然后会创建一块新的 MemTable 来提供写入操作Immutable MemTable 将异步flush 到 SST 中
SST
SST Sorted String Table 有序键值对集合是 LSM-Tree 在磁盘中的数据结构可以通过建立 key 的索引以及布隆过滤器来加快 key 的查询LSM-Tree 会将所有的 DML 操作记录保存在内存中继而批量顺序写到磁盘中这与 B Tree 有很大不同B Tree 的数据更新直接需要找到原数据所在页并修改对应值而 LSM-Tree 是直接 append 的方式写到磁盘虽然后面会通过合并的方式去除冗余无效的数据
BlockCache
块缓存是 RocksDB 在内存中缓存数据以用于读取的地方。用户可以带上一个期望的空间大小传一个 Cache 对象给 RocksDB实例。一个缓存对象可以在同一个进程的多个 RocksDB 实例之间共享这允许用户控制总的缓存大小。块缓存存储未压缩过的块。用户也可以选择设置另一个块缓存用来存储压缩后的块。读取的时候会先拉去未压缩的数据块的缓存然后才拉取压缩数据块的缓存。在打开直接 IO 的时候压缩块缓存可以替代OS 的页缓存。RocksDB 里面有两种实现方式分别叫做 LRUCache 和 ClockCache。两个类型的缓存都通过分片来减轻锁冲突。容量会被平均的分配到每个分片分片间不共享空间。默认情况下每个缓存会被分片到 64 个分片每个分片至少有 512 B 空间。用户可以选择将索引和过滤块缓存在 BlockCache 中默认情况下索引和过滤块都在 BlockCache 外面存储
LRU 缓存
默认情况下RocksDB 会使用 LRU 块缓存实现空间为8MB。每个缓存分片都维护自己的 LRU 列表以及自己的查找哈希表。通过每个分片持有一个互斥锁来实现并发。不管是查找还是插入都需要申请该分片的互斥锁。用户可以通过调用NewLRUCache 创建一个 LRU 缓存
Clock缓存
ClockCache 实现了CLOCK算法。每个clock缓存分片都维护一个缓存项的环形列表。一个clock指针遍历这个环形列表来找一个没有固定(unpinned block)的项进行驱逐同时如果在上一个扫描中他被使用过了那么给予这个项两次机会来留在缓存里。tbb::concurrent_hash_map 被用来查找数据。与 LRU 缓存比较clock 缓存有更好的锁粒度。在 LRU 缓存下面每个分片的互斥锁在读取的时候都需要上锁因为他需要更新他的 LRU 列表。在一个 clock 缓存上查找数据不需要申请该分片的互斥锁只需要搜索并行的哈希表就行了所以有更好锁粒度。只有在插入的时候需要每个分片的锁。用 clock 缓存在一定环境下我们能看到读性能的增长
写入流程 写入位于磁盘中的 WALWrite Ahead Log里。 写入memtable。 当大小达到一定阈值后原有的memtable冻结变成immutable。后续的写入交接给新的memtable和WAL。 后台开启 Compaction 线程开始将immutable落库变成一个L0 层的SSTable写入成功后释放掉以前的 WAL。 若插入新的SSTable后当前层Li的总文件大小超出了阈值会从Li中挑选出一个文件和Li1层的重叠文件继续合并直到所有层的大小都小于阈值。合并过程中会保证L1以后各SSTable的Key不重叠。
读取流程 FindFiles。从SST文件中查找如果在 L0那么每个文件都得读因为 L0 不保证Key不重叠如果在更深的层那么Key保证不重叠每层只需要读一个 SST 文件即可。L1 开始每层可以在内存中维护一个 SST的有序区间索引在索引上二分查找即可 LoadIB FB。IB 和 FB 分别是 index block 和 filterblock 的缩写。index block是SST内部划分出的block的索引filter block 则是一个布隆过滤器Bloom Filter可以快速排除 Key 不在的情况因此首先加载这两个结构 SearchIB。二分查找 index block找到对应的block SearchFB。用布隆过滤器过滤如果没有则返回 LoadDB。则把这个block加载到内存 SearchDB。在这个block中继续二分查找 ReadValue。找到 Key后读数据如果考虑 WiscKey KV分离的情况还需要去 vLog 中读取
LSM-Tree 三大问题
读放大
用来描述数据库必须物理读取的字节数相较于返回的字节数之比。和数据分层存放读取操作也需要分层依次查找直到找到对应数据这个过程可能涉及多次 IO在 range query 的时候影响更大
空间放大
用来描述磁盘上存储的数据字节数相较于数据库包含的逻辑字节数之比所有的写入操作都是顺序写而不是就地更新无效数据不会马上被清理掉
写放大
用来描述实际写入磁盘的数据大小和程序要求写入的数据大小之比为了减小读放大和空间放大RocksDB 采用后台线程合并数据的方式来解决这些合并过程中会造成对同一条数据多次写入磁盘
列族column family
RocksDB 的每个键值对都与唯一一个列族column family结合。如果没有指定 Column Family键值对将会结合到“default” 列族。 列族提供了一种从逻辑上给数据库分片的方法。他的一些有趣的特性包括
支持跨列族原子写。意味着你可以原子执行Write({cf1,key1,value1},{cf2,key2,value2}) 。跨列族的一致性视图。允许对不同的列族进行不同的配置即时添加删除列族。两个操作都是非常快的。
实现
列族的主要实现思想是他们共享一个 WAL 日志但是不共享memtable 和 table 文件。通过共享 WAL 文件实现了原子写。通过隔离 memtable 和 table 文件我们可以独立配置每个列族并且快速删除它们。每当一个单独的列族刷盘我们创建一个新的 WAL 文件。所有列族的所有新的写入都会去到新的 WAL 文件。但是我们还不能删除旧的 WAL因为他还有一些对其他列族有用的数据。我们只能在所有的列族都把这个 WAL 里的数据刷盘了才能删除这个 WAL 文件。这带来了一些有趣的实现细节以及一些有趣的调优需求。确保你的所有列族都会有规律地刷盘。另外看一下Options::max_total_wal_size通过配置他过期的列族能自动被刷盘。
事务
当使用 TransactionDB 或者 OptimisticTransactionDB 的时候RocksDB 将支持事务。事务带有简单的BEGIN/COMMIT/ROLLBACK API并且允许应用并发地修改数据具体的冲突检查由 RocksDB 来处理。RocksDB 支持悲观 和乐观的并发控制。 注意当通过 WriteBatch 写入多个 key 的时候RocksDB 提供原子化操作。事务提供了一个方法来保证他们只会在没有冲突的时候被提交。与WriteBatch 类似只有当一个事务交其他线程才能看到被修改的内容读 committed。
悲观事务TransactionDB
当使用 TransactionDB 的时候所有正在修改的 RocksDB 里的 key 都会被上锁让 RocksDB 执行冲突检测。如果一个 key发生锁冲突操作会返回一个错误。当事务被提交数据库保证这个事务是可以写入的。一个 TransactionDB 在有大量并发工作压力的时候相比OptimisticTransactionDB 有更好的表现。然而由于非常过激的上锁策略使用 TransactionDB 会有一定的性能损耗。TransactionDB 会在所有写操作的时候做冲突检查包括不使用事务写入的时候。
乐观事务OptimisticTransactionDB
乐观事务提供轻量级的乐观并发控制用来给那些多个事务间不会有高的竞争或者干涉的工作场景。乐观事务在预备写的时候不使用任何锁。作为替代他们把这个操作推迟到在提交的时候检查是否有其他人修改了正在进行的事务。如果和另一个写入有冲突或者他无法做决定提交会返回错误并且没有任何 key 都不会被写入。乐观的并发控制在处理那些偶尔出现的写冲突非常有效。然而对于那些大量事务对同一个 key 写入导致写冲突频繁发生的场景却不是一个好主意。对于这些场景使用TransactionDB 是更好的选择。OptimisticTransactionDB 在大量非事务写入而少量事务写入的场景会比 TransactionDB性能更好。