LevelDb

LevelDb 是 Google 開源的持久化 KV 單機存儲引擎。數據庫

針對存儲面對的廣泛隨機 IO 問題,leveldb 採用了 merge-dump 的方式,將邏輯場景的寫請求轉換成順序寫log 和寫 memtable 操做,由後臺進程將 memtable 持久化成 sstable。數組

對於讀請求,隨機 IO 仍是沒法避免,但它設計了一系列策略來保證讀的效率。緩存

1. 特色

  1. 鍵和值都是任意的字節數組
  2. 數據根據鍵排序,排序規則可重載
  3. 有三種基本的操做:Put(key,value), Get(key), Delete(key)
  4. 支持多個操做組合的原子操做
  5. 用戶能夠建立臨時快照,獲得一個一致的數據視圖
  6. 支持向前和向後的數據迭代
  7. 數據用Snappy壓縮庫自動壓縮
  8. 外部操做(如文件系統操做等)經過一個虛擬接口使用,用戶能夠對操做系統進行定製相應操做

2. 侷限性

  1. 這不是一個SQL數據庫。它不具備關係數據模型,它不支持SQL查詢,也不支持索引
  2. 在同一時間只有一個進程(能夠是多線程)能夠訪問一個數據庫
  3. 有沒有客戶端-服務器模型的支持

3. 性能

如下是谷歌官方提供的性能測試報告:安全

3.1 測試環境

LevelDB: version 1.1
日期: Sun May 1 12:11:26 2011
CPU: 4 x Intel(R) Core(TM)2 Quad CPU Q6600@2.40GHz
CPUCache: 4096 KB
鍵: 每一個16 bytes 
值: 每一個100 bytes(壓縮後50 bytes) 元組數目: 1000000
內存大小: 110.6 MB (估計)
文件大小: 62.9 MB (估計)服務器

3.2 寫的性能

順序添加 : 1.765 micros/op; 62.7 MB/s 
同步添加 : 268.409 micros/op; 0.4 MB/s (10000 ops)
隨機添加 : 2.460 micros/op; 45.0 MB/s 
數據重寫 : 2.380 micros/op; 46.5 MB/s 多線程

隨機寫入的速度能夠達到每秒40萬條併發

3.3 讀的性能

隨機讀取 : 16.677 micros/op; (大約每秒6萬條)
順序讀取 : 0.476 micros/op; 232.3 MB/s 
逆序讀取 : 0.724 micros/op; 152.9 MB/s app

若是增長數據壓縮後,性能有所提升異步

隨機讀取 : 11.602 micros/op; (大約每秒8.5萬條)
順序讀取 : 0.423 micros/op; 232.3 MB/s 
逆序讀取 : 0.663 micros/op; 152.9 MB/s 函數

若是提供足夠的緩存性能還會有所提高

隨機讀取 : 9.775 micros/op; (不使用壓縮大約每秒10萬條)
隨機讀取 : 5.215 micros/op; (使用壓縮大約每秒19萬條)

順序讀的性能尤其突出,在每秒230萬條以上 

4. 使用方法

 

4.1 打開一個數據庫

一個leveldb數據庫有一個名字對應的文件系統目錄,全部數據庫的內容存儲在此目錄中:

1 #include <assert>
2 #include "leveldb/db.h"
3 
4 leveldb::DB* db;
5 leveldb::Options options;
6 options.create_if_missing = true;
7 leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);
8 assert(status.ok());

  若是你想在數據庫已經存在的狀況下報錯,那須要設置options以下:

1 options.error_if_exists = true;

4.2 狀態

  也許您已經到leveldb::Status,這類型能夠獲得leveldb的大部分功能結果,您能夠檢查結果是不是ok的,還能夠打印相關的錯誤消息:

1 leveldb::Status s = ...;
2 if (!s.ok()) cerr << s.ToString() << endl;

4.3 關閉數據庫

當您對一個數據庫的操做完成了,只需刪除數據庫對象就能夠了關閉數據庫:

1 ... open the db as described above ...
2 ... do something with db ...
3 delete db;

4.4 讀和寫

  leveldb提供了Put,delete,Get方法來修改和查詢數據庫:

1 std::string value;
2 leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);
3 if (s.ok()) s = db->Put(leveldb::WriteOptions(), key2, value);
4 if (s.ok()) s = db->Delete(leveldb::WriteOptions(), key1);

4.5 原子化更新

  leveldb提供一個操做序列的原子化:

 1 #include "leveldb/write_batch.h"
 2 ...
 3 std::string value;
 4 leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);
 5 if (s.ok()) {
 6     leveldb::WriteBatch batch;
 7     batch.Delete(key1);
 8     batch.Put(key2, value);
 9     s = db->Write(leveldb::WriteOptions(), &batch);
10 }

  除了用於原子化,WriteBatch也能夠經過把不少操做放到一塊兒用來加快批更新。

4.6 同步寫入

默認狀況下,每次寫入leveldb是異步的:寫入函數只要把寫操做交付給操做系統就返回,從操做系統的內存傳輸到底層的持久性存儲是異步的。
能夠打開sync標誌來指定某次寫操做直到被寫入的數據已經被一路推到永久存儲時才返回:

1 leveldb::WriteOptions write_options;
2 write_options.sync = true;
3 db->Put(write_options, ...);

    異步寫入的速度每每超過同步寫入的一千倍,但異步寫的缺點是機器崩潰可能會致使最後的少許更新丟失。同步寫能夠更新標記,說明崩潰時在何處從新啓動。

4.7 併發

  一個數據庫只能由一個進程打開,而在一個單一的過程當中,同一個leveldb:: DB對象能夠被多個併發的線程安全地共享。

4.8 迭代

  下面的例子展現瞭如何打印在一個數據庫中的全部鍵值對:

1 leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());
2 for (it->SeekToFirst(); it->Valid(); it->Next()) {
3     cout << it->key().ToString() << ": "  << it->value().ToString() << endl;
4 }
5 assert(it->status().ok());  // Check for any errors found during the scan
6 delete it;

  下面例子顯示瞭如何只處理範圍[start,limit)內的鍵:

1 for (it->Seek(start); it->Valid() && it->key().ToString() < limit; it->Next()) {
2     ...
3 }

您也能夠以相反的順序處理條目。 (警告:反向迭代可能會略慢於正向迭代。):

1 for (it->SeekToLast(); it->Valid(); it->Prev()) {
2     ...
3 }

4.9 快照

  快照提供key-value整個存儲狀態的只讀視圖,ReadOptions::snapshot可能非NULL來代表讀操做於DB的特定版本狀態。
  若是ReadOptions::snapshot是NULL,會對當前狀態產生快照。
  快照經過DB::GetSnapshot()方法建立:

1 leveldb::ReadOptions options;
2 options.snapshot = db->GetSnapshot();
3 ... apply some updates to db ...
4 leveldb::Iterator* iter = db->NewIterator(options);
5 ... read using iter to view the state when the snapshot was created ...
6 delete iter;
7 db->ReleaseSnapshot(options.snapshot);

  須要注意的是當一個快照再也不須要,要用DB::ReleaseSnapshot方法來釋放。 

5. 參數設定

  參數能夠經過改變include/leveldb/options.h中定義的默認值來設定。
  leveldb 中啓動時的一些配置,經過 Option 傳入。
  get/put/delete 時,也有相應的ReadOption/WriteOption。

  5.1 參數項

 1 // include/leveldb/option.h
 2 Options {
 3 // 傳入的 comparator
 4 const Comparator* comparator;
 5 // open 時,若是 db 目錄不存在就建立
 6 bool create_if_missing;
 7 // open 時,若是 db 目錄存在就報錯
 8 bool error_if_exists;
 9 // 是否保存中間的錯誤狀態(RecoverLog/compact),compact 時是否讀到的 block 作檢驗。
10 bool paranoid_checks;
11 // 傳入的 Env。
12 Env* env;
13 // 傳入的打印日誌的 Logger
14 Logger* info_log;
15 // memtable 的最大 size
16 size_t write_buffer_size;
17 // db 中打開的文件最大個數
18 // db 中須要打開的文件包括基本的 CURRENT/LOG/MANIFEST/LOCK, 以及打開的 sstable 文件。
19 // sstable 一旦打開,就會將 index 信息加入 TableCache,因此把
20 // (max_open_files - 10)做爲 table cache 的最大數量.
21 int max_open_files;
22 // 傳入的 block 數據的 cache 管理
23 Cache* block_cache;
24 // sstable 中 block 的 size
25 size_t block_size;
26 // block 中對 key 作前綴壓縮的區間長度
27 int block_restart_interval;
28 // 壓縮數據使用的壓縮類型(默認支持 snappy,其餘類型須要使用者實現)
29 CompressionType compression;
30 }
31 
32 // include/leveldb/option.h
33 struct ReadOptions {
34 // 是否對讀到的 block 作校驗
35 bool verify_checksums;
36 // 讀到的 block 是否加入 block cache
37 bool fill_cache;
38 // 指定讀取的 SnapShot
39 const Snapshot* snapshot;
40 }
41 
42 // include/leveldb/option.h
43 struct WriteOptions {
44 // write 時,記 binlog 以後,是否對 binlog 作 sync。
45 bool sync;
46 // 若是傳入不爲 NULL,write 完成以後同時作 SnapShot.
47 const Snapshot** post_write_snapshot;
48 }

 另外還有一些編譯時的常量,與 Option 一塊兒控制:

 1 // db/dbformat.h
 2 namespace config {
 3 // level 的最大值
 4 static const int kNumLevels = 7;
 5 // level-0 中 sstable 的數量超過這個閾值,觸發 compact
 6 static const int kL0_CompactionTrigger = 4;
 7 // level-0 中 sstable 的數量超過這個閾值, 慢處理這次寫(sleep1ms)
 8 static const int kL0_SlowdownWritesTrigger = 8;
 9 // level-0 中 sstable 的數量超過這個閾值, 阻塞至 compact memtable 完成。
10 static const int kL0_StopWritesTrigger = 12;
11 // memtable dump 成的 sstable,容許推向的最高 level
12 // (參見 Compact 流程的 VersionSet::PickLevelForMemTableOutput())
13 static const int kMaxMemCompactLevel = 2;
14 }
15 // db/version_set.cc
16 namespace leveldb {
17 // compact 過程當中,level-0 中的 sstable 由 memtable 直接 dump 生成,不作大小限制
18 // 非 level-0 中的 sstable 的大小設定爲 kTargetFileSize
19 static const int kTargetFileSize = 2 * 1048576;
20 // compact level-n 時,與 level-n+2 產生 overlap 的數據 size (參見 Compaction)
21 static const int64_t kMaxGrandParentOverlapBytes = 10 * kTargetFileSize;
22 }

  5.2 塊大小

    leveldb把相鄰鍵碼放入相同的塊,以這樣的塊爲單元來轉移到持久存儲中,缺省的塊大小約爲4096未壓縮字節。
    主要是作批量掃描的應用能夠經過增長塊大小來提升性能。
    使用小於1KB或比高於MB數量級的塊大小沒有什麼好處,而壓縮會在塊大小較大的條件下更有效。

  5.3 壓縮

  每一個塊在寫入持久存儲以前被單獨壓縮。壓縮是缺省的,由於默認的壓縮方法是很是快的,不可壓縮的數據會自動禁用壓縮。
  在少見狀況,應用程序可能要徹底禁用壓縮來改善性能:

1 leveldb::Options options;
2 options.compression = leveldb::kNoCompression;
3 ... leveldb::DB::Open(options, name, ...) ....

6. 校驗

  ReadOptions::verify_checksums能夠設置爲true來強制讀操做對全部數據校驗,默認爲false
  若是數據庫損壞了,leveldb::RepairDB函數能夠恢復儘量多的數據。

相關文章
相關標籤/搜索