DBImpl::Recover

把数据库恢复到上次退出的状态,
Recover的基本功能:如果存在表数据,则Load表数据,并对日志进行恢复,否则,根据flag创建新表或者返回错误

Recover的基本流程是:首先是处理创建flag,比如存在就返回失败等等;然后是尝试从已存在的sstable文件恢复db;最后如果发现有大于manifest文件记录的log编号的log文件,则需要回放log(回放的是上一次db关闭时还存在于mem和immem的记录,因为这些记录并没有持久化到磁盘sst文件中),更新db数据。回放期间db可能会dump新的level 0文件,因此需要把db元信息的变动记录到edit中返回

Status DBImpl::Recover(VersionEdit* edit, bool *save_manifest) {mutex_.AssertHeld();// 创建DB目录,不关注错误env_->CreateDir(dbname_);// 在DB目录下打开或创建(如果不存在)LOCK文件并锁定它,防止其他进程打开此表Status s = env_->LockFile(LockFileName(dbname_), &db_lock_);if (!s.ok()) {return s;}//判断CURRENT文件是否存在,current不存在说明数据库不存在if (!env_->FileExists(CurrentFileName(dbname_))) {//若CURRENT文件不存在,如果options选项设置了create_if_missing,则创建新的dbif (options_.create_if_missing) {s = NewDB();if (!s.ok()) {return s;}} else {//否则返回db不存在return Status::InvalidArgument(dbname_, "does not exist (create_if_missing is false)");}} else {if (options_.error_if_exists) {//如果数据库存在且设置了error_if_exists,则返回error_if_exists错误return Status::InvalidArgument(dbname_, "exists (error_if_exists is true)");}}// 如果运行到此,表明数据库已经存在,需要load,第一步是从MANIFEST文件中恢复VersionSet  s = versions_->Recover(save_manifest);if (!s.ok()) {return s;}SequenceNumber max_sequence(0);/*尝试从所有比manifest文件中记录的log要新的log文件中恢复(前一个版本可能会添加新的
log文件,却没有记录在manifest中)。这种情况出现在memtable或者immemtable还没来得
及写入sst文件db就挂掉了,因此需要从比manifest文件中记录的log要新的log文件中恢复*/
//prev_log是早前版本level_db使用的机制,现在以及不再使用,这里只是为了兼容const uint64_t min_log = versions_->LogNumber();const uint64_t prev_log = versions_->PrevLogNumber();std::vector<std::string> filenames;// 列出目录内的所有文件s = env_->GetChildren(dbname_, &filenames);if (!s.ok()) {return s;}std::set<uint64_t> expected;// 这个函数实质是获取仍然存活(仍然有效)的文件versions_->AddLiveFiles(&expected);uint64_t number;FileType type;std::vector<uint64_t> logs;//这里先找出所有满足条件的log文件:比manifest文件记录的log编号更新。for (size_t i = 0; 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()) {char buf[50];snprintf(buf, sizeof(buf), "%d missing files; e.g.",static_cast<int>(expected.size()));return Status::Corruption(buf, TableFileName(dbname_, *(expected.begin())));}//找到log文件后,首先排序,保证按照生成顺序,依次回放log。回放LOG时,记录被插入到memtable,如果超过write buffer,则还会dump出level 0的sst文件,        std::sort(logs.begin(), logs.end());for (size_t i = 0; i < logs.size(); i++) {// 此方法会将日志种每条记录的sequence num与max_sequence进行比较,以记录下最大的sequence num。 并把DB元信息的变动(sstable文件的变动)追加到edit中返回。  s = RecoverLogFile(logs[i], (i == logs.size() - 1), save_manifest, edit,&max_sequence);if (!s.ok()) {return s;}// 记录哪些文件编号已经被使用(可能回放的日志文件编号大于versions_的当前最大文件编号)versions_->MarkFileNumberUsed(logs[i]);}// 更新最大的全局事务序列号,因为log文件对应的memtable还没生成sst,便不会被写入到MANIFEST中。if (versions_->LastSequence() < max_sequence) {versions_->SetLastSequence(max_sequence);}return Status::OK();
}

DBImpl::RecoverLogFile()

该函数打开指定的log文件,回放日志,用于恢复db。期间可能会执行compaction,生产新的level 0sstable文件,记录文件变动到edit中。
它声明了一个局部类LogReporter以打印错误日志

