txdb 源码分析系列(三)

前一篇文章主要介绍了,txdb 的一个整体逻辑,本文将详细描述 txdb 模块与 leveldb 的交互,以及对 leveldb 的封装。

上一篇文章提到,在 dbwrapper.hCDBWrapper 是对 leveldb 的一个简单封装,所有要写入 leveldb 的东西都会调用 CDBWrapper 这个类,下面我们就来分析一下如何调用,以及 CDBWrapper 究竟实现了哪些逻辑。

CDBWrapper 的构造函数

class CDBWrapper {
public:
    CDBWrapper(const boost::filesystem::path &path, size_t nCacheSize,
               bool fMemory = false, bool fWipe = false,
               bool obfuscate = false);
    ~CDBWrapper();
};

CDBWrapper 主要有如下参数:

  • path -->系统中存储leveldb数据的位置
  • nCacheSize -->配置各种leveldb缓存设置
  • fMemory --> 如果为true,则使用leveldb的内存环境
  • fWipe --> 如果为true,则删除所有现有数据
  • obfuscate --> 如果为true,则通过简单的XOR存储数据; 如果为false,则与零字节数组进行异或运算

Read 函数

CDataStream 我们简单理解为一个内存 buffer,read()传入的key是经过泛化的一个K,经过 serialize 之后,相当于一个内存的 slice,ssKey << key;将这个内存 slice 写入内存 buffer-->sskey 中,之后使用 leveldb 的 Slice 方法写入,ssKey.data() 代表首地址 ,ssKey.size()表示其大小。

之后我们构造一个 string 的 strValue,调用 leveldb的 Get(),传入readoptions读选项,slKey 和构造好的 string 的 strValue

最后,我们通过 ssValue 去 leveldb 中拿到我们所要的 value,并经过 Xor(异或运算)进行解码之后,ssValue >> value; 把值塞回给 read 的 value 参数,这样,我们就通过确定的 key 拿到其对应的 value。

在上述过程中,我们发现,我们从leveldb中拿到的值是经过 Xor 编码之后写入的,我们最后读取出来需要经过 Xor 解码的过程,那么编码的过程在哪里呢?很明显,读的逆向操作是 write 操作。

template <typename K, typename V> bool Read(const K &key, V &value) const {
        CDataStream ssKey(SER_DISK, CLIENT_VERSION);
        ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
        ssKey << key;
        leveldb::Slice slKey(ssKey.data(), ssKey.size());

        std::string strValue;
        leveldb::Status status = pdb->Get(readoptions, slKey, &strValue);
        if (!status.ok()) {
            if (status.IsNotFound()) return false;
            LogPrintf("LevelDB read failure: %s\n", status.ToString());
            dbwrapper_private::HandleError(status);
        }
        try {
            CDataStream ssValue(strValue.data(),
                                strValue.data() + strValue.size(), SER_DISK,
                                CLIENT_VERSION);
            ssValue.Xor(obfuscate_key);
            ssValue >> value;
        } catch (const std::exception &) {
            return false;
        }
        return true;
    }

Write 函数

在这个函数中,我们能清楚的理解到,write函数首先调用CDBBatch的write函数,最后返回WriteBatch批量写函数,所以,我们写入leveldb的过程是一个批量写的过程。

template <typename K, typename V>
    bool Write(const K &key, const V &value, bool fSync = false) {
        CDBBatch batch(*this);
        batch.Write(key, value);
        return WriteBatch(batch, fSync);
    }

CDBBatch 的 write函数

write 函数的参数同样是泛化过的 K,V,我们通过 ssKey << key;ssValue << value;将key 和 value 塞进内存 buffer 中,最后通过 Xor 编码之后,调用 put 函数写入 leveldb 中,leveldb::Slice slKey(ssKey.data(), ssKey.size());依旧表示的是首地址以及大小,slValue同理。

class CDBBatch {
    friend class CDBWrapper;
private:
    CDataStream ssKey;
    CDataStream ssValue;
public:
    template <typename K, typename V> void Write(const K &key, const V &value) {
        ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
        ssKey << key;
        leveldb::Slice slKey(ssKey.data(), ssKey.size());

        ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE);
        ssValue << value;
        ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent));
        leveldb::Slice slValue(ssValue.data(), ssValue.size());

        batch.Put(slKey, slValue);
        // LevelDB serializes writes as:
        // - byte: header
        // - varint: key length (1 byte up to 127B, 2 bytes up to 16383B, ...)
        // - byte[]: key
        // - varint: value length
        // - byte[]: value
        // The formula below assumes the key and value are both less than 16k.
        size_estimate += 3 + (slKey.size() > 127) + slKey.size() +
                         (slValue.size() > 127) + slValue.size();
        ssKey.clear();
        ssValue.clear();
    }

WriteBatch函数

WriteBatch的参数fSync 判断write的过程是否为同步write。

bool WriteBatch(CDBBatch &batch, bool fSync = false);

Exists 函数

Exists 函数的参数是一个泛化后的 key,通过 key 可以判断该 key 所对应的 value 究竟是否在 leveldb 中存在。

