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 

如果增加數據壓縮后,性能有所提高

隨機讀取 : 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函數可以恢復盡可能多的數據。

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM