LevelDB 源码剖析(一)准备工作:环境搭建、接口使用、常用优化
文章目錄
- 環境搭建
- 實戰使用
- 創建、關閉數據庫
- 數據讀、寫、刪除
- 批量處理
- 迭代器遍歷
- 常用優化方案
- 壓縮
- 緩存
- 過濾器
- 命名
環境搭建
# 下載源碼 git clone https://github.com/google/leveldb.git# 下載依賴第三方庫(benchmark、googletest) git submodule update --init # 執行編譯 cd leveldb/ mkdir -p build && cd build cmake -DCMAKE_BUILD_TYPE=Debug .. && cmake --build .# 頭文件加入系統目錄 cp -r ./include/leveldb /usr/include/ cp build/libleveldb.a /usr/local/lib/實戰使用
創建、關閉數據庫
創建數據庫或打開數據庫,均通過 Open 函數實現。Open 為一個靜態成員函數,其函數聲明如下所示:
static Status Open(const Options& options, const std::string& name,DB** dbptr);- const Options & options:用于指定數據庫創建或打開后的基本行為。
- const std::string & name:用于指定數據庫的名稱。
- DB* dbptr*:定義了一個DB類型的指針的指針,該指針作為Open函數操作后傳給調用者使用的DB類型的實際指針。
- Status:當操作成功時,函數返回 status.ok() 的值為 True,*dbptr 分配了不為 NULL 的實際指針地址;若其中的操作存在錯誤,則 status.ok() 的值為 False,并且對應的 *dbptr 為 NULL。
關閉數據庫非常簡單,只需要使用 delete 釋放 db 即可。
代碼示例如下:
#include"leveldb/db.h" #include<string> #include<iostream>using namespace std;int main() {leveldb::DB* db;leveldb::Options op;op.create_if_missing = true;//打開數據庫leveldb::Status status = leveldb::DB::Open(op, "/tmp/testdb", &db);if(!status.ok()){cout << status.ToString() << endl;exit(1);}//關閉數據庫delete db;return 0; }數據讀、寫、刪除
數據庫中的讀、寫與刪除操作分別用 Get 、 Put、Delete 這3個接口函數實現。接口定義如下:
Status Get(const ReadOptions& options, const Slice& key,std::string* value); Status Put(const WriteOptions&, const Slice& key,const Slice& value); Status Delete(const WriteOptions&, const Slice& key);- ReadOptions& options:代表實際讀操作傳入的行為參數。
- WriteOptions& options:代表實際寫操作傳入的行為參數。
這里需要注意的是 Delete 并不會直接刪除數據,而是在對應位置插入一個 key 的刪除標志,然后在后續的Compaction 過程中才最終去除這條 key-value 記錄。
代碼示例如下:
#include"leveldb/db.h" #include"leveldb/slice.h" #include<iostream>using namespace std;int main() {leveldb::DB* db;leveldb::Options op;op.create_if_missing = true;//打開數據庫leveldb::Status status = leveldb::DB::Open(op, "/tmp/testdb", &db);if(!status.ok()){cout << status.ToString() << endl;exit(1);}//寫入數據leveldb::Slice key("hello");string value("world");status = db->Put(leveldb::WriteOptions(), key, value);if(status.ok()){cout << "key: " << key.ToString() << " value: " << value << " 寫入成功。" << endl;}//查找數據status = db->Get(leveldb::ReadOptions(), key, &value);if(status.ok()){cout << "key: " << key.ToString() << " value: " << value << " 查找成功。" << endl;}//刪除數據status = db->Delete(leveldb::WriteOptions(), key);if(status.ok()){cout << "key: " << key.ToString() << " 刪除成功。" << endl;}//關閉數據庫delete db;return 0; }批量處理
針對大量的操作,LevelDB 不具有傳統數據庫所具備的事務操作機制,然而它提供了一種批量操作的方法。這種批量操作方法主要具有兩個作用:一是提供了一種原子性的批量操作方法;二是提高了整體的數據操作速度。
LevelDB 針對批量操作定義了 WriteBatch 的類型。WriteBatch 有 3 個非常重要的接口:數據寫(Put)、數據刪 除(Delete)以及清空批量寫入緩存(Clear),具體定義如下所示:
class LEVELDB_EXPORT WriteBatch {public: //...void Put(const Slice& key, const Slice& value);void Delete(const Slice& key);void Clear();//... };當我們想將 WriteBatch 中的數據寫入 DB 時,只需要調用 Write 接口,其主要用于處理之前保存在 WriteBatch 對象中的所有批量操作,其詳細接口定義如下所示:
Status Write(const WriteOptions& options, WriteBatch* updates);- 注意:一旦我們寫入完成后,就會調用 updates 中的 Clear 來清空之前保存的批量操作。
代碼示例如下:
#include"leveldb/db.h" #include"leveldb/slice.h" #include"leveldb/write_batch.h" #include<iostream>using namespace std;int main() {leveldb::DB* db;leveldb::Options op;op.create_if_missing = true;//打開數據庫leveldb::Status status = leveldb::DB::Open(op, "/tmp/testdb", &db);if(!status.ok()){cout << status.ToString() << endl;exit(1);}//批量寫入leveldb::Slice key;string value;leveldb::WriteBatch batch;for(int i = 0; i < 10; i++){value = ('0' + i);key = "k" + value;batch.Put(key, value);}status = db->Write(leveldb::WriteOptions(), &batch);if(status.ok()){cout << "批量寫入成功" << endl; }//批量刪除for(int i = 0; i < 10; i++){key = "k" + ('0' + i);batch.Delete(key);}status = db->Write(leveldb::WriteOptions(), &batch);if(status.ok()){cout << "批量刪除成功" << endl; }//關閉數據庫delete db;return 0; }迭代器遍歷
針對 DB 中所有的數據記錄,LevelDB 不僅支持前向的遍歷,也支持反向的遍歷。在 DB 對象類型中,通過調用NewIterator 創建一個新的迭代器對象,該接口具體定義如下:
Iterator* NewIterator(const ReadOptions&);- ReadOptions& option:用于指定在遍歷訪問過程中的相關設置。
這里有一個需要注意的地方,這里返回的迭代器不能直接使用,而是需要先使用對應的 Seek 操作偏移到指定位置后才能進行對應的迭代操作。
代碼示例如下:
#include"leveldb/db.h" #include"leveldb/slice.h" #include"leveldb/iterator.h" #include<iostream>using namespace std;int main() {leveldb::DB* db;leveldb::Options op;op.create_if_missing = true;//打開數據庫leveldb::Status status = leveldb::DB::Open(op, "/tmp/testdb", &db);if(!status.ok()){cout << status.ToString() << endl;exit(1);}leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());//正向遍歷cout << "開始正向遍歷:" << endl;for(it->SeekToFirst(); it->Valid(); it->Next()){cout << "key: " << it->key().ToString() << " value: " << it->value().ToString() << endl;}//逆向遍歷cout << "開始逆向遍歷:" << endl;for(it->SeekToLast(); it->Valid(); it->Prev()){cout << "key: " << it->key().ToString() << " value: " << it->value().ToString() << endl;}//范圍查詢cout << "開始范圍查詢:" << endl;for(it->Seek("k4"); it->Valid() && it->key().ToString() < "k8"; it->Next()){cout << "key: " << it->key().ToString() << " value: " << it->value().ToString() << endl;}delete it;//關閉數據庫delete db;return 0; }常用優化方案
壓縮
當每一個塊寫入存儲設備中時,可以選擇是否對塊進行壓縮后再存儲,以及 Options 參數的 compression 成員變量決定是否開啟壓縮。默認情況下,壓縮是開啟的,且壓縮的速度很快,基本對整體的性能沒有太大影響
用戶在調用時,可以用 kNoCompression 或 kSnappyCompression 對 compression 參數進行設定,從而確定塊在實際存儲過程中是否進行壓縮。
leveldb::Options op; op.compression = leveldb::kNoCompression; //不啟用壓縮 op.compression = leveldb::kSnappyCompression; //Snappy壓縮緩存
Cache的作用是充分利用內存空間,減少磁盤的 I/O 操作,從而提升整體運行性能。LevelDB 默認的 Cache 采用的是 LRU 算法,即近期最少使用的數據優先從 Cache 中淘汰,而經常使用的數據駐留在內存,從而實現對需要頻繁讀取的數據的快速訪問。
LevelDB 中定義了一個全局函數 NewLRUCache 用于創建一個 LRUCache。
leveldb::Options op; op.block_cache = leveldb::NewLRUCache(10 * 1024 * 1024); //參數主要用于指定LevelDB的塊的Cache空間,如果為NULL則默認為8MB過濾器
由于 LevelDB 中所有的數據均保存在磁盤中,因而一次 Get 的調用,有可能導致多次的磁盤 I/O 操作。為了盡可能減少讀過程時磁盤 I/O 的操作次數,LevelDB 采用了 FilterPolicy 機制。LevelDB 中 Options 對象類型的filter_policy 參數,主要用于確定運行過程中 Get 所遵循的 FilterPolicy 機制。
用戶可以通過調用 NewBloomFilterPolicy 接口函數以創建布隆過濾器,并將其賦值給對應的 filter_policy 參數。
leveldb::Options op; op.filter_policy = leveldb::NewBloomFilterPolicy(10);命名
LevelDB 中磁盤數據讀取與緩存均以塊為單位,并且實際存儲中所有的數據記錄均以 key 進行順序存儲。根據排序結果,相鄰的 key 所對應的數據記錄一般均會存儲在同一個塊中。正是由于這一特性,用戶針對自身的應用場景需要充分考慮如何優化 key 的命名設計,從而最大限度地提升整體的讀取性能。
為了提升性能,命名規則是:針對需要經常同時訪問的數據,其 key 在命名時,可以通過將這些 key 設置相同的前綴保證這些數據的 key 是相鄰近的,從而使這些數據可存儲在同一個塊內。 基于此,那些不常用的數據記錄自然會放置在其他塊內。
總結
以上是生活随笔為你收集整理的LevelDB 源码剖析(一)准备工作:环境搭建、接口使用、常用优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LevelDB是什么?为什么我们需要K-
- 下一篇: LevelDB 源码剖析(三)公共基础: