本篇介紹典型的基於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...