1. 程式人生 > >leveldb之Put、Get操作

leveldb之Put、Get操作

一個簡單的leveldb使用示例如下:

#include <assert.h>
#include <iostream>
#include "leveldb/db.h"
#include "leveldb/env.h"
using namespace std;

#include <assert.h>
#include <iostream>
#include "leveldb/db.h"
#include "leveldb/env.h"

using namespace std;

int main()
{
leveldb::DB *db;
leveldb::Options ops;
ops.create_if_missing=true
; std::string dbpath="testdb"; leveldb::Status status=leveldb::DB::Open(ops,dbpath,&db); assert(status.ok()); string key="lili"; string value="hihi"; string res; status=db->Put(leveldb::WriteOptions(),key,value);//將Key-Value插入到leveldb中 status=db->Get(leveldb::ReadOptions(),key,&res);//根據Key值在leveldb中查詢其對應的Value,返回值存放在res中
cout<<res<<endl; delete db; return 0; }

Put操作

1、根據leveldb的原始碼可知,leveldb::DB是一個抽象基類,其中定義了一些純虛擬函式,包括Put和Get等,使其只能作為父類被繼承,而不能被例項化。leveldb::DBImpl繼承自該基類,因此在呼叫db->Put()時,首先呼叫的是leveldb::DBImpl->Put()方法:

Status DBImpl::Put(const WriteOptions& o, const Slice& key, const Slice& val) {
  return
DB::Put(o, key, val); }

在該方法中,只是簡單的呼叫父類leveldb::DB實現的Put()方法,leveldb::DBImpl只是提供一個介面。

2、以前以為純虛擬函式是隻能定義不能實現的,通過leveldb才發現原來純虛擬函式也是可以實現的,然後被顯式呼叫。leveldb::DB中的Put方法如下:

Status DB::Put(const WriteOptions& opt, const Slice& key, const Slice& value) {
  WriteBatch batch;
  batch.Put(key, value);//將key,value組織成一條記錄存放在batch中
  return Write(opt, &batch);//呼叫Write方法寫入記錄
}

一條記錄包含如下內容:
Type、Key、Value
當要插入記錄時,Type為kTypeValue,當要刪除記錄時,Type為kTypeDeletion,同時中每一個batch都有一個對當前批處理記錄資訊的統計(sequence(8位元組)和count(4位元組),共12位元組)
由此可見,當我們要刪除一個數據時,並不是直接從記憶體中刪除,而是插入一條帶有刪除標誌的記錄

在本例中要插入資料:key=”lili”; value=”hihi”;
由之前對WriterBatch的分析可知,得到的batch為:
01 00 00 00 00 00 00 00 01 00 00 00 (前8位元組表示是第一個batch,後4位元組表示此batch中只有一條記錄)
01(kTypeValue) 04(Key.size) 6C 69 6C 69(lili) 04(value.size) 68 69 68 69(hihi)
共12+1+1+4+1+4=23位元組=0x17

3、然後呼叫leveldb::DBImpl->Write()寫入記錄:

Status DBImpl::Write(const WriteOptions& options, WriteBatch* my_batch) {
  Writer w(&mutex_);
  w.batch = my_batch;
  w.sync = options.sync;//default is false
  w.done = false;

  MutexLock l(&mutex_);
  writers_.push_back(&w);//將writer加入任務佇列deque
  while (!w.done && &w != writers_.front()) {//未執行,且不在任務佇列首部,則等待
    w.cv.Wait();
  }
  if (w.done) {//已執行完畢,返回status
    return w.status;
  }

  //確保有Memtable和log檔案可以用於資料的寫入,對已寫滿的Memtable後臺排程Compaction
  Status status = MakeRoomForWrite(my_batch == NULL);
  uint64_t last_sequence = versions_->LastSequence();
  Writer* last_writer = &w;
  if (status.ok() && my_batch != NULL) {// 當batch為空時,是準備執行compactions操作,否則插入記錄
    WriteBatch* updates = BuildBatchGroup(&last_writer);//將任務佇列中所有的非同步任務組織在一起形成一個WriteBatch,一起批量寫入,可以極大的提升寫的效率
    WriteBatchInternal::SetSequence(updates, last_sequence + 1);
    last_sequence += WriteBatchInternal::Count(updates);

    {
      mutex_.Unlock();
      status = log_->AddRecord(WriteBatchInternal::Contents(updates));//呼叫fwrite將記錄寫入log檔案中
      bool sync_error = false;
      if (status.ok() && options.sync) {
        status = logfile_->Sync();//若設定了同步,則每次寫成功後都同步一次
      }
      if (status.ok()) {
        status = WriteBatchInternal::InsertInto(updates, mem_);//將updates中的記錄插入到mem_中
      }
      mutex_.Lock();
    }
    if (updates == tmp_batch_) tmp_batch_->Clear();
    versions_->SetLastSequence(last_sequence);
  }

  while (true) {//等待佇列中的其它任務
    Writer* ready = writers_.front();
    writers_.pop_front();
    if (ready != &w) {
      ready->status = status;
      ready->done = true;
      ready->cv.Signal();
    }
    if (ready == last_writer) break;
  }

  // Notify new head of write queue
  if (!writers_.empty()) {
    writers_.front()->cv.Signal();
  }
  return status;
}

