leveldb使用指南
這篇文章是levelDB官方文檔的譯文,原文地址:LevelDB library documentation
這篇文章主要講leveldb接口使用和注意事項。?
leveldb是一個持久型的key-value數據庫。key,value可以是任意的字節數組,key之間是有序的。key的比較函數可以由用戶指定。
1. 打開數據庫
leveldb使用文件系統目錄名作為name,并把數據庫所有內容都存儲在這個目錄中。這是個打開數據庫,并且指定如果數據庫不存在就新建的例子:
#include <cassert>#include "leveldb/db.h"leveldb::DB* db;leveldb::Options options;options.create_if_missing = true;leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);assert(status.ok());...- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果想在創建數據庫時發現已經存在就報錯,那么調用leveldb::DB::Open之前添加下面這一行:
options.error_if_exists = true;- 1
- 1
2. 返回值狀態Status
你也許已經注意到上面的leveldb::Status type?. 這種類型是leveldb大多數函數的返回值,函數的返回值有可能是error??梢酝ㄟ^判斷result是不是ok來判斷:
leveldb::Status s = ...;if (!s.ok()) cerr << s.ToString() << endl;- 1
- 2
- 1
- 2
3. 關閉數據庫
當數據庫操作完成,關閉數據庫只需要刪除數據庫對象:
... open the db as described above ...... do something with db ...delete db;- 1
- 2
- 3
- 1
- 2
- 3
4. 讀和寫
數據庫提供Put, Delete, and Get函數來修改查詢數據庫。下面的例子把key1的值賦給key2:
std::string value;leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);if (s.ok()) s = db->Put(leveldb::WriteOptions(), key2, value);if (s.ok()) s = db->Delete(leveldb::WriteOptions(), key1);- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
5. 原子性更新
如果上面的代碼中,再給key2賦值之后,刪除key1之前,進程退出了,那么key1 and key2就會有相同的值。這種情況可以使用WriteBatch class來原子性的進行一系列的更新:
#include "leveldb/write_batch.h"...std::string value;leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);if (s.ok()) {leveldb::WriteBatch batch;batch.Delete(key1);batch.Put(key2, value);s = db->Write(leveldb::WriteOptions(), &batch);}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
WriteBatch是一系列對數據庫的更新操作,并且這些批量操作之間有一定的順序性。注意到我們雖然在給key2賦值之前刪除,使用writebtch最終并不會錯誤的造成vaue丟失。?
撇開writebatch帶來的原子性優勢,writebatch也能通過把多個更新放在一個批量操里面來加速操作。
6. 同步寫
通常情況下,所有的leveldb寫操作都是異步的:當leveldb把寫操作交個操作系統之后就返回。從操作系統內存到硬盤等持久性存儲是異步的。如果在寫的時候打開同步寫選項,那么只有當數據持久化到硬盤之后才會返回。(On Posix systems, this is implemented by calling either?fsync(...)?orfdatasync(...)?or?msync(..., MS_SYNC)?before the write operation returns.)
leveldb::WriteOptions write_options;write_options.sync = true;db->Put(write_options, ...);- 1
- 2
- 3
- 1
- 2
- 3
異步寫通常比同步寫快1000倍以上。異步寫的不足就是當機器宕機時會丟失最后更新的數據。寫進程的異常退出并不會造成數據的丟失。
通常情況下異步寫能夠被妥善的處理。例如,當你在網數據庫寫大量的數據時,在機器宕機之后能通過重新寫一次數據來修復?;旌鲜褂猛胶彤惒揭彩强梢缘摹@缑縉次寫做一次同步。當機器宕機的時候,只需要重新寫最后一次同步寫之后的數據。同步寫一個新增一個標記來記錄上一次同步寫的位置。
WriteBatch是一個異步寫。一個WriteBatch內部的多個更新操作放在一起也可以使用同步寫操作,(i.e.,?write_options.sync?is set to true). 可以通過批量操作降低同步寫的消耗。
7. 并發
一個數據庫每次只能被一個進程打開。leveldb為了防止誤操作需要一個lock。在一個進程內部,同一個leveldb::DB對象可以在這個進程的多個并發線程之間安全的共享。 例如,不同的線程可以寫,獲取指針,或者讀取相同的數據庫,而不需要額外的同步操作,因為leveldb自動做了請求的同步。然而,其他的對象,例如迭代器或者WriteBatch,需要外部的同步操作。如果兩個線程共享同一個這樣的對象,那么他們必須用自己的lock protocal對數據庫操作進行保護。這在公共的header文件里有更詳細的內容。
8. 迭代器
下面的例子說明了如何輸出數據庫的所有key-value對:
leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());for (it->SeekToFirst(); it->Valid(); it->Next()) {cout << it->key().ToString() << ": " << it->value().ToString() << endl;}assert(it->status().ok()); // Check for any errors found during the scandelete it;- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
處理[start,limit)范圍內的key:
for (it->Seek(start);it->Valid() && it->key().ToString() < limit;it->Next()) {...}- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
逆序處理:(逆序會比順序慢一些)
for (it->SeekToLast(); it->Valid(); it->Prev()) {...}- 1
- 2
- 3
- 1
- 2
- 3
9. Snapshots快照
快照在整個key-value存儲狀態上提供了一個持久性的只讀視圖。非空的ReadOptions::snapshot提供了一個針對db特定狀態的只讀視圖。如果ReadOptions::snapshot是NULL,那么讀操作是在對當前數據庫狀態的隱式視圖上的進行的。
使用DB::GetSnapshot()方法創建Snapshots:
leveldb::ReadOptions options;options.snapshot = db->GetSnapshot();... apply some updates to db ...leveldb::Iterator* iter = db->NewIterator(options);... read using iter to view the state when the snapshot was created ...delete iter;db->ReleaseSnapshot(options.snapshot);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果快照不再需要了,應該使用DB::ReleaseSnapshot接口來釋放,這會消除為了維持快照的狀態多與操作。
10. Slice分片
上面代碼中it->key()?and?it->value()?調用的返回值都是leveldb::Slice類型的實例,slice是一個包含長度和一個紙箱字節數組的簡單結構體。因為我們不需要每次都復制很多的keys和values,所以返回Slice比返回std::string是一個更好的選擇。另外,level-db不返回以null結尾的c類型的字符串,是因為leveldb允許key和value中包含'\0'字符。
C++ strings和null-terminated C-style strings可以很容易的轉換為Slice:
leveldb::Slice s1 = "hello";std::string str("world");leveldb::Slice s2 = str;- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
A Slice can be easily converted back to a C++ string:
std::string str = s1.ToString();assert(str == std::string("hello"));- 1
- 2
- 1
- 2
使用slice的時候需要仔細,需要確保在使用slice的時候,他的指針所指向的地址是有效的。例如,下面不正當的使用:
leveldb::Slice slice;if (...) {std::string str = ...;slice = str;}Use(slice);- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
在if的作用域外,str已經被銷毀了,所以slice所指向的內存地址已經被釋放了。
11. Comparators
前面的例子都是使用默認的排序函數,也就是字典序。另外,我們也在打開數據庫的時候也可以指定一個排序比較函數。
例如,假設數據庫的key由兩個數字組成,我們首先用第一個數字排序,第一個數相等時使用第二個數比較。首先,我們先定義一個leveldb::Comparator的子類來實現我們的想法:
class TwoPartComparator : public leveldb::Comparator {public:// Three-way comparison function:// if a < b: negative result// if a > b: positive result// else: zero resultint Compare(const leveldb::Slice& a, const leveldb::Slice& b) const {int a1, a2, b1, b2;ParseKey(a, &a1, &a2);ParseKey(b, &b1, &b2);if (a1 < b1) return -1;if (a1 > b1) return +1;if (a2 < b2) return -1;if (a2 > b2) return +1;return 0;}// Ignore the following methods for now:const char* Name() const { return "TwoPartComparator"; }void FindShortestSeparator(std::string*, const leveldb::Slice&) const { }void FindShortSuccessor(std::string*) const { }};- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
使用自定義的comparator創建數據庫:
TwoPartComparator cmp;leveldb::DB* db;leveldb::Options options;options.create_if_missing = true;options.comparator = &cmp;leveldb::Status status = leveldb::DB::Open(options, "/tmp/testdb", &db);...- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
12. 后向兼容
當數據庫創建的時候comparator的Name()函數也附加在數據庫上,并且后續每次打開的時候都會進行檢查。如果comparator的name變了,leveldb::DB::Open就會失敗。因此,當且僅當1,新的key格式和比較函數和現存的數據庫不兼容,2,丟棄當前的數據庫的所有內容也無所謂。的時候才會修改comparator的name.
不過你仍然可以逐漸的進化key的格式。例如,你可以存儲為每個key存儲一個版本號,(多數情況下一個字節足夠用),當想使用一個新的key格式的時候,a,使用相同的comparator name,b,為新的key格式增加版本號,c,修改comparator函數,能通過key里面的版本來判斷怎么解析key。
13. 性能
可以通過修改include/leveldb/options.h里面參數的默認值進行性能調優。
13.1 塊大小
leveldb把相鄰的key放進同一個block,block是讀寫數據庫時的單元。默認的未壓縮block大小是4KB。那些經常對數據庫做塊讀取的應用希望增加塊的大小。如果把塊大小調小對性能有提升的話,那些經常隨機數據庫的應用會希望減小塊的大小。一般來說,塊小于1KB或者大于幾MB是無益的。并且,當塊比較大的時候數據壓縮效率會高一些。
13.2 壓縮
每個塊在寫到持久存儲之前是獨立進行壓縮的。由于默認的壓縮算法非常快,所以壓縮默認是打開的。并且,對于不可壓縮的數據,也會自動停止壓縮。在很少的情況下,應用程序可能會完全禁用壓縮,但是除非benchmark表明性能有提成否則不建議這么做。
leveldb::Options options;options.compression = leveldb::kNoCompression;... leveldb::DB::Open(options, name, ...) ....- 1
- 2
- 3
- 1
- 2
- 3
13.3 Cache
數據庫的內容存儲在文件系統上的一系列文件中。每個文件里面有很多的壓縮數據塊。如果options.cache是非空的,那么數據庫會使用cache來緩存經常使用的未壓縮的數據塊。
#include "leveldb/cache.h"leveldb::Options options;options.cache = leveldb::NewLRUCache(100 * 1048576); // 100MB cacheleveldb::DB* db;leveldb::DB::Open(options, name, &db);... use the db ...delete dbdelete options.cache;- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
cache存的是未壓縮的數據,因此,cache需要根據應用層的數據大小,計算應該緩存的數據量。當進行批量讀的時候,應用可能會希望禁用cache,以防止批量讀的數據不要把已經緩存的內容替換掉。一個順序指針可以滿足要求:
leveldb::ReadOptions options;options.fill_cache = false;leveldb::Iterator* it = db->NewIterator(options);for (it->SeekToFirst(); it->Valid(); it->Next()) {...}- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
13.4 Key Layout
數據傳輸單位和緩存單位都是數據塊。根據數據庫排序算法,相鄰的key一般情況下會放在同一個數據塊里面。因此,通過把經常一起訪問的相鄰key放在一個block里面,把不經常使用的key放在分隔的塊里面,應用程序可以提升性能。
例如,我們在leveldb之上實現一個文件系統。我們希望entry的類型會這樣存儲:
filename -> permission-bits, length, list of file_block_idsfile_block_id -> data- 1
- 2
- 1
- 2
我們希望文件名的前綴是某個字符,如’/’,file_block_id的前綴是不同的字符,例如’0’,這樣我們就能只瀏覽metadate,而不用強制緩存大量的文件內容了。
13.5 過濾器Filters
由于leveldb在磁盤上組織數據的方式,一個Get()調用可能導致多次磁盤讀操作??蛇x的FilterPolicy機制可以潛在的減少磁盤讀操作。
leveldb::Options options;options.filter_policy = NewBloomFilterPolicy(10);leveldb::DB* db;leveldb::DB::Open(options, "/tmp/testdb", &db);... use the database ...delete db;delete options.filter_policy;- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
上面的代碼在數據庫中使用一個基于Bloom filter的filtering策略?;贐loom filter的filtering策略為每個key在內存保存一些數據位,這上面的例子中,為每個key保存10位),這個filter可以降低Get()調用操作中不必要的磁盤讀大概100倍。增加每個key的位數可以更大的降低磁盤讀,但是會增加內存的使用。建議那些工作集不適合放在內存的應用,以及隨機讀比較多的應用使用filter policy。
如果你使用的是自定義的comparator,應該確保filter policy和comparator是兼容的。例如,一個在key進行比較的時候會刪除前后的空格的comparator。應用程序也應該提供一個忽略前后空格的自定義filter policy。例如:
class CustomFilterPolicy : public leveldb::FilterPolicy {private:FilterPolicy* builtin_policy_;public:CustomFilterPolicy() : builtin_policy_(NewBloomFilterPolicy(10)) { }~CustomFilterPolicy() { delete builtin_policy_; }const char* Name() const { return "IgnoreTrailingSpacesFilter"; }void CreateFilter(const Slice* keys, int n, std::string* dst) const {// Use builtin bloom filter code after removing trailing spacesstd::vector<Slice> trimmed(n);for (int i = 0; i < n; i++) {trimmed[i] = RemoveTrailingSpaces(keys[i]);}return builtin_policy_->CreateFilter(&trimmed[i], n, dst);}bool KeyMayMatch(const Slice& key, const Slice& filter) const {// Use builtin bloom filter code after removing trailing spacesreturn builtin_policy_->KeyMayMatch(RemoveTrailingSpaces(key), filter);}};- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
高級應用程序可以使用不依賴 bloom filter的策略,或者根據key集合的特征提供自己的filter policy。詳情可以參照leveldb/filter_policy.h?。
14. 校驗和
leveldb把校驗和和存儲在文件系統中的數據聯系起來。下面是兩種校驗和驗證的方式:?
ReadOptions::verify_checksums可以設置為true,來強制對從文件系統讀取的所有數據進行校驗和驗證。默認不使用。
Options::paranoid_checks可以在打開數據庫之前設置為true,來確保一旦檢測到內部錯誤就盡快拋出異常。當數據庫打開的時候可能拋出異常,或者后續的數據庫操時拋出。默認情況下,會禁用多疑的檢測,這樣的話,即使部分持久性存儲崩潰數據庫依舊可以使用。
如果數據庫崩潰了,如果多疑檢測打開的話,可能無法打開這個數據庫,可以使用leveldb::RepairDB函數來修復盡可能多的數據。
15. 空間估算
GetApproximateSizes方法可以用來估算一個或多個key占用文件系統空間。
leveldb::Range ranges[2];ranges[0] = leveldb::Range("a", "c");ranges[1] = leveldb::Range("x", "z");uint64_t sizes[2];leveldb::Status s = db->GetApproximateSizes(ranges, 2, sizes);- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
上面的代碼中,size[0]是key范圍在[a..c)之間的內容占用的文件空間的大小估算值。sizes[1]是key范圍在[a..c)之間的內容占用的文件空間的大小估算值。
16. 環境變量
leveldb所有文件操作和其他的系統調用通過leveldb::Env對象來判斷如何使用,復雜的客戶端可能希望提供自己的Env實現做到更好的控制。例如,應用程序可以人為為文件IO操作增加延時以降低leveldb對系統的其他應用帶來的影響。
class SlowEnv : public leveldb::Env {.. implementation of the Env interface ...};SlowEnv env;leveldb::Options options;options.env = &env;Status s = leveldb::DB::Open(options, ...);- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
17. 可移植性
通過提供leveldb/port/port.h引用的types/methods/functions的平臺特定實現,leveldb就能移植到新的平臺。leveldb/port/port_example.h里面有更詳細的內容。
另外,新平臺可能需要新的leveldb::Env實現。See?leveldb/util/env_posix.h?for an example.
18. 其他
leveldb的更多實現細節可以看下面的文檔:?
levelDB實現細節?
levelDB immutable Table的文件格式?
leveldb日志文件格式
總結
以上是生活随笔為你收集整理的leveldb使用指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 提高C++程序运行效率的10个简单方法
- 下一篇: github 国内加速镜像