Status DBImpl::RecoverLogFile(uint64_t log_number, bool last_log,bool* save_manifest, VersionEdit* edit,SequenceNumber* max_sequence) {mutex_.AssertHeld();//打开指定的log文件std::string fname = LogFileName(dbname_, log_number);SequentialFile* file;Status status = env_->NewSequentialFile(fname, &file);if (!status.ok()) {MaybeIgnoreError(&status);return status;}// Create the log reader.LogReporter reporter;//根据log文件句柄file创建log::Reader,准备读取log。log::Reader reader(file, &reporter, true/*checksum*/,0/*initial_offset*/);Log(options_.info_log, "Recovering log #%llu",(unsigned long long) log_number);// Read all the records and add to a memtablestd::string scratch;Slice record;WriteBatch batch;int compactions = 0;MemTable* mem = NULL;//依次读取所有的log记录,并插入到新生成的memtable中。这里使用到了批量更新接口WriteBatch,具体后面再分析。  while (reader.ReadRecord(&record, &scratch) &&status.ok()) {if (record.size() < 12) {// log数据错误,不满足最小长度12reporter.Corruption(record.size(), Status::Corruption("log record too small"));continue;}WriteBatchInternal::SetContents(&batch, record);// log内容设置到WriteBatch中,因为写日志时一条record就是一个WriteBatch的内容。if (mem == NULL) {mem = new MemTable(internal_comparator_);mem->Ref();}status = WriteBatchInternal::InsertInto(&batch, mem);// 插入到memtable中  MaybeIgnoreError(&status);if (!status.ok()) {break;}//恢复出kv对记录中最大的事务序列号const SequenceNumber last_seq =WriteBatchInternal::Sequence(&batch) +WriteBatchInternal::Count(&batch) - 1;if (last_seq > *max_sequence) {*max_sequence = last_seq;}if (mem->ApproximateMemoryUsage() > options_.write_buffer_size){// 如果mem的内存超过设置值write_buffer_size,则执行compactioncompactions++;*save_manifest = true;//一旦生成sst便会记录在edit,save_manifest设为ture告诉调用这edit有新的数据status = WriteLevel0Table(mem, edit, NULL);mem->Unref();mem = NULL;if (!status.ok()) {// Reflect errors immediately so that conditions like full// file-systems cause the DB::Open() to fail.break;}}}delete file;// See if we should keep reusing the last log file.//是否应该继续使用上次剩下的日志文件if (status.ok() && options_.reuse_logs && last_log && compactions == 0) {//如果option设置可以以及是回放的最后一个日志文件以及回放过程中没有生成新的sst文件才行assert(logfile_ == NULL);assert(log_ == NULL);assert(mem_ == NULL);uint64_t lfile_size;if (env_->GetFileSize(fname, &lfile_size).ok() &&env_->NewAppendableFile(fname, &logfile_).ok()) {Log(options_.info_log, "Reusing old log %s \n", fname.c_str());log_ = new log::Writer(logfile_, lfile_size);//继续使用该日志文件logfile_number_ = log_number;if (mem != NULL) {mem_ = mem;//如果刚才回放生成了memtable,就让db继续使用mem = NULL;} else {// mem can be NULL if lognum exists but was empty.//mem为空说明刚才的日志文件只是存在但没有存放记录。依然创建一个新的mem_mem_ = new MemTable(internal_comparator_);mem_->Ref();}}}if (mem != NULL) {//扫尾工作,走到这说明不继续使用旧日志文件,因此需要mem刷到新的sstable文件中if (status.ok()) {*save_manifest = true;status = WriteLevel0Table(mem, edit, NULL);}mem->Unref();}return status;
}

VersionSet::Recover

当正常运行期间,每当调用LogAndApply的时候,都会将VersionEdit作为一笔记录,追加写入到MANIFEST文件。我们知道VersionEdit就是记录数据库的一个版本到另一个版本间的sst文件变化情况以及各层合并点变化情况
注意,VersionEdit可以序列化,存进MANIFEST文件,同样道理,MANIFEST中可以将VersionEdit一个一个的重放出来。这个重放的目的,是为了得到当前的Version 以及VersionSet。
一般来讲,当打开的DB的时候,需要获得这种信息,而这种信息的获得,靠的就是所有VersionEdit 按照次序一一回放,生成当前的Version。