template <typename K> bool Exists(const K &key) const {
        CDataStream ssKey(SER_DISK, CLIENT_VERSION);
        ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
        ssKey << key;
        leveldb::Slice slKey(ssKey.data(), ssKey.size());

        std::string strValue;
        leveldb::Status status = pdb->Get(readoptions, slKey, &strValue);
        if (!status.ok()) {
            if (status.IsNotFound()) return false;
            LogPrintf("LevelDB read failure: %s\n", status.ToString());
            dbwrapper_private::HandleError(status);
        }
        return true;
    }

Erase 函数

Erase函数与write函数同理,首先调用CDBBatch中的Erase函数,最后返回WriteBatch。不同的是 Erase 函数用来删除传入的 key 所定位到的 value。

template <typename K> bool Erase(const K &key, bool fSync = false) {
        CDBBatch batch(*this);
        batch.Erase(key);
        return WriteBatch(batch, fSync);
    }

CDBBatch 的 Erase函数

思路同理,同样是泛化后的key,写入内存buffer --->ssKey中,然后通过leveldb::Slice判断这个key的首地址和大小,调用batch.Delete(slKey);将其删除。
ssKey.clear();代表删除内存中的临时变量。

 template <typename K> void Erase(const K &key) {
        ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
        ssKey << key;
        leveldb::Slice slKey(ssKey.data(), ssKey.size());

        batch.Delete(slKey);
        // LevelDB serializes erases as:
        // - byte: header
        // - varint: key length
        // - byte[]: key
        // The formula below assumes the key is less than 16kB.
        size_estimate += 2 + (slKey.size() > 127) + slKey.size();
        ssKey.clear();
    }

Flush函数

Flush函数需要注意,它并不适用于LevelDB,不是我们想象中的将要写入的数据flush到leveldb中, 只是提供与BDB的兼容性:

bool Flush() { 
    return true; 
}

Sync 函数

sync函数用来判断批量写入的时候是否采用同步的方式:

bool Sync() {
        CDBBatch batch(*this);
        return WriteBatch(batch, true);
}

NewIterator 函数

该函数返回的返回值是CDBIterator:

CDBIterator *NewIterator() {
        return new CDBIterator(*this, pdb->NewIterator(iteroptions));
}

下面我们来分析一下CDBIterator

CDBIterator

CDBIterator 包含两个参数,_parent表示父CDBWrapper的实例;_piter表示原始的leveldb迭代器。

class CDBIterator {
private:
    const CDBWrapper &parent;
    leveldb::Iterator *piter;
public:
    CDBIterator(const CDBWrapper &_parent, leveldb::Iterator *_piter)
        : parent(_parent), piter(_piter){};
    ~CDBIterator();
}

换句话说, CDBIterator就是对leveldb::Iterator的封装,并且封装了如下函数来供使用

Seek

Seek 函数,通过泛化的 key 写入 ssKey,之后获取首地址以及大小,传入leveldb内部的seek函数来实现查找的功能。

template <typename K> void Seek(const K &key) {
        CDataStream ssKey(SER_DISK, CLIENT_VERSION);
        ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
        ssKey << key;
        leveldb::Slice slKey(ssKey.data(), ssKey.size());
        piter->Seek(slKey);
}

其他函数同理,主要实现有:

bool Valid(); //确认是否有效
void SeekToFirst(); //从头开始找
void Next();  //获取下一个元素
bool GetKey(K &key) //获取key
int GetKeySize() //获取key的size
bool GetValue(V &value) //获取value
int GetValueSize() //后去value的size

IsEmpty函数

IsEmpty函数返回一个bool类型,他的作用和她的字面意思是一样的,如果IsEmpty 管理的数据库不包含数据,则返回true。

bool IsEmpty();

EstimateSize函数

EstimateSize 函数用来预估从 key_begin 到 key_end 的范围占了文件系统多大的空间,pdb->GetApproximateSizes(&range, 1, &size);这个函数是 leveldb 内部的一个函数具体含义如下:

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

GetApproximateSizes(ranges, 2, sizes) 的第二个参数 2 就是 uint64_t sizes[2] 中的 2 ,因为在 cpp 中数组作为参数时会退化,所以第三个参数 sizes 只是一个指针,2 代表的是获取上面两个range所占的大小。

size_t EstimateSize(const K &key_begin, const K &key_end) const {
        CDataStream ssKey1(SER_DISK, CLIENT_VERSION),
            ssKey2(SER_DISK, CLIENT_VERSION);
        ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
        ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
        ssKey1 << key_begin;
        ssKey2 << key_end;
        leveldb::Slice slKey1(ssKey1.data(), ssKey1.size());
        leveldb::Slice slKey2(ssKey2.data(), ssKey2.size());
        uint64_t size = 0;
        leveldb::Range range(slKey1, slKey2);
        pdb->GetApproximateSizes(&range, 1, &size);
        return size;
    }

引用


本文由Copernicus 团对 冉小龙分析编写,转载无需授权。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,074评论 25 707
  • 个人翻译,转载请注明出处,谢谢! Adobe's Real Time Messaging Protocol 摘要 ...
    SniperPan阅读 2,734评论 1 17