Leveldb官方文檔(中文版)

Level

Jeff Dean, Sanjay Ghemawat

       leveldb提供一個持久的鍵值存儲,鍵和值是任意的字節數組。可以根據用戶自定義的鍵值排序規則,來進行鍵值存儲。


打開數據庫

       leveldb數據庫名稱與文件系統目錄相同,數據庫中所有的數據都是存儲在該目錄下。接下來的例子就展示如何打開一個數據庫,必要時創建它:

#include <assert>
#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());
       如果你認爲在數據庫存在的情況下創建數據庫是一個錯誤,則可以在打開數據庫前在代碼中添加一行代碼如下:

options.error_if_exists = true;

狀態

       你可能已經注意到:以上代碼中有leveldb::Status類型,這種類型的值被作爲大多數leveldb函數的返回值,以用來標記一個錯誤。你可以用它來判斷函數是否執行成功,也可以打印相關的錯誤信息。

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

關閉數據庫

       當對數據庫操作完畢後,釋放database對象,如下所示:

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

讀寫操作

       leveldb提供了Put,Delete和Get函數來修改/查詢數據庫,例如,以下代碼將key1的value複製到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);

原子更新(Atomic Updates)

       注意,如果一個進程在插入了key2之後並且在刪除key1之前終止,相同的值可能會存儲在多個鍵。這樣的問題可以通過使用WriteBatch類來進行一系列的原子更新。

#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);
}

       這個WriteBatch類包含了對數據庫進行的一系列操作,並且這些操作是有序的。注意我們稱之爲在Put之前Delete,這樣是爲了如果key1和key2是相同的,我們最終不會錯誤的刪除其對應的值。除了它的原子性的好處外,writebatch也可以用來加快處理大量的updates,在同一批中添加獨立的update。


同步寫(Synchronous Writes)

       默認情況下,每次寫LevelDB是異步的:它在寫操作從當前進程傳遞到操作系統後就返回了,數據從系統內存到底層持久存儲是異步發生的。同步標誌可以被打開,此時是爲了確保寫操作的數據被寫入到持久存儲中後才返回。(POSIX系統,這是通過調用fsync實施(…)或fdatasync(…)或msync(…,ms_sync)寫操作返回之前。)

leveldb::WriteOptions write_options;
write_options.sync = true;
db->Put(write_options, ...);
       異步寫入速度通常是同步寫入的一千多倍,異步寫入的缺點是,機器崩潰可能導致最後幾次更新丟失。請注意,一個崩潰的寫作過程(即,不是一個重新啓動)不會造成任何損失,即使同步寫入失敗了,在數據真正寫入持久存儲前該更新是先從進程內存傳遞到操作系統的。

       異步寫入可以安全地使用。例如,當往數據庫中寫入大量數據時,你可以處理丟失的更新通過在崩潰後重啓大容量的寫入操作。一種混合方案可能是每N個寫是同步的,並且在發生崩潰事件時,批量寫入重新啓動運是在最後的同步寫之前完成的。(同步寫可以更新一個標記,說明在崩潰的地方重新啓動。)

       writebatch提供了一種替代異步寫。多個更新可被放在同一個writebatch中一起使用一個同步寫(即write_options.sync設置爲true)。該同步寫的額外成本將被分攤到所有寫操作中。


併發(Concurrency)

       一個數據庫只能一次被一個進程打開,Leveldb的實現是從操作系統獲取一個鎖來防止誤操作。在單一進程中,這個leveldb::DB對象可以被通過併發的線程安全地共享。即在沒有任何外部同步情況下,不同的線程可以寫入、讀取迭代器或獲得相同的數據庫(LevelDB實現會自動同步所需的)。然而其他對象(如迭代器和writebatch)可能需要外部同步,如果2個線程共享這樣的對象,他們必須使用他們自己的鎖定協議來同步訪問,更多的細節可在公共頭文件。


迭代(Iteration)

       接下來的例子展示如何打印數據庫中的所有鍵值信息:

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 scan
delete it;

       接下來的版本展示如何處理鍵在範圍[start, limit)的數據:

