leveldb源码分析—Recover和Repair
leveldb作为一个KV存储引擎将数据持久化到磁盘,而对于一个存储引擎来说在存储过程中因为一些其他原因导致程序down掉甚至数据文件被破坏等都会导致程序不能按正常流程再次启动。那么遇到这些状况以后如何使程序最大程度的恢复数据就是非常重要的一项工作,leveldb也提供了这方面的工作。
首先来看recover,这是每一次启动数据库的时候都会呗调用到的流程。其功能是恢复数据库在运行中突然因为某些原因down掉而这个时候leveldb中的丢失的当前状态,以及memtable甚至immtable中还未持久化到SSTable中的数据。我们知道leveldb采用的是WAL的方式进行的,那么对于leveldb中的当前状态存储在manifest文件中,通过读取已经持久化的状态,然后在结合WAL总的信息就可以恢复到最新状态;而memtable和immtable中的数据恢复的主要就是将未持久化到SSTable的数据从Log中读取出来重做到memtable或者SSTable即可。整个恢复的流程如下:
Status DBImpl::Recover(VersionEdit* edit) {
env_->CreateDir(dbname_);
Status s = env_->LockFile(LockFileName(dbname_), &db_lock_);
if (!env_->FileExists(CurrentFileName(dbname_))) {
if (options_.create_if_missing) {
s = NewDB(); //生成全新的manifest和current文件
return s;
}
} s = versions_->Recover(); // 恢复当前version信息
s = env_->GetChildren(dbname_, &filenames); // 获取文件列表
versions_->AddLiveFiles(&expected);
for (size_t i = ; i < filenames.size(); i++) {
if (ParseFileName(filenames[i], &number, &type)) {
expected.erase(number); //删除存在的文件
if (type == kLogFile && ((number >= min_log) || (number == prev_log)))
logs.push_back(number); //存储当前已有的日志文件
}
}
if (!expected.empty()) { //如果文件缺失
return Status::Corruption(buf);
}
std::sort(logs.begin(), logs.end()); //排序日志文件
for (size_t i = ; i < logs.size(); i++) {
s = RecoverLogFile(logs[i], edit, &max_sequence); //重做日志操作
versions_->MarkFileNumberUsed(logs[i]);
}
if (s.ok()) {
if (versions_->LastSequence() < max_sequence) {
versions_->SetLastSequence(max_sequence);
}
}
}
下面我们首先来详细看看从manifest文件中恢复version信息的流程
Status VersionSet::Recover() {
// Read "CURRENT" file, which contains a pointer to the current manifest file
Status s = ReadFileToString(env_, CurrentFileName(dbname_), ¤t);
// 生成当前manifest的文件名
std::string dscname = dbname_ + "/" + current;
SequentialFile* file;
s = env_->NewSequentialFile(dscname, &file);
if (!s.ok()) {
return s;
}
// 各种初始化
{
// 逐个读取每个versionedit,并重建、记录响应的log_num等
while (reader.ReadRecord(&record, &scratch) && s.ok()) {
s = edit.DecodeFrom(record);
if (s.ok()) {
if (edit.has_comparator_ &&
edit.comparator_ != icmp_.user_comparator()->Name()) {
}
}
if (s.ok()) {
builder.Apply(&edit);
}
if (edit.has_log_number_) {
log_number = edit.log_number_;
have_log_number = true;
}
if (edit.has_prev_log_number_) {
prev_log_number = edit.prev_log_number_;
have_prev_log_number = true;
}
if (edit.has_next_file_number_) {
next_file = edit.next_file_number_;
have_next_file = true;
}
if (edit.has_last_sequence_) {
last_sequence = edit.last_sequence_;
have_last_sequence = true;
}
}
}
delete file;
file = NULL;
if (s.ok()) {
//恢复时获取到的num,seq等的判断和处理
MarkFileNumberUsed(prev_log_number);
MarkFileNumberUsed(log_number);
}
if (s.ok()) {
Version* v = new Version(this);
builder.SaveTo(v); // 存储一个全量的version
// 计算Compaction相关的数据
Finalize(v);
AppendVersion(v); // 将version加入version链表中,然后根据恢复得到的信息维护当前version的信息
manifest_file_number_ = next_file;
next_file_number_ = next_file + ;
last_sequence_ = last_sequence;
log_number_ = log_number;
prev_log_number_ = prev_log_number;
}
return s;
}
恢复了当前version的基本信息以后进行日志操作就可以恢复内存中的数据了,重做日志操作即是将日志中记录的操作读取出来,然后再将读取到的操作重新写入到leveldb中
Status DBImpl::RecoverLogFile(uint64_t log_number,
VersionEdit* edit,
SequenceNumber* max_sequence) {
while (reader.ReadRecord(&record, &scratch) &&
status.ok()) {
if (record.size() < ) ; //size不对
WriteBatchInternal::SetContents(&batch, record);
status = WriteBatchInternal::InsertInto(&batch, mem);//插入 if (last_seq > *max_sequence) {
*max_sequence = last_seq;
}
if (mem->ApproximateMemoryUsage() > options_.write_buffer_size) {
status = WriteLevel0Table(mem, edit, NULL); //写成SSTable
}
}
if (status.ok() && mem != NULL) {
status = WriteLevel0Table(mem, edit, NULL);
//写成SSTable
}
return status;
}
recover是leveldb中较为轻量级的恢复,主要是恢复leveldb未持久化到磁盘的数据和状态,每次启动时都会被调用到。那么如果我们的数据库受到了更大程度的伤害呢?比如SSTable文件损坏甚至丢失,manifest文件丢失。那么这个时候就必须使用下面的repair了,这个是当leveldb不能正常启动的时候需要手动进行的。
repair主要包含了如下几个流程来完成数据的修复:
FindFiles(); // 遍历数据目录下的文件解析文件名至manifest,日志,SSTable
ConvertLogFilesToTables(); // 解析日志文件生成为SSTble,主要用到了ConvertLogToTable,
ExtractMetaData(); /* 逐个遍历解析扫描到的SSTable并重新manifest的filemeta信息以供后面使用,
如果解析过程中有不能解析的数据,丢弃不能解析的数据并生成新的SSTable */
WriteDescriptor(); // 重置manifest文件号为1并生成最新的manifest,将其记录到current文件
ConvertLogFilesToTables主要用了一个和RecoverLogFile函数类似的ConvertLogToTable函数,他们的主要区别在RecoverLogFile在转化日志操作的时候使用的是leveldb的全局环境memtable和immtable,所以recover的数据有可能会持久化为SSTable,而有一部分则会留在内存中的memtable中;而ConvertLogToTable则是自己新建了一个局部的memtable来进行操作,然后将数据持久化为SSTable,这个过程从log恢复的数据一定会持久化为SSTable。
然后我们着重看下ExtractMetaData函数对扫描到的SSTable文件的逐个进行ScanTable的流程:
void ScanTable(uint64_t number) {
std::string fname = TableFileName(dbname_, number); //生成ldb文件名
Status status = env_->GetFileSize(fname, &t.meta.file_size); //获取文件大小,否则尝试sst文件后缀名
if (!status.ok()) {
// 略,和上面类似
}
if (!status.ok()) { // 如果都不能获取到文件大小,这个文件恢复失败,将其放入lost下
ArchiveFile(TableFileName(dbname_, number));
ArchiveFile(SSTTableFileName(dbname_, number));
return;
}
// 遍历文件获得metadata信息.
Iterator* iter = NewTableIterator(t.meta);
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
Slice key = iter->key();
if (!ParseInternalKey(key, &parsed)) {
continue;
}
counter++;
if (empty) { // 第一个key记录问文件最小key
empty = false;
t.meta.smallest.DecodeFrom(key);
} //否则替换上一次设置的最大key,这样遍历到最后一个时就保留的是最后的一个
t.meta.largest.DecodeFrom(key);
if (parsed.sequence > t.max_sequence) {
t.max_sequence = parsed.sequence;
}
}
// 如果遍历过程中解析key无失败,直接存储metadata,否则遍历SSTable生成新的SSTable,并删除旧的
if (status.ok()) {
tables_.push_back(t);
} else {
RepairTable(fname, t); // RepairTable archives input file.
}
}
如果数据有损坏,那么就必须重建SSTable,其过程如下:
void RepairTable(const std::string& src, TableInfo t) {
// 遍历SSTable生成新的SSTable,并删除旧的
// 生成新文件
std::string copy = TableFileName(dbname_, next_file_number_++);
WritableFile* file;
Status s = env_->NewWritableFile(copy, &file);
if (!s.ok()) {
return;
}
TableBuilder* builder = new TableBuilder(options_, file);
// Copy data.
Iterator* iter = NewTableIterator(t.meta);
int counter = ;
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
builder->Add(iter->key(), iter->value());
counter++;
}
delete iter;
ArchiveFile(src); //将旧文件移到lost下
if (counter == ) {
builder->Abandon(); // Nothing to save
} else {
s = builder->Finish();
if (s.ok()) {
t.meta.file_size = builder->FileSize();
}
}
delete builder;
builder = NULL;
if (s.ok()) {
s = file->Close();
}
delete file;
file = NULL;
if (counter > && s.ok()) {
std::string orig = TableFileName(dbname_, t.meta.number);
s = env_->RenameFile(copy, orig);
if (s.ok()) {
tables_.push_back(t);
}
}
if (!s.ok()) {
env_->DeleteFile(copy);
}
}
在recover和repair的过程中我们发现了version的维护和管理十分重要,原本不打算再对这部分进行分析,但是现在看来对其进行一下细致的梳理还是很有必要的,预计下一篇专门介绍这部分内容。
leveldb源码分析—Recover和Repair的更多相关文章
- leveldb源码分析--SSTable之block
在SSTable中主要存储数据的地方是data block,block_builder就是这个专门进行block的组织的地方,我们来详细看看其中的内容,其主要有Add,Finish和CurrentSi ...
- leveldb源码分析--WriteBatch
从[leveldb源码分析--插入删除流程]和WriteBatch其名我们就很轻易的知道,这个是leveldb内部的一个批量写的结构,在leveldb为了提高插入和删除的效率,在其插入过程中都采用了批 ...
- leveldb源码分析--Key结构
[注]本文参考了sparkliang的专栏的Leveldb源码分析--3并进行了一定的重组和排版 经过上一篇文章的分析我们队leveldb的插入流程有了一定的认识,而该文设计最多的又是Batch的概念 ...
- Leveldb源码分析--1
coming from http://blog.csdn.net/sparkliang/article/details/8567602 [前言:看了一点oceanbase,没有意志力继续坚持下去了,暂 ...
- leveldb源码分析--日志
我们知道在一个数据库系统中为了保证数据的可靠性,我们都会记录对系统的操作日志.日志的功能就是用来在系统down掉的时候对数据进行恢复,所以日志系统对一个要求可靠性的存储系统是极其重要的.接下来我们分析 ...
- leveldb源码分析之Slice
转自:http://luodw.cc/2015/10/15/leveldb-02/ leveldb和redis这样的优秀开源框架都没有使用C++自带的字符串string,redis自己写了个sds,l ...
- LevelDB源码分析--Cache及Get查找流程
本打算接下来分析version相关的概念,但是在准备的过程中看到了VersionSet的table_cache_这个变量才想起还有这样一个模块尚未分析,经过权衡觉得leveldb的version相对C ...
- leveldb源码分析--SSTable之TableBuilder
上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的. void TableBuilder::Add(const Slice& key, const Slice ...
- leveldb源码分析之内存池Arena
转自:http://luodw.cc/2015/10/15/leveldb-04/ 这篇博客主要讲解下leveldb内存池,内存池很多地方都有用到,像linux内核也有个内存池.内存池的存在主要就是减 ...
随机推荐
- SQL Server里等待统计(Wait Statistics)介绍
在今天的文章里我想详细谈下SQL Server里的统计等待(Wait Statistics),还有她们如何帮助你立即为什么你的SQL Server当前很慢.一提到性能调优,对我来说统计等待是SQL S ...
- node生成自定义命令(yargs/commander)
第一部分可以生成一个自定义命令,例如常见的”express”,yargs和commander则可以在生成的自定义命令上做扩展,yargs将命令扩展成类似express --l xx的形式;而comma ...
- SQL Server 诊断查询-(5)
Query #57 Buffer Usage -- Breaks down buffers used by current database by object (table, index) in t ...
- SQL年月日方面的查询信息
这是计算一个月第一天的SQL 脚本: SELECT DATEADD(mm, DATEDIFF(mm,0,getdate()), 0) --当月的第一天 SELECT DATEADD(mm, DAT ...
- 使用VS GDB扩充套件在VS上远端侦错Linux上的C/C++程序
在 Linux 上开发 C/C++ 程序,或许你会直接(本机或远端)登入 Linux,打开编辑器写完代码后,就用 gcc/g++ 来编译,遇到要除错(debug)的时候,则会选择使用 gdb 来进行除 ...
- Java知识点总结(不定时更新)
1.基于分代的垃圾收集算法 设计思路:把对象按照寿命长短来分组,分为年轻代和年老代,新创建的对象被分在年轻代,如果对象经过几次回收后仍然存活,那么再把这个对象划分到年老代.年老代的收集频率不像年轻代那 ...
- VS "15" 预览 5 中 VB 15 新增的功能
VS "15" 预览 5 给 VB 带来了更新.这次的更新内容有3个: * 值元组 ValueTuple这个功能能把一组计算结果成组返回.为了使用这个功能,我们要安装 System ...
- 序列中找子序列的dp
题目网址: http://codeforces.com/problemset/problem/414/B Description Mashmokh's boss, Bimokh, didn't lik ...
- CentOS6.5 FTP配置
一:安装vsftpd 查看是否已经安装vsftpd rpm -qa | grep vsftpd 如果没有,就安装,并设置开机启动 yum -y install vsftpd chkconfig vsf ...
- uploadify API
apifunctionjavascriptflashsecurity服务器 属性: uploader : uploadify.swf 文件的相对路径,该swf文件是一个带有文字BROWSE的按钮,点击 ...