Leveldb原始碼分析--9
6 SSTable之3
6.5 讀取sstable檔案
6.5.1 類層次
Sstable檔案的讀取邏輯在類Table中,其中涉及到的類還是比較多的,如圖6.5-1所示。
圖6.5-1
Table類匯出的函式只有3個,先從這三個匯出函式開始分析。其中涉及到的類(包括上圖中為畫出的)都會一一遇到,然後再一一拆解。
本節分析sstable的開啟邏輯,後面再分析key的查詢與資料遍歷。
6.5.2 Table::Open()
開啟一個sstable檔案,函式宣告為:
static Status Open(const Options& options, RandomAccessFile* file, uint64_tfile_size, Table** table);
這是Table類的一個靜態函式,如果操作成功,指標*table指向新開啟的表,否則返回錯誤。
要開啟的檔案和大小分別由引數file和file_size指定;option是一些選項;
下面就分析下函式邏輯:
S1 首先從檔案的結尾讀取Footer,並Decode到Footer物件中,如果檔案長度小於Footer的長度,則報錯。Footer的decode很簡單,就是根據前面的Footer結構,解析並判斷magic number是否正確,解析出meta index和index block的偏移和長度。
*table = NULL; if (size <Footer::kEncodedLength) { // 檔案太短 returnStatus::InvalidArgument("file is too short to be an sstable"); } charfooter_space[Footer::kEncodedLength]; // Footer大小是固定的 Slice footer_input; Status s = file->Read(size -Footer::kEncodedLength, Footer::kEncodedLength, &footer_input, footer_space); if (!s.ok()) return s; Footer footer; s =footer.DecodeFrom(&footer_input); if (!s.ok()) return s;
S2 解析出了Footer,我們就可以讀取index block和meta index了,首先讀取index block。
BlockContents contents;
Block* index_block = NULL;
if (s.ok()) {
s = ReadBlock(file, ReadOptions(),footer.index_handle(), &contents);
if (s.ok()) {
index_block = newBlock(contents);
}
}
這是通過呼叫ReadBlock完成的,下面會分析這個函式。
S3 已經成功讀取了footer和index block,此時table已經可以響應請求了。構建table物件,並讀取metaindex資料構建filter policy。如果option打開了cache,還要為table建立cache。
if (s.ok()) {
// 已成功讀取footer和index block: 可以響應請求了
Rep* rep = new Table::Rep;
rep->options = options;
rep->file = file;
rep->metaindex_handle =footer.metaindex_handle();
rep->index_block =index_block;
rep->cache_id =(options.block_cache ? options.block_cache->NewId() : 0);
rep->filter_data = rep->filter= NULL;
*table = new Table(rep);
(*table)->ReadMeta(footer);// 呼叫ReadMeta讀取metaindex
} else {
if (index_block) deleteindex_block;
}
到這裡,Table的開啟操作就已經為完成了。下面來分析上面用到的ReadBlock()和ReadMeta()函式.
6.5.3 ReadBlock()
前面講過block的格式,以及Block的寫入(TableBuilder::WriteRawBlock),現在我們可以輕鬆的分析Block的讀取操作了。
這是一個全域性函式,宣告為:
Status ReadBlock(RandomAccessFile* file, const ReadOptions& options, const BlockHandle&handle, BlockContents* result);
下面來分析實現邏輯:
S1 初始化結果result,BlockContents是一個有3個成員的結構體。
result->data = Slice();
result->cachable = false; // 無cache
result->heap_allocated =false; // 非heap分配
S2 根據handle指定的偏移和大小,讀取block內容,type和crc32值,其中常量kBlockTrailerSize=5= 1byte的type和4bytes的crc32。
Status s = file->Read(handle.offset(),handle.size() + kBlockTrailerSize, &contents, buf);
S3 如果option要校驗CRC32,則計算content + type的CRC32並校驗。
S4 最後根據type指定的儲存型別,如果是非壓縮的,則直接取資料賦給result,否則先解壓,把解壓結果賦給result,目前支援的是snappy壓縮。
另外,檔案的Read介面返回的Slice結果,其data指標可能沒有使用我們傳入的buf,如果沒有,那麼釋放Slice的data指標就是我們的事情,否則就是檔案來管理的。
if (data != buf) { // 檔案自己管理,cacheable等標記設定為false
delete[] buf;
result->data =Slice(data, n);
result->heap_allocated= result->cachable =false;
} else { // 讀取者自己管理,標記設定為true
result->data =Slice(buf, n);
result->heap_allocated= result->cachable = true;
}
對於壓縮儲存,解壓後的字串儲存需要讀取者自行分配的,所以標記都是true。
6.5.4 Table::ReadMeta()
解決完了Block的讀取,接下來就是meta的讀取了。函式宣告為:
void Table::ReadMeta(const Footer& footer)
函式邏輯並不複雜,
S1首先呼叫ReadBlock讀取meta的內容
if(rep_->options.filter_policy == NULL) return; // 不需要metadata
ReadOptions opt;
BlockContents contents;
if (!ReadBlock(rep_->file,opt, footer.metaindex_handle(), &contents).ok()) {
return; // 失敗了也沒報錯,因為沒有meta資訊也沒關係
}
S2 根據讀取的content構建Block,找到指定的filter;如果找到了就呼叫ReadFilter構建filter物件。Block的分析留在後面。
Block* meta = newBlock(contents);
Iterator* iter =meta->NewIterator(BytewiseComparator());
std::string key ="filter.";
key.append(rep_->options.filter_policy->Name());
iter->Seek(key);
if (iter->Valid() &&iter->key() == Slice(key)) ReadFilter(iter->value());
delete iter;
delete meta;
6.5.5 Table::ReadFilter()
根據指定的偏移和大小,讀取filter,函式宣告:
void ReadFilter(const Slice& filter_handle_value);
簡單分析下函式邏輯
S1 從傳入的filter_handle_value Decode出BlockHandle,這是filter的偏移和大小;
BlockHandle filter_handle;
filter_handle.DecodeFrom(&filter_handle_value);
S2 根據解析出的位置讀取filter內容,ReadBlock。如果block的heap_allocated為true,表明需要自行釋放記憶體,因此要把指標儲存在filter_data中。最後根據讀取的data建立FilterBlockReader物件。
ReadOptions opt;
BlockContents block;
ReadBlock(rep_->file, opt,filter_handle, &block);
if (block.heap_allocated)rep_->filter_data = block.data.data(); // 需要自行釋放記憶體
rep_->filter = newFilterBlockReader(rep_->options.filter_policy, block.data);
以上就是sstable檔案的讀取操作,不算複雜。