for (it->Seek(start);
	it->Valid() && it->key().ToString() < limit;
	it->Next()) {
	...
}
       當然,你也可以逆序輸出鍵值信息(注意:逆向迭代可能比正向迭代稍慢):
for (it->SeekToLast(); it->Valid(); it->Prev()) {
	...
}

快照(Snapshots)

       快照提供了對整個鍵值狀態一致的只讀版本,ReadOptions::snapshot是!NULL的話,表示在特定版本的數據庫狀態上進行讀操作;ReadOptions::snapshot是NULL的話,表示在隱式版本的數據庫狀態上進行讀操作。Snapshots是通過DB::GetSnapshot()方法來創建的。

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);
       注意,當快照不再需要,應該使用DB::releasesnapshot接口來釋放它,這可以擺脫持續的讀取快照操作。

片(Slice)

       it->key()和it->value()的返回值被稱爲leveldb::Slice類型,Slice是一個簡單的shu結構,它包含一個長度和一個外部字節數組的指針。返回一個Slice比返回一個標準的std::string類型更有效率,因爲我們不需要複製潛在的大的鍵和值。此外,leveldb方法不返回'\0'結尾的C-type字符串,這樣的話,leveldb的鍵值就可以包含'\0'數據了。

       C++的strings和以'\0'結尾的C-type字符串也已很容易轉化爲Slice類型:

leveldb::Slice s1 = "hello";

std::string str("world");
leveldb::Slice s2 = str;
       一個Slice類型很容易轉換回C++ string類型:
std::string str = s1.ToString();
assert(str == std::string("hello"));
       使用Slice類型是需注意,要保證其內部的字節數組指針指向內容是可用的,以下代碼作爲錯誤示例:

leveldb::Slice slice;
if (...) {
	std::string str = ...;
	slice = str;
}
// 此時slice所指內存已經不可用,有可能發生未知錯誤
Use(slice);

比較器(Comparators)

       前面的例子對鍵使用的是默認排序函數,即字典序排序。當打開數據庫時,你可以提供自定義比較器。例如,假設每個數據庫的鍵包含2個數字,我們首先按照第一個數字進行排序,相同時在按照第二個數字排序。首先,定義一個leveldb::Comparator的子類,如下所示:

class TwoPartComparator : public leveldb::Comparator {
	public:
	// Three-way comparison function:
	//   if a < b: negative result
	//   if a > b: positive result
	//   else: zero result
	int 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 { }
};
       現在創建一個使用自定義比較器的數據庫:

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);
...

向後兼容

       比較器的名字方法的結果連接到數據庫時創建,並在每一個後續數據庫打開時進行檢查。如果名字改變了,leveldb::DB::Open會調用失敗。因此,改變名字當且僅當新的鍵值格式與現有的數據庫,和比較函數是不相容的,它可以拋棄所有現有數據庫的內容。
       你仍然可以逐漸發展關鍵格式隨着時間一點點的前期規劃。例如,您可以存儲一個版本號結束時每個鍵(一個字節應該足夠了對於大多數使用)。當您希望切換到一個新的密鑰格式(如。添加一個可選的第三部分,鑰匙由TwoPartComparator處理),(a)保持相同的比較器名稱(b)增加新密鑰版本號(c)改變比較器功能所以它使用中的版本號決定如何解釋他們的關鍵。


性能

       性能可以被調整,通過改變類型中定義的默認值,其在include/ leveldb / options.h中。


塊大小(Block size)

       leveldb組相鄰鍵合爲同一塊,一塊是單位轉移到和從持久存儲。默認的塊大小是大約4096個未壓縮字節。應用程序主要是做批量掃描數據庫的內容可能希望增加這個尺寸。應用程序做很多點讀的小值可能希望切換到一個更小的塊大小如果性能測量顯示有所改善。並沒有太多好處使用塊小於1 kb,或比幾兆字節更大。還要注意,壓縮將更有效和更大的塊大小。


