LevelDB學習筆記 (1):初識LevelDB

周小倫發表於2021-07-03

LevelDB學習筆記 (1):初識LevelDB

1. 寫在前面

1.1 什麼是levelDB

LevelDB就是一個由Google開源的高效的單機Key/Value儲存系統,該儲存系統提供了Key到Value的有序對映。

地址: https://github.com/google/leveldb

中文文件: https://kevins.pro/leveldb_chinese_doc.html

1.2 為什麼要學levelDB

學習原始碼算是一種很好的學習方式,準備精讀幾個經典的開原始碼,那學習levelDB的原因主要有下

  1. 谷歌開源,程式碼質量非常高,而且只有1w行(去掉測試程式碼)不是過於複雜
  2. 現在主流的kv儲存系統有很多基於levelDB或者借鑑了levelDB的思想

2. Linux/Mac下的編譯執行

1. Quick start

下面來自於官方文件

mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release .. && cmake --build . #release版本
cmake -DCMAKE_BUILD_TYPE=Debug .. && cmake --build .	 #debug版本

在類linux系統下編譯非常簡單,上面兩行就可以了

不過這裡編譯可能會遇到一些問題

比如我遇到了上面的問題,這是因為在third_party這個資料夾內缺少googletest和benchmark。

這裡手動下載一下這兩個檔案把這個目錄下的空檔案替換了就好了。然後在執行cmake即可

benchmark

googletest

上面是對應的github地址,直接點進去git clone就行,當然最近的github好像git clone總是出問題,我是直接下載zip然後替換過去的。

2. Quick test

新建一個資料夾叫my_test。新增一個新的cpp檔案run_test.cpp

隨後修改CMakeList.txt裡新增關於新測試檔案的規則。就可以了

我們準備寫一個執行的測試檔案,來建立一個db,隨後進行新增和刪除資料

#include <iostream>
#include <cassert>
#include "leveldb/db.h"
#include "leveldb/write_batch.h"
int main()
{
    // Open a database.
    leveldb::DB* db;
    leveldb::Options opts;
    opts.create_if_missing = true;
    leveldb::Status status = leveldb::DB::Open(opts, "../tmp/testdb", &db);
    assert(status.ok());

    // Write data.
    status = db->Put(leveldb::WriteOptions(), "name", "zxl");
    assert(status.ok());

    // Read data.
    std::string val;
    status = db->Get(leveldb::ReadOptions(), "name", &val);
    assert(status.ok());
    std::cout << val << std::endl;
   }

上面的程式碼就是我們在tmp檔案下建立一個testdb。然後寫入一條資料 key = "name" value = "zxl"。這個時候我們執行get操作就會得到我們剛才寫入的值

3. LevelDB的基本操作

關於資料庫的建立以及讀寫操作我們上面以及提過了

3.1 原子更新-Atomic Updates

下面的例子來自官方文件

考慮下面這一種情況,假如程式在Put key2 和 Delete key1兩個操作之間結束,那麼key1和key2這兩個不同的鍵值將儲存相同的值,這和我們的意願相違背。為了避免這種情況的出現,leveldb利用WriteBatch來進行具有原子性的更新。

td::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);
#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物件儲存對資料庫進行的一系列操作,然後在這一批次中按照順序執行這些操作。

注意:這裡先進行Delete操作,然後再進行Put操作。

WriteBatch類除了原子性的優勢外,也可以用於通過將大量個體變動放置在同一批次中而加速批量更新。

3.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()) {
  ...
}

好下面結合迭代和剛才學到的原子更新自己寫一個測試檔案

  1. 順序全體迭代
 // test Atomic Updates
    leveldb::WriteBatch batch;
    batch.Delete("name");
    batch.Put("name0", "zxl0");
    batch.Put("name1", "zxl1");
    batch.Put("name2", "zxl2");
    status = db->Write(leveldb::WriteOptions(), &batch);
    assert(status.ok());
    //Scan database.
    leveldb::Iterator *it = db->NewIterator(leveldb::ReadOptions());
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
        std::cout << it->key().ToString() << ": " <<
                  it->value().ToString() << std::endl;
    }
    assert(it->status().ok());

這段程式碼就會輸出我們寫入的三個k-v對

2. range 迭代

  for (it->Seek("name1"); it->Valid() && it->key().ToString() < "name2"; it->Next()) {
    std::cout << it->key().ToString() << ": " <<
              it->value().ToString() << std::endl;
  }
  delete it;

只會輸出key值位於[name1,"name2")之間的value也就只輸出了key=name1的value

當然還有其他的基本操作,但是這裡我們展示了增刪改查已經足夠了.後面會在梳理具體實現的時候在增加新的測試檔案

相關文章