通過呼叫log_->AddRecord()將記錄寫入到log中,由之前對log檔案寫操作的分析 可知,log檔案每次寫入一個batch的時候都會在前面為其加上7位元組的首部(CRC(4位元組)、記錄長度(2位元組)、type(1位元組)),其中CRC與type有關。
對於本例,length=23=0x17位元組
記錄為第一個,且不會佔滿當前block,因此type為 kFullType=0x01

最終在testdb目錄下的.log檔案中可看到如下結果:
00000000h: 66 5F 61 EE 17 00 01 01 00 00 00 00 00 00 00 01 ; f_a?………..
00000010h: 00 00 00 01 04 6C 69 6C 69 04 68 69 68 69 ; …..lili.hihi

向log檔案中寫入成功後,會呼叫WriteBatchInternal::InsertInto()將記錄插入到Memtable中,記錄在Memtable中是按照user_key升序,sequence降序排列的,這樣所有user _key相同的記錄都是聚集在一起的,且其中第一個就是最新的記錄,在後面的合併操作中,我們只需要處理相同user _key的第一條記錄即可,後面的都可以丟棄。
寫操作可能會導致Memtable寫滿,此時就需要將其轉化為immutable memtable,並在後臺呼叫合併操作將其轉化為SSTable。這是在MakeRoomForWrite()中完成的。

Get操作

db->Get()會呼叫DBImpl::Get(const ReadOptions& options,const Slice& key,std::string* value),查詢key對應的值存放到value中。

Status DBImpl::Get(const ReadOptions& options,
                   const Slice& key,
                   std::string* value) {
  Status s;
  MutexLock l(&mutex_);
  MemTable* mem = mem_;
  MemTable* imm = imm_;
  Version* current = versions_->current();
  mem->Ref();
  if (imm != NULL) imm->Ref();
  current->Ref();

  bool have_stat_update = false;
  Version::GetStats stats;

  // Unlock while reading from files and memtables
  {//主要的查詢操作,,
    mutex_.Unlock();
    // First look in the memtable, then in the immutable memtable (if any).
    LookupKey lkey(key, snapshot);
    if (mem->Get(lkey, value, &s)) {//首先在memtable中查詢
      // Done
    } else if (imm != NULL && imm->Get(lkey, value, &s)) {//若沒找到則繼續在immutable memtable中查詢
      // Done
    } else {//若還沒找到,則繼續在sstable中查詢
      s = current->Get(options, lkey, value, &stats);
      have_stat_update = true;
    }
    mutex_.Lock();
  }

  if (have_stat_update && current->UpdateStats(stats)) {
    MaybeScheduleCompaction();//可能進行campact操作
  }
  mem->Unref();
  if (imm != NULL) imm->Unref();
  current->Unref();
  return s;
}

由上可知,leveldb是按照Memtable、immutable memtable,SSTable的優先順序來進行查詢的。其中每個SSTable檔案都是有查詢次數限制的,在FileMetaData(記錄每個.sst檔案資訊的資料結構)中被初始化的。因此在SSTable中查詢時,也可能會觸發合併操作。
在查詢時用到了version,leveldb 使用 version 來儲存資料庫的狀態,Version 儲存了所有level的所有的SSTable檔案資訊,通過version->Current()可用來獲取”current” version(當前版本)。
在SSTable中查詢時,首先是從低到高遍歷當前Version中所有level中的所有檔案,逐個找出Key值範圍覆蓋了目標Key值的檔案,然後依次在這些檔案中進行查詢。
每一個數據庫中都有一個快取變數table_cache,用於快取最近使用的.sst檔案資訊,由於記憶體訪問速度比磁碟訪問速度快得多,這樣可以極大的提高查詢的效率。因此在查詢.sst檔案時,若.sst檔案不在快取中,則將其加入快取。

相關推薦

leveldbPutGet操作

一個簡單的leveldb使用示例如下: #include <assert.h> #include <iostream> #include "leveldb/db.h" #include "leveldb/env.h" using n

一起學HBase——總結HBase中的PUTGETDELETE操作

傳統的關係型資料庫有CRUD增刪改查操作,同樣對於NoSQL列式資料庫也有CRUD操作。本文對HBase中常用的Scan、GET、PUT、DELETE操作的用法做個總結。 Put操作 Put相當於傳統資料庫的add操作,就是在資料庫中新增一條或多條記錄。 Put操作分為兩類,一類是一次操作一條記錄,另外一類是

Java基礎HashMap原理分析(putgetresize)