壓縮(Compression)

       每一塊進行單獨壓縮在寫入持久存儲之前。壓縮在默認情況下是啓用的,因爲默認的壓縮方法是非常快,和自動禁用uncompressible數據。在極少數情況下,應用程序可能希望完全禁用壓縮,如果基準顯示性能改進時才這樣做:

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

緩存(Cache)

       數據庫的內容存儲在文件系統中的文件和每個文件存儲一個序列的壓縮塊。如果選項打開,緩存是空的,則它是用來緩存經常使用的未壓縮的塊內容。

#include "leveldb/cache.h"

leveldb::Options options;
options.cache = leveldb::NewLRUCache(100 * 1048576);  // 100MB cache
leveldb::DB* db;
leveldb::DB::Open(options, name, &db);
... use the db ...
delete db
delete options.cache;
       注意緩存包含的是未壓縮的數據,因此它的大小應該和應用程序級的數據大小一致,沒有任何減少壓縮。(緩存壓縮塊交給操作系統緩衝區緩存,或提供的任何自定義Env實現客戶端)。

       當執行批量讀取時,應用程序可能希望禁用緩存,數據處理的大部分閱讀不最終取代大部分的緩存內容。一個per-iterator選項可以用來實現這一目標:

leveldb::ReadOptions options;
options.fill_cache = false;
leveldb::Iterator* it = db->NewIterator(options);
for (it->SeekToFirst(); it->Valid(); it->Next()) {
	...
}

鍵佈局(Key Layout)(50%)

       注意,磁盤傳輸和緩存單元是一個塊,相鄰的鍵(根據數據庫排序順序)通常會被放置在同一塊。因此應用程序可以改善其性能,在臨近的地方訪問鍵,不常使用的鍵在一個單獨的區域空間。

       例如,假設我們實現一個簡單的文件系統並且之上,我們可能希望存儲條目的類型是:

filename -> permission-bits, length, list of file_block_ids
file_block_id -> data
       我們可能想用一個字母前綴文件名鍵(' / ')和file_block_id以不同的字母鍵(比如“0”)所以,掃描不強迫我們獲取的元數據和緩存的文件內容。
       因爲數據在磁盤上存儲,並且一個Get()調用可能會涉及多個磁盤讀取操作。可選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;

校驗和(Checksums)

       leveldb將校驗和與所有數據存儲在文件系統中,有兩個獨立的控制提供瞭如何積極這些校驗和驗證:

* ReadOptions:verify_checksums可能設置爲true,迫使校驗和驗證的所有數據從文件系統讀取代表一個特定的閱讀。默認情況下沒有這樣的驗證。

* Options::paranoid_checks可能設置爲true之前打開數據庫的數據庫實現提高一個錯誤當它檢測到一個內部突變。這取決於數據庫的部分已經損壞,錯誤可能發生在數據庫打開是,或者是在另一個數據庫操作時。默認情況下,強制性檢查是關閉的,即       使它的持久存儲部分已損壞,該數據庫也可使用。

如果數據庫已損壞(也許它不能被打開時,偏執的檢查是打開的),該LevelDB::repairdb函數可用於恢復儘可能多的數據。

近似大小(Approximate Sizes)

       GetApproximateSizes()函數可以用於獲得鍵值存儲所佔用的系統空間的近似大小(單位字節)。

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);
       上面的代碼將設置sizes[0]爲鍵在範圍[a..c)上的所用字節數,同理sizes[1]爲鍵在範圍[x..z)上的所用字節數。


環境(Environment)

       所有的文件操作(和其他操作系統的系統調用)的發佈實施,是通過leveldb::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, ...);

移植(Porting)

       Leveldb可移植到新的平臺通過提供平臺相關的 types/methods/functions exported by leveldb/port/port.h函數具體實現,具體請看leveldb/port/port_example.h。

       此外,移植新平臺可能需要一個新的默認leveldb::Env實現,請看leveldb/util/env_posix.h作爲示例。


其他信息(Other Information)

       關於leveldb的具體實現細節請看以下文檔(這些都在源碼中的doc文件夾下):

leveldb/doc/impl.html

leveldb/doc/table_format.txt

leveldb/doc/log_format.txt


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章