本篇介绍典型的基于SStable的存储。适用于与SSD一块儿使用。更多存储相关见:https://segmentfault.com/a/11...。涉及到leveldb,rocksdb。基本上分布式都要单独作,重点是单机架构,数据写入,合并,ACID等功能和性能相关的。
先对性能有个直观认识:
mysql写入千条/s,读万应该没问题。redis 写入 万条/s 7M/s(k+v 700bytes,双核)读是写入的1.4倍 mem 3gb 2核。这两个网上搜的,不保证正确,就看个大概吧。
SSD上 rocksdb随机和顺序的性能差很少,写要比读性能稍好。随机读写1.7万条/s 14M/s (32核)。batch_write/read下SSD单线程会好8倍。普通write只快1.2倍。
没有再一个机器上的对比。rocksdb在用SSD和batch-write/read下的读写性能仍是能够的。mysql
架构图git
数据的读取是按照 MemTable、Immutable MemTable 以及不一样层级的 SSTable 的顺序进行的,前二者都是在内存中,后面不一样层级的 SSTable 都是以 *.ldb 文件的形式持久存储在磁盘上github
1.调用 MakeRoomForWrite 方法为即将进行的写入提供足够的空间;
在这个过程当中,因为 memtable 中空间的不足可能会冻结当前的 memtable,发生 Minor Compaction 并建立一个新的 MemTable 对象;不可变imm去minor C,新的memtable继续接收写
在某些条件知足时,也可能发生 Major Compaction,对数据库中的 SSTable 进行压缩;
2.经过 AddRecord 方法向日志中追加一条写操做的记录;
3.再向日志成功写入记录后,咱们使用 InsertInto 直接插入 memtable 中,内存格式为跳表,完成整个写操做的流程;redis
全局的sequence(memcache中记录,这里指的就是内存)。读写事务都申请writebatch,过程以下程序。
虽然是批量,可是仍然串行,是选择一个leader(cas+memory_order)将多个writebatch合并一块儿写入WAL,再依次写入memtable再提交。
每一批writebatch完成后才更新sequence算法
加锁,获取队列信息,释放锁,这次队列加入到weitebatch中处理,写日志成功后写入mem,此时其余线程能够继续加入队列,结束后加锁,更新seq,将处理过的队列移除。 Status DBImpl::Write(const WriteOptions &options, WriteBatch *my_batch){ Writer w(&mutex_); w.batch = my_batch; w.sync = options.sync; w.done = false; MutexLock l(&mutex_); writers_.push_back(&w); while (!w.done && &w != writers_.front()) { w.cv.Wait(); } if (w.done) { return w.status; } // May temporarily unlock and wait. Status status = MakeRoomForWrite(my_batch == nullptr); uint64_t last_sequence = versions_->LastSequence(); Writer *last_writer = &w; if (status.ok() && my_batch != nullptr) { // nullptr batch is for compactions WriteBatch *updates = BuildBatchGroup(&last_writer); WriteBatchInternal::SetSequence(updates, last_sequence + 1); last_sequence += WriteBatchInternal::Count(updates); // Add to log and apply to memtable. We can release the lock // during this phase since &w is currently responsible for logging // and protects against concurrent loggers and concurrent writes // into mem_. { mutex_.Unlock(); status = log_->AddRecord(WriteBatchInternal::Contents(updates)); bool sync_error = false; if (status.ok() && options.sync) { status = logfile_->Sync(); if (!status.ok()) { sync_error = true; } } if (status.ok()) { status = WriteBatchInternal::InsertInto(updates, mem_); } mutex_.Lock(); if (sync_error) { // The state of the log file is indeterminate: the log record we // just added may or may not show up when the DB is re-opened. // So we force the DB into a mode where all future writes fail. RecordBackgroundError(status); } } if (updates == tmp_batch_) tmp_batch_->Clear(); versions_->SetLastSequence(last_sequence); } while (true) { Writer* ready = writers_.front(); writers_.pop_front(); if (ready != &w) { ready->status = status; ready->done = true; ready->cv.Signal(); } if (ready == last_writer) break; } // Notify new head of write queue if (!writers_.empty()) { writers_.front()->cv.Signal(); } return status; }
频繁插入查询,没有删除。须要无写状态下的遍历(dump过程)=》跳表
默认4Msql
sstable(默认7个)【上层0,下层7】数据库
布隆过滤器过滤
.double-hashing
i从0-k, gi(x) = h1(x) + ih2(x) + i^2 mod m,Prefix
的特色来减小存储数据量,减小了数据存储,但同时也引入一个风险,若是最开头的Entry数据损坏,其后的全部Entry都将没法恢复。为了下降这个风险,leveldb引入了重启点
,每隔固定条数Entry会强制加入一个重启点,这个位置的Entry会完整的记录本身的Key,并将其shared值设置为0。同时,Block会将这些重启点的偏移量及个数记录在全部Entry后边的Tailer中。32K。内存写入完成时,直接将缓冲区fflush到磁盘
日志的类型 first full, middle,last 若发现损坏的块直接跳过直到下一个first或者full
(不须要修复).重作时日志部份内容会嵌入到另外一个日志文件中segmentfault
记录
keysize | key | sequnce_number | type |value_size |value
type为插入或删除。排序按照key+sequence_number做为新的key数组
记录LogNumber,Sequence,下一个SST文件编号等状态信息;
维护SST文件索引信息及层次信息,为整个LevelDB的读、写、Compaction提供数据结构支持;
记录Compaction相关信息,使得Compaction过程能在须要的时候被触发;配置大小
以版本的方式维护元信息,使得Leveldb内部或外部用户能够以快照的方式使用文件和数据。
负责元信息数据的持久化,使得整个库能够从进程重启或机器宕机中恢复到正确的状态;
versionset链表
每一个version引用的file(指向filemetadata的二维指针(每层包含哪些file)),如LogNumber,Sequence,下一个SST文件编号的状态信息安全
每一个version之间的差别versionedit。每次计算versionedit,落盘Manifest文件(会存version0和每次变动),用versionedit构建新的version。manifest文件会有多个,current文件记录当前manifest文件,使启动变快
Manifest文件是versionset的物理结构。中记录SST文件在不一样Level的分布,单个SST文件的最大最小key,以及其余一些LevelDB须要的元信息。
每当调用LogAndApply(compact)的时候,都会将VersionEdit做为一笔记录,追加写入到MANIFEST文件。而且生成新version加入到版本链表。
MANIFEST文件和LOG文件同样,只要DB不关闭,这个文件一直在增加。
早期的版本是没有意义的,咱们不必还原全部的版本的状况,咱们只须要还原还活着的版本的信息。MANIFEST只有一个机会变小,抛弃早期过期的VersionEdit,给当前的VersionSet来个快照,而后重新的起点开始累加VerisonEdit。这个机会就是从新开启DB。
LevelDB的早期,只要Open DB必然会从新生成MANIFEST,哪怕MANIFEST文件大小比较小,这会给打开DB带来较大的延迟。后面判断小的manifest继续沿用。
若是不延用老的MANIFEST文件,会生成一个空的MANIFEST文件,同时调用WriteSnapShot将当前版本状况做为起点记录到MANIFEST文件。
dB打开的恢复用MANIFEST生成全部LIVE-version和当前version
google的bigtable是chubby(分布式锁)+单机lebeldb
https://github.com/facebook/r...
range
merge(就是为了add这种多个rocksdb操做)
工具解析sst
压缩算法除了level的snappy还有zlib,bzip2(同时支持多文件)
支持增量备份和全量备份
支持单进程中启动多个实例
能够有多个memtable,解决put和compact的速度差别瓶颈。数据结构:跳表(只有这个支持并发)\hash+skiplist\hash+list等结构
这里讲了memtable并发写入的过程,利用了InlineSkipList,它是支持多读多写的,节点插入的时候会使用 每层CAS 判断节点的 next域是否发生了改变,这个 CAS 操做使用默认的memory_order_seq_cst。 http://mysql.taobao.org/monthly/2017/07/05/ 源码分析 https://youjiali1995.github.io/rocksdb/inlineskiplist/
通用合并(有时亦称做tiered)与leveled合并(rocksdb的默认方式)。它们的最主要区别在于频度,后者会更积极的合并小的sorted run到大的,而前者更倾向于等到二者大小至关后再合并。遵循的一个规则是“合并结果放到可能最高的level”。是否触发合并是依据设置的空间比例参数。
size amplification ratio = (size(R1) + size(R2) + ... size(Rn-1)) / size(Rn)
低写入放大(合并次数少),高读放个大(扫描文件多),高临时空间占用(合并文件多)
RocksDB典型的作法是Level 0-2不压缩,最后一层使用zlib(慢,压缩比很高),而其它各层采用snappy
备份
相关接口:CreateNewBackup(增量),GetBackupInfo获取备份ID,VerifyBackup(ID),恢复:BackupEngineReadOnly::RestoreDBFromBackup(备份ID,目标数据库,目标位置)。备份引擎open时会扫描全部备份耗时间,常开启或删除文件。
步骤:
禁用文件删除 获取实时文件(包括表文件,当前,选项和清单文件)。 将实时文件复制到备份目录。因为表文件是不可变的而且文件名是惟一的,所以咱们不会复制备份目录中已存在的表文件。例如,若是00050.sst已备份并GetLiveFiles()返回文件00050.sst,则不会将该文件复制到备份目录。可是,不管是否须要复制文件,都会计算全部文件的校验和。若是文件已经存在,则将计算的校验和与先前计算的校验和进行比较,以确保备份之间没有发生任何疯狂。若是检测到不匹配,则停止备份并将系统恢复到以前的状态BackupEngine::CreateNewBackup()叫作。须要注意的一点是,备份停止可能意味着来自备份目录中的文件或当前数据库中相应的实时文件的损坏。选项,清单和当前文件始终复制到专用目录,由于它们不是不可变的。 若是flush_before_backup设置为false,咱们还须要将日志文件复制到备份目录。咱们GetSortedWalFiles()将全部实时文件调用并复制到备份目录。 从新启用文件删除
第一个图中的置换LRU,CLOCK。CLOCK介于FIFO和LRU之间,首次装入主存时和随后再被访问到时,该帧的使用位设置为1。循环,每当遇到一个使用位为1的帧时,操做系统就将该位从新置为0,遇到第一个0替换。concurrent_hash_map是CAS等并发安全
更多:
SST大时顶级索引:https://github.com/facebook/r...
两阶段提交:https://github.com/facebook/r...