DataBlock属于SSTable中Block的一种,关于DataBlock与SSTable的关系,可点此链接查看SSTable结构说明。
本篇主要是对DataBlock的读写流程解读。这里为便于理解,则将DataBlock格式再次放出:
写流程很简单,就是按照DataBlock格式去封装。
namespace leveldb {
struct Options;
class BlockBuilder {
public:
explicit BlockBuilder(const Options* options);
BlockBuilder(const BlockBuilder&) = delete;
BlockBuilder& operator=(const BlockBuilder&) = delete;
// Reset the contents as if the BlockBuilder was just constructed.
void Reset();
// REQUIRES: Finish() has not been called since the last call to Reset().
// REQUIRES: key is larger than any previously added key
void Add(const Slice& key, const Slice& value);
// Finish building the block and return a slice that refers to the
// block contents. The returned slice will remain valid for the
// lifetime of this builder or until Reset() is called.
Slice Finish();
// Returns an estimate of the current (uncompressed) size of the block
// we are building.
size_t CurrentSizeEstimate() const;
// Return true iff no entries have been added since the last Reset()
bool empty() const { return buffer_.empty(); }
private:
<!写KV时的一些参数选项,主要用到了参数:
1、block_restart_interval(重启点间隔);
2、comparator(KV比较)。
>
const Options* options_;
std::string buffer_; // Destination buffer
std::vector<uint32_t> restarts_; // Restart points
<!用于统计插入的KV个数,便于和重启点间隔比较,
进而决定是不是要新设置一个重启点。
>
int counter_; // Number of entries emitted since restart
bool finished_; // Has Finish() been called?
std::string last_key_;
};
} // namespace leveldb
namespace leveldb {
<!构建一个组织DataBlock格式方法类>
BlockBuilder::BlockBuilder(const Options* options)
: options_(options), restarts_(), counter_(0), finished_(false) {
assert(options->block_restart_interval >= 1);
restarts_.push_back(0); // First restart point is at offset 0
}
<!重置,主要把用于生产DataBlock的一些临时变量重置>
void BlockBuilder::Reset() {
buffer_.clear();
restarts_.clear();
restarts_.push_back(0); // First restart point is at offset 0
counter_ = 0;
finished_ = false;
last_key_.clear();
}
<!获取DataBlock的大致大小,DataBlock由三部分组成:
1、存储KV数据的buffer_;
2、存储重启点的数组restart_[],每个重启点类型占4Byte;
3、表示重启点个数的num_restarts_,占4Byte。
>
size_t BlockBuilder::CurrentSizeEstimate() const {
return (buffer_.size() + // Raw data buffer
restarts_.size() * sizeof(uint32_t) + // Restart array
sizeof(uint32_t)); // Restart array length
}
<!表示完成一个DataBlock,这里就是在已存有KV的buffer_尾部追加
重启点restart[]和表示重启点个数的restart.size()。
>
Slice BlockBuilder::Finish() {
// Append restart array
for (size_t i = 0; i < restarts_.size(); i++) {
PutFixed32(&buffer_, restarts_[i]);
}
PutFixed32(&buffer_, restarts_.size());
finished_ = true;
return Slice(buffer_);
}
<!这里就是往DataBlock中添加一个key。
上层已保证添加的Key都是有序而且是递增的。
>
void BlockBuilder::Add(const Slice& key, const Slice& value) {
Slice last_key_piece(last_key_);
assert(!finished_);
<!这里要说下重启点间隔参数options_->block_restart_interval,
我们知道DataBlock为压缩空间,先将一个Entry的完整key保存下来,
后续先添加的Entry的key只保存与前一个key不同的部分。而多少个key之后
再保存一个完整的key,则就是靠重启点间隔参数来控制的。
>
<!两个重启点间隔直接的KV个数conter_肯定是 <= 重启点间隔>
assert(counter_ <= options_->block_restart_interval);
<!第一次进来,或者后续进来的key都是大于前一个key的>
assert(buffer_.empty() // No values yet?
|| options_->comparator->Compare(key, last_key_piece) > 0);
size_t shared = 0;
<!若存储KV个数还未达到重启点个数要求,
则找出新Key与上一个Key的相同部分个数,
用shared来统计
>
if (counter_ < options_->block_restart_interval) {
// See how much sharing to do with previous string
const size_t min_length = std::min(last_key_piece.size(), key.size());
while ((shared < min_length) && (last_key_piece[shared] == key[shared])) {
shared++;
}
} else {
<!表示设置一个重启点,其实就是将buffer_中的接下来要存的
完整key的offset写入到restart_中,BlockBuilder类构造或者
调用Reset()方法时,已默认往restarts_中存入了重启点偏移位0。
>
// Restart compression
restarts_.push_back(buffer_.size());
counter_ = 0;
}
<!这里求出前后两个key不相同的部分,如果是新设置的重启点,
shared值为0,也就是说保存一个完整的key。
>
const size_t non_shared = key.size() - shared;
<!下面流程很好懂了,写各个字段值>
// Add "" to buffer_
PutVarint32(&buffer_, shared);
PutVarint32(&buffer_, non_shared);
PutVarint32(&buffer_, value.size());
// Add string delta to buffer_ followed by value
buffer_.append(key.data() + shared, non_shared);
buffer_.append(value.data(), value.size());
<!将当前新写入的key保存为last_key,
同将counter_++,用于重启点间隔判断。
>
// Update state
last_key_.resize(shared);
last_key_.append(key.data() + shared, non_shared);
assert(Slice(last_key_) == key);
counter_++;
}
} // namespace leveldb
读流程其实就是去按DataBlock格式去解析DataBlock。
namespace leveldb {
struct BlockContents;
class Comparator;
class Block {
public:
<!BlockContents就是DataBlock>
// Initialize the block with the specified contents.
explicit Block(const BlockContents& contents);
Block(const Block&) = delete;
Block& operator=(const Block&) = delete;
~Block();
size_t size() const { return size_; }
Iterator* NewIterator(const Comparator* comparator);
private:
class Iter;
uint32_t NumRestarts() const;
const char* data_;
<!size_就是DataBlock的大小>
size_t size_;
<!重启点数组在DataBlock中起始偏移位>
uint32_t restart_offset_; // Offset in data_ of restart array
<!表示Block是否管理此内存,
如果为true,则Block自己去释放内存。
>
bool owned_; // Block owns data_[]
};
} // namespace leveldb
namespace leveldb {
<!data_是block首地址,size_是block的大小。
一个block由block-data、block-restart、block-numRestart
三部分组成,numRestart占用4个字节,所以这里要求出numRestart
个数,就是直接解码最后是个字节。
>
inline uint32_t Block::NumRestarts() const {
<!numRestart是uint32_t表示,所以至少这么大>
assert(size_ >= sizeof(uint32_t));
return DecodeFixed32(data_ + size_ - sizeof(uint32_t));
}
<!
这里的BlockContents中的data就是由BlockBuilder生成的Block格式数据。
heap_allocated表示是否由使用BlockContents的调用者来释放内存。
>
Block::Block(const BlockContents& contents)
: data_(contents.data.data()),
size_(contents.data.size()),
owned_(contents.heap_allocated) {
<!1、numRestart都是一个uint32_t大小了,所以size_ 都小于一个4字节单元那就是异常了,命size_ = 0>
if (size_ < sizeof(uint32_t)) {
size_ = 0; // Error marker
} else {
<!2、每个restart[]都是一个uint32_t类型,减去一个uint32_t大小的numRestart大小,估算出restart[]最大个数,如果已存在的numRestart都大于这个
最大值,则认为是异常的,命size_ 为0>
size_t max_restarts_allowed = (size_ - sizeof(uint32_t)) / sizeof(uint32_t);
if (NumRestarts() > max_restarts_allowed) {
// The size is too small for NumRestarts()
size_ = 0;
} else {
<!计算出restart[]在整个block中的偏移位offset.>
restart_offset_ = size_ - (1 + NumRestarts()) * sizeof(uint32_t);
}
}
}
<!析构时判断下是否要删除Block>
Block::~Block() {
if (owned_) {
delete[] data_;
}
}
// Helper routine: decode the next block entry starting at "p",
// storing the number of shared key bytes, non_shared key bytes,
// and the length of the value in "*shared", "*non_shared", and
// "*value_length", respectively. Will not dereference past "limit".
//
// If any errors are detected, returns nullptr. Otherwise, returns a
// pointer to the key delta (just past the three decoded values).
<!先看一个Entry的结构:
_____________________________________________________________
| *shared | *non_shared | *value_length | key_delta | value |
_____________________________________________________________
其中*shared、*non_shared、*value_length这三个都是varint32编码,
每个字段最少占用一个字节。
由上面的结构说明之后,看下面的解析很清楚了。
>
static inline const char* DecodeEntry(const char* p, const char* limit,
uint32_t* shared, uint32_t* non_shared,
uint32_t* value_length) {
if (limit - p < 3) return nullptr;
*shared = reinterpret_cast<const uint8_t*>(p)[0];
*non_shared = reinterpret_cast<const uint8_t*>(p)[1];
*value_length = reinterpret_cast<const uint8_t*>(p)[2];
if ((*shared | *non_shared | *value_length) < 128) {
// Fast path: all three values are encoded in one byte each
p += 3;
} else {
if ((p = GetVarint32Ptr(p, limit, shared)) == nullptr) return nullptr;
if ((p = GetVarint32Ptr(p, limit, non_shared)) == nullptr) return nullptr;
if ((p = GetVarint32Ptr(p, limit, value_length)) == nullptr) return nullptr;
}
<!p此时指向key_delta,limit指向重启点的起始处,所有二者的距离
至少能容下一个key_delta + Value的大小。否则异常。
>
if (static_cast<uint32_t>(limit - p) < (*non_shared + *value_length)) {
return nullptr;
}
return p;
}
<!这个创建一个遍历Block的迭代器,Block中每个K-V就是一个Entry>
class Block::Iter : public Iterator {
private:
<!KV比较器>
const Comparator* const comparator_;
<!Block首地址>
const char* const data_; // underlying block contents
<!restart[]数组在Block中的偏移首地址>
uint32_t const restarts_; // Offset of restart array (list of fixed32)
<!restart点的个数>
uint32_t const num_restarts_; // Number of uint32_t entries in restart array
// current_ is offset in data_ of current entry. >= restarts_ if !Valid
<!当前指向的Entry在Block中的偏移>
uint32_t current_;
<!表示当前restart所在restart[]中的index>
uint32_t restart_index_; // Index of restart block in which current_ falls
<!当前current_对应的KV>
std::string key_;
<!这个Value有多个用途,
1、当前key对应的value,
2、指向当前Entry的一个指针
>
Slice value_;
<!操作状态记录>
Status status_;
<!比较两个K大小,a > b则返回>0, a<b则返回<0, a=b则返回=0>
inline int Compare(const Slice& a, const Slice& b) const {
return comparator_->Compare(a, b);
}
<!指向下一个Entry>
// Return the offset in data_ just past the end of the current entry.
inline uint32_t NextEntryOffset() const {
return (value_.data() + value_.size()) - data_;
}
<!解码返回索引为index的restart的值>
uint32_t GetRestartPoint(uint32_t index) {
assert(index < num_restarts_);
return DecodeFixed32(data_ + restarts_ + index * sizeof(uint32_t));
}
<!跳到index对应的restart所指向的值,同时要将当前key和value都置位>
void SeekToRestartPoint(uint32_t index) {
key_.clear();
restart_index_ = index;
// current_ will be fixed by ParseNextKey();
// ParseNextKey() starts at the end of value_, so set value_ accordingly
uint32_t offset = GetRestartPoint(index);
value_ = Slice(data_ + offset, 0);
}
public:
<!构造一个迭代器:
1、指定比对器comparator_;
2、指定迭代器所指向的基地址data_;
3、Block中重启点的偏移restarts_;
4、重启点的个数num_restarts_;
5、当前key在Block中的偏移默认为restart_;
6、当前重启点的索引默认为最后一个即num_restarts_。
>
Iter(const Comparator* comparator, const char* data, uint32_t restarts,
uint32_t num_restarts)
: comparator_(comparator),
data_(data),
restarts_(restarts),
num_restarts_(num_restarts),
current_(restarts_),
restart_index_(num_restarts_) {
assert(num_restarts_ > 0);
}
<!当前key的偏移current_要小于重启点的偏移restarts_才是合法的,
虽然构造迭代器Iter的时候赋值为相等,当后续使用时应该会处理。
>
bool Valid() const override { return current_ < restarts_; }
Status status() const override { return status_; }
<!获取当前key,当要进行合法性判断>
Slice key() const override {
assert(Valid());
return key_;
}
<!Value值>
Slice value() const override {
assert(Valid());
return value_;
}
<!Next key,由ParseNextKey负责实现>
void Next() override {
assert(Valid());
ParseNextKey();
}
<!指向前一个节点,大体思路如下:
1、先找到一个重启点,这个重启点指向的entry对应的offset小于当前的key的偏移current_。
2、跳到重启点指向的Entry。
3、然后在当前指向的entry循环向下遍历,找到当前current_前一个偏移Entry。
>
void Prev() override {
assert(Valid());
<!循环查找小于当前current偏移的重启点>
// Scan backwards to a restart point before current_
const uint32_t original = current_;
while (GetRestartPoint(restart_index_) >= original) {
<!如果重启点指向的entry还是>=当前的Entry偏移,
同时重启点的索引已经为0,表明前面以无Entry,
当前Entry就是第一个Entry,这里就把current_和
restart_index重新置位。
>
if (restart_index_ == 0) {
// No more entries
current_ = restarts_;
restart_index_ = num_restarts_;
return;
}
restart_index_--;
}
SeekToRestartPoint(restart_index_);
<!这里就是循环解析,直到找到当前Entry的前一个Entry,
ParseNextKey解析出下一个Key-Value,NextEntryOffset在
已解析出来的KV基础上通过Value addr + Value Size来进一步
判断下一个Entry是否就是original所指向的entry。
>
do {
// Loop until end of current entry hits the start of original entry
} while (ParseNextKey() && NextEntryOffset() < original);
}
<!Seek到Block中大于等于target的Entry,实现方式如下:
1、先通过二分法在Block的restart[]数组中找到最近的
一个restart重启点,这个重启点指向的key < target。
2、然后从这个restart重启点指向的key向下遍历,找到
大于等于target的Entry。
>
void Seek(const Slice& target) override {
// Binary search in restart array to find the last restart point
// with a key < target
uint32_t left = 0;
uint32_t right = num_restarts_ - 1;
<!这里判断left >= right就跳出循环,
然后沿着left指向的值往下寻找。
>
while (left < right) {
uint32_t mid = (left + right + 1) / 2;
uint32_t region_offset = GetRestartPoint(mid);
uint32_t shared, non_shared, value_length;
<!解码出restart指向的Entry的key值,且值不为空,
另外restart指向的key值是不共享的,也就是说shared值为0,
所以如果二者都不满足,则报错。
>
const char* key_ptr =
DecodeEntry(data_ + region_offset, data_ + restarts_, &shared,
&non_shared, &value_length);
if (key_ptr == nullptr || (shared != 0)) {
CorruptionError();
return;
}
<!
1、如果解码出的restart指向的key值小于target,但这个restart
接下来的数据是有可能>=target的,所以left = mid,然后继续查找。
2、如果解码出的restart指向的key值大于等于target,那这个restart
后续的的值肯定都是大于等于target的,所以就直接跳过这个mid指向
的值,将mid - 1 赋值与rigth,然后继续查找。
>
Slice mid_key(key_ptr, non_shared);
if (Compare(mid_key, target) < 0) {
// Key at "mid" is smaller than "target". Therefore all
// blocks before "mid" are uninteresting.
left = mid;
} else {
// Key at "mid" is >= "target". Therefore all blocks at or
// after "mid" are uninteresting.
right = mid - 1;
}
}
<!跳到left重启点指向的Entry,然后线性往下搜索。>
// Linear search (within restart block) for first key >= target
SeekToRestartPoint(left);
while (true) {
if (!ParseNextKey()) {
return;
}
if (Compare(key_, target) >= 0) {
return;
}
}
}
<!跳到第一个Entry,ParseNextKey会解析出第一个Key和其对应的Value>
void SeekToFirst() override {
SeekToRestartPoint(0);
ParseNextKey();
}
<!跳到最后一个重启点指向的Entry,
然后循环向下解析比较,知道最后一个Entry。
>
void SeekToLast() override {
SeekToRestartPoint(num_restarts_ - 1);
while (ParseNextKey() && NextEntryOffset() < restarts_) {
// Keep skipping
}
}
private:
<!解析到错误的entry所要执行的动作。
好像所有的流程,只有解析异常了都会
将current_指向restart[]起始处,
restart_index为num_restarts_
>
void CorruptionError() {
current_ = restarts_;
restart_index_ = num_restarts_;
status_ = Status::Corruption("bad entry in block");
key_.clear();
value_.clear();
}
<!解析下一个Key>
bool ParseNextKey() {
<!1、求出下一个Key的offset>
current_ = NextEntryOffset();
<!2、计算出实际的地址>
const char* p = data_ + current_;
<!3、计算出重启点数组的实际地址>
const char* limit = data_ + restarts_; // Restarts come right after data
<!4、如果Entry地址>=limit这地址,表示已经无entry数据了,
返回false状态。
>
if (p >= limit) {
// No more entries to return. Mark as invalid.
current_ = restarts_;
restart_index_ = num_restarts_;
return false;
}
// Decode next entry
<!5、解析出下一个Entry值,并查询出其结构的各个值>
uint32_t shared, non_shared, value_length;
p = DecodeEntry(p, limit, &shared, &non_shared, &value_length);
if (p == nullptr || key_.size() < shared) {
CorruptionError();
return false;
} else {
key_.resize(shared);
key_.append(p, non_shared);
value_ = Slice(p + non_shared, value_length);
<!如果当前current_的偏移已经大于其对应重启点的下一个重启点所指向
的偏移位,则表示current_已不在当前重启点范围内了,所以需要将
当前current_重启点索引后移为下一个。个人感觉这里应该是为了解决
偏移的下一个Entry已跨到下一个重启点范围了,所以需要重新更新下
当前current_的重启点索引,但是这里用的判断条件是< current_,
也就是说当跨到下一个重启点指向的第一个Entry,不会立马更新current_当前索引,需要跨到下一个重启点的第二个Entry才会更新这个index。
不知道为什么不直接改成<=current_判断。
>
while (restart_index_ + 1 < num_restarts_ &&
GetRestartPoint(restart_index_ + 1) < current_) {
++restart_index_;
}
return true;
}
}
};
<!这就是新建一个迭代器,并做一些异常检测>
Iterator* Block::NewIterator(const Comparator* comparator) {
if (size_ < sizeof(uint32_t)) {
return NewErrorIterator(Status::Corruption("bad block contents"));
}
const uint32_t num_restarts = NumRestarts();
if (num_restarts == 0) {
return NewEmptyIterator();
} else {
return new Iter(comparator, data_, restart_offset_, num_restarts);
}
}
} // namespace leveldb
重启点间隔参数表示两个重启点之间的Key个数,原文中的说明如下:
// Number of keys between restart points for delta encoding of keys.
// This parameter can be changed dynamically. Most clients should
// leave this parameter alone.
int block_restart_interval = 16;
有两点要说明下: