比特币源码分析:txdb 模块(三)

栏目: 数据库 · 发布时间: 6年前

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

前一篇文章主要介绍了,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;
}

引用

  • 源码:bitcoin-abc( https://github.com/Bitcoin-ABC/bitcoin-abc )

  • 版本号:v0.16.0

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

HTML5秘籍(第2版)

HTML5秘籍(第2版)

[美] Matthew MacDonald / 李松峰、朱巍、刘帅 / 人民邮电出版社 / 2015-4 / 89.00元

不依赖插件添加音频和视频,构建适用于所有浏览器的播放页面。 用Canvas创建吸引人的视觉效果,绘制图形、图像、文本,播放动画,运行交互游戏。 用CSS3将页面变活泼,比如添加新奇的字体,利用变换和动画添加吸引人的效果。 设计更出色的Web表单,利用HTML5新增的表单元素更加高效地收集访客信息。 一次开发,多平台运行,实现响应式设计,创建适配桌面计算机、平板电脑和智能手机......一起来看看 《HTML5秘籍(第2版)》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器