在分析HashMap之前,先看下圖,理解一下HashMap的結構 ![圖片](https://images.cnblogs.com/cnblogs_com/kezhuang/1846266/o_200912094424企業微信20200912054321.png) 我手畫了一個圖,簡單描述一下HashMa

Postman接口測試POSTGET請求方法

進行 edit 包含 刪除 登錄 功能 AD dev 代理服務 一、基礎知識   1.HTTP的五種請求方法:GET, POST ,HEAD,OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。     GET請求:請求指定的頁面信息,並返回實

HashMap底層原理分析(putget方法)

return sta rec oca ati 技術分享 AI TP load 1、HashMap底層原理分析(put、get方法) HashMap底層是通過數組加鏈表的結構來實現的。HashMap通過計算key的hashCode來計算hash值,只要hashCode一樣

ConcurrentHashMap原始碼putget方法

以下ConcurrentHashMap類是基於jdk1.7來分析。 JDK1.7中ConcurrentHashMap是通過segments陣列和HashEntry陣列+連結串列來進行實現的。利用鎖分段技術,支援任務數量執行緒的讀和一定數量執行緒的寫。 我們看下ConcurrentHash

Postman介面測試POSTGET請求方法

一、基礎知識   1.HTTP的五種請求方法:GET, POST ,HEAD,OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。     GET請求:請求指定的頁面資訊,並返回實體主體。(通常用來接收資料)。     POST請求:向指定資源提交資

Android的Okhttp框架postget用法講解(落雨敏)

         前言:okhttp作為Android主流網路框架之一,但在近日okhttp網路請求卻比較火,主要原因是在谷歌官方在6.0以後在Android sdk已經移除了httpClient,加入我們okHttp。在常用的框架之中( vol

MySQL操作

一、庫操作 建立庫 create database 庫名(charset utf8 對庫的編碼進行設定,不寫就用預設值) 庫名可以由字母、數字、下劃線、特殊字元,要區分大小寫,唯一性,不能使用關鍵字,不能用數字開頭,最長128位 檢視資料庫注意:在cmd中輸入指令是不區分大小寫的 show datab

python自學筆記15例項繪圖dataframe操作讀寫csv,excle

用Python繪圖,藉助強大的numpy和matplotlib import numpy as np import matplotlib.pyplot as plt import pandas as pd x = np.linspace(0,1) y = np

js 面試題---陣列字串操作

1.  計算字串中某個字元的個數       var aaa='1231011211009181';       function count(str){            var arr=[];            for(var i=0;i<str.leng

Bottle例項—POSTGET再解

# -*- coding:  utf-8 -*- #!/usr/bin/python # filename: GETPOST_test.py # codedtime: 2014-9-20 19:07:04import bottledef check_login(username, password):    

PHP抓包curlfile_get_contents 操作例項

1.概述 file_get_contents函式多用來於來採集遠端伺服器上的內容,但使用file_get_contents函式之前我們在php.ini中是必須把allow_url_fopen開啟才行  cURL 是一個利用URL語法規定來傳輸檔案和資料的工

Restful的GETPOSTPUTDELETEDPATCH幾種操作

HTTP Method 與 CURD 資料處理操作對應 HTTP方法 資料處理 說明 POST Create 新增一個沒有id的資源 GET Read

volley getputpostdelete

Google released Android Volley Library around May/June 2013, which has been internally used by Google for some time. It is supposed to provide Fast N

Java程式設計HTTP的PostGetPutDelete

Http:在網路中,傳送檔案、資料需要遵循的一種協議。客戶端需要和伺服器端建立聯絡,就需要使用HTTP協議,保證伺服器端可以識別客戶端的請求,並把相應的資源發給客戶端使用。例如,訪問CSDN,在不同電腦的網頁上輸入https://www.csdn.net/即可看到CSDN的網

python_HTTP(實現GETPUTPOSTDELETE操作

第一篇博文,剛開始學Python,記錄一下自己實現的指令碼,若有不準確的地方請指正! 目標:用Python實現HTTP請求 工具:PyCharm2016.1.4 語言:Python3.4 1、HTTP操作:GET、DELETE、PUT、POST 2、測試環境:首先開啟伺服

四:Java字符串操作StringStringBuffer和StringBuilder

equal const wstring str asi 有時 string對象 階段 stringbu string是我們經經常使用到的一個類型,事實上有時候認為敲代碼就是在重復的操作字符串,這是C的特點,在java中。jdk非常好的封裝了關於字符串的操

Python全棧開發4內置函數文件操作和遞歸

開發 hang mon alien yun alpha err fdm ax1 %E5%AD%97%E8%8A%82%E5%BA%8F%E8%BD%AC%E6%8D%A2%E4%B8%8E%E7%BB%93%E6%9E%84%E4%BD%93%E4%BD%8D%E5%9F%

C/C++ 文件操作CreateFileReadFile和WriteFile

amp 列表 invalid bsp 功能 空間 out 系統 file 通常使用下列函數來通過Win系統來對外圍設備進行通信處理: ------------------------------- 1. CreateFile   這個函數的功能是創建或者打開一個文件或者I/