Status VersionSet::Recover(bool *save_manifest) {// Read "CURRENT" file, which contains a pointer to the current manifest file//读取"CURRENT"文件的内容到current,该文件内容包含了最新的Manifest文件名。std::string current;Status s = ReadFileToString(env_, CurrentFileName(dbname_), &current);if (!s.ok()) {return s;}if (current.empty() || current[current.size()-1] != '\n') {return Status::Corruption("CURRENT file does not end with newline");}//去掉文件名最后的'\n'current.resize(current.size() - 1);//获取完整的最新Manifest文件名std::string dscname = dbname_ + "/" + current;SequentialFile* file;//打开该Manifest文件s = env_->NewSequentialFile(dscname, &file);if (!s.ok()) {return s;}bool have_log_number = false;bool have_prev_log_number = false;bool have_next_file = false;bool have_last_sequence = false;uint64_t next_file = 0;uint64_t last_sequence = 0;uint64_t log_number = 0;uint64_t prev_log_number = 0;Builder builder(this, current_);{LogReporter reporter;reporter.status = &s;log::Reader reader(file, &reporter, true/*checksum*/, 0/*initial_offset*/);Slice record;std::string scratch;/*读取MANIFEST内容,MANIFEST是以log的方式写入的,因此这里调用的是log::Reader来读取。然后调用VersionEdit::DecodeFrom,从内容解析出VersionEdit对象,并将VersionEdit记录的改动应用到versionset中。读取MANIFEST中的log number, prev log number, nextfile number, last sequence。*/while (reader.ReadRecord(&record, &scratch) && s.ok()) {VersionEdit edit;s = edit.DecodeFrom(record);if (s.ok()) {//edit记录的user_comparator名一定要和当前打开数据库传入的user_comparator匹配//否则会出错,因为原来数据库生成的sst文件是按user_comparator排序的if (edit.has_comparator_ &&edit.comparator_ != icmp_.user_comparator()->Name()) {s = Status::InvalidArgument(edit.comparator_ + " does not match existing comparator ",icmp_.user_comparator()->Name());}}/*按照次序,讲Verison的变化量层层回放,最重会得到最终版本的Version*/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()) {if (!have_next_file) {s = Status::Corruption("no meta-nextfile entry in descriptor");} else if (!have_log_number) {s = Status::Corruption("no meta-lognumber entry in descriptor");} else if (!have_last_sequence) {s = Status::Corruption("no last-sequence-number entry in descriptor");}if (!have_prev_log_number) {prev_log_number = 0;}
//将读取到的log number, prev log number标记为已使用。MarkFileNumberUsed(prev_log_number);MarkFileNumberUsed(log_number);}if (s.ok()) {Version* v = new Version(this);/*通过回放所有的VersionEdit,得到最终版本的Version,存入v*/builder.SaveTo(v);// Install recovered versionFinalize(v);AppendVersion(v);manifest_file_number_ = next_file;next_file_number_ = next_file + 1;last_sequence_ = last_sequence;log_number_ = log_number;prev_log_number_ = prev_log_number;// 我们是否需要创建新的MANIFEST 文件/*随着时间的流逝,发生Compact的机会越来越多,Version跃升的次数越多,自然VersionEdit出现的次数越来越多,而每一个VersionEdit都会记录到MANIFEST,这必然会造成MANIFEST文件不断变大。*/if (ReuseManifest(dscname, current)) {// No need to save new manifest} else {*save_manifest = true;}}return s;
}

leveldb:数据库recover机制相关推荐

  1. 数据库锁机制为什么很重要?

    前言 在座的朋友们,你们的时间够用吗?想要成为一个成功的人吗?如果你们都有这样的疑惑,那就保持一刻谦虚的心态,跟着罗老师学习时间管理吧! 毕竟时间管理大师是一个用户访问多个资源,今天咱们来讲讲当多个用 ...

  2. mysql数据库锁定机制

    为什么80%的码农都做不了架构师?>>>    前言 为了保证数据的一致完整性,任何一个数据库都存在锁定机制.锁定机制的优劣直接应想到一个数据库系统的并发处理能力和性能,所以锁定机制 ...

  3. go的错误处理(异常捕获、处理):defer+recover机制处理错误、自定义异常(自定义错误)

    defer+recover机制处理错误 [1]展示错误: 发现:程序中出现错误/恐慌以后,程序被中断,无法继续执行. [2]错误处理/捕获机制: go中追求代码优雅,引入机制:defer+recove ...

  4. 乐观锁的颗粒度_MySql数据库锁机制详解

    概述 数据库锁定机制简单的来说,就是数据库为了保证数据的一致性与完整性,而使各种共享资源在被并发访问时变得有序所设计的一种规则.对于任何一种数据库来说都需要有相应的锁机制,所以MySQL也不能例外.M ...

  5. 云开发的数据库权限机制解读丨云开发101

    在使用云开发进行开发时,数据库权限是一个让不少人困扰的部分,四种数据库权限,到底是什么意思?其各自的权限.应用场景都是什么?大多数人对于这个机制,还是模糊的.为了帮助大家进行更好的开发,在涉及到具体的 ...

  6. 带你了解什么是MySQL数据库(八)数据库锁机制

    目录 数据库的锁机制 锁的分类 MySQL中的行级锁,表级锁,页级锁(粒度) 行级锁之共享锁与排他锁(级别) innodb存储引擎的锁机制 行级锁与表级锁区分 三种行锁算法 死锁问题 什么时候使用表锁 ...

  7. SQL Server数据库锁机制及类型

    SQL Server数据库锁机制及类型 [06-05 12:08:14]作者:责任编辑:heyaorong Microsoft SQL Server(以下简称SQL Server)作为一种中小型数据库 ...

  8. oracle强制拉库跳过recovery,学习笔记:Oracle坏块 数据库recover恢复时遇到坏块的解决思路案例...

    天萃荷净 recover遇到坏块处理本质探讨,记录一次在Oracle数据库recover恢复过程中,遇到数据库坏块无法恢复的解决思路案例 如果在还原出来的数据文件中有坏块,而归档日志和联机日志是正常的 ...

  9. AS400数据库事务处理机制

    AS400数据库事务处理机制 记录锁等待与程序处理机制 在多作业并发处理中为了保证事务的完整性,数据库通过记录锁.表锁来确保数据的正确性.一个作业在获得一条记录锁时,如果此记录已经处于锁状态,那么当前 ...

最新文章

  1. 我学shell程序的记录
  2. H2内嵌数据库的使用
  3. 人工智能再次参加高考:和作家比写作文,AI能打多少分?
  4. [tensorflow]win 环境 安装anacoda 4.8.2 和tensorflow 2.1.0
  5. IDEA访问不到SpringBoot项目webapp下的内容
  6. 工作流入门比较经典的文献
  7. java核心技术 下载 网盘_【资源分享】某宝买的40000GB游戏,有你想要的游戏哦,可单独保存或下载...
  8. c语言在线考试系统的需求分析,在线考试系统需求分析.doc
  9. Detours内联HOOK
  10. 试试Live Witer
  11. AppStore发布流程(从证书创建到app发布一站式)
  12. 笔记本win10宽带共享wifi热点教程
  13. 色差仪如何控制油漆涂层色差
  14. 编写一个单科学生成绩处理程序
  15. 以太网与 TCP/IP
  16. MeterSphere:超好用的开源测试平台
  17. 对当下很火的两大短视频平台 抖音 和 微视进行竞品分析
  18. javaweb项目案例:员工管理系统
  19. 笔记本卡顿不流畅是什么原因_简单解决电脑不流畅经常卡顿问题,非常有用快点看看...
  20. 图像处理计算机基本配置,图形图像工作的电脑配置推荐_DIY攒机硬件郎中-中关村在线...

热门文章

  1. 全景相机行业重点企业竞争格局分析及市场销售规模前景预测
  2. python的django看不懂_学Python Django学得很迷茫,怎么办?
  3. 字符设备驱动文件初始化与卸载(s5pv210/tiny210)
  4. Ansible之 AWX 创建管理项目的一些笔记
  5. P1506 拯救oibh总部题解
  6. 全球定位系统的组成与定位原理
  7. 非ie获取当前登录计算机用户,Web系统通过EXE文件实现读取客户电脑MAC等硬件信息且兼容非IE浏览器...
  8. 文字环绕图片效果实现
  9. C语言中你一知半解的‘\b‘
  10. win7硬盘安装工具_云骑士新版硬盘安装win7系统