前一篇文章主要介绍了,txdb 的一个整体逻辑,本文将详细描述 txdb 模块与 leveldb 的交互,以及对 leveldb 的封装。
上一篇文章提到,在 dbwrapper.h
的 CDBWrapper
是对 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
- 版本号:v0.16.0
本文由Copernicus 团对 冉小龙
分析编写,转载无需授权。
网友评论