Linux上leveldb的安装和使用中我们写了一个测试代码,内容如下:

#include "leveldb/db.h"
#include <cassert>
#include <iostream> using namespace std;
using namespace leveldb; int main() {
leveldb::DB *db;
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options, "testdb", &db);
assert(status.ok()); status = db->Put(WriteOptions(), "test", "Hello World!");
assert(status.ok());
string res;
status = db->Get(ReadOptions(), "test", &res);
assert(status.ok());
cout << res << endl; delete db;
return ;
}

其中db.h中定义了leveldb对外接口,定义了class DB,这个类只是一个接口类,leveldb::DB::Open操作来自leveldb源代码db文件夹下db_impl.cc文件,源码内容如下:

Status DB::Open(const Options& options, const std::string& dbname,
DB** dbptr) {
*dbptr = NULL; DBImpl* impl = new DBImpl(options, dbname);
impl->mutex_.Lock();
VersionEdit edit;
// Recover handles create_if_missing, error_if_exists
bool save_manifest = false;
Status s = impl->Recover(&edit, &save_manifest);
if (s.ok() && impl->mem_ == NULL) {
// Create new log and a corresponding memtable.
uint64_t new_log_number = impl->versions_->NewFileNumber();
WritableFile* lfile;
s = options.env->NewWritableFile(LogFileName(dbname, new_log_number),
&lfile);
if (s.ok()) {
edit.SetLogNumber(new_log_number);
impl->logfile_ = lfile;
impl->logfile_number_ = new_log_number;
impl->log_ = new log::Writer(lfile);
impl->mem_ = new MemTable(impl->internal_comparator_);
impl->mem_->Ref();
}
}
if (s.ok() && save_manifest) {
edit.SetPrevLogNumber(); // No older logs needed after recovery.
edit.SetLogNumber(impl->logfile_number_);
s = impl->versions_->LogAndApply(&edit, &impl->mutex_);
}
if (s.ok()) {
impl->DeleteObsoleteFiles();
impl->MaybeScheduleCompaction();
}
impl->mutex_.Unlock();
if (s.ok()) {
assert(impl->mem_ != NULL);
*dbptr = impl;
} else {
delete impl;
}
return s;
}

DB::Open函数创建的是一个DBImpl类,具体的操作由DBImpl类来处理.DBImpl构造函数如下:

DBImpl::DBImpl(const Options& raw_options, const std::string& dbname)
: env_(raw_options.env), // Env* const
internal_comparator_(raw_options.comparator), // const InternalKeyComparator
internal_filter_policy_(raw_options.filter_policy), // const InternalFilterPolicy
options_(SanitizeOptions(dbname, &internal_comparator_, // const Options
&internal_filter_policy_, raw_options)),
owns_info_log_(options_.info_log != raw_options.info_log), // bool
owns_cache_(options_.block_cache != raw_options.block_cache), // bool
dbname_(dbname), // const std::string
db_lock_(NULL), // FileLock*
shutting_down_(NULL), // port::AtomicPointer
bg_cv_(&mutex_), // port::CondVar
mem_(NULL), // MemTable*
imm_(NULL), // MemTable*
logfile_(NULL), // WritableFile*
logfile_number_(), // uint64_t
log_(NULL), // log::Writer*
seed_(), // uint32_t
tmp_batch_(new WriteBatch), // WriteBatch*
bg_compaction_scheduled_(false), // bool
manual_compaction_(NULL) { // ManualCompaction*
has_imm_.Release_Store(NULL); // Reserve ten files or so for other uses and give the rest to TableCache.
const int table_cache_size = options_.max_open_files - kNumNonTableCacheFiles;
table_cache_ = new TableCache(dbname_, &options_, table_cache_size); versions_ = new VersionSet(dbname_, &options_, table_cache_,
&internal_comparator_);
}

以下是成员变量的含义:

  • env_, 负责所有IO, 比如建立文件
  • internal_comparator_, 用来比较不同key的大小
  • internal_filter_policy_, 可自定义BloomFilter
  • options_, 对调用者传入的options进行调整
  • db_lock_, 文件锁
  • shutting_down_, 基于memory barrier的原子指针
  • bg_cv_, 多线程的条件
  • mem_ = memtable
  • imm = immemtable
  • tmp_batch_, 所有Put都是以batch写入, 这里建立临时的batch
  • manual_compaction_, 内部开发者调用时的参数, 可以不用理会
  • has_imm_, 用于判断是否有等待或者正在写入硬盘的immemtable
  • table_cache_, SSTable查询缓存
  • versions_, 数据库MVCC

接下来说一下memory barrier的问题.

程序经由编译器处理后变成了一条条的机器指令,每条指令又对应了更基础的几个硬件阶段,各个指令在这些硬件阶段里排队处理,组成流水线.由于不同指令对应的硬件阶段不同,导致有些时候流水线填不满,影响性能.因此硬件会主动的对指令顺序做微调,提升流水线的效率,也就是乱序执行.乱序执行是有规则的,在单线程下几乎不需要软件介入,但多线程就不一样了.比如说我们的程序这么写:

thread1:
: some_task = ;
: complete = true; thread2:
while (!complete) sleep();
printf("got %d\n", task_result);

程序本意是等任务完成后输出 got 123,但结果很可能是 got 0,原因就是 1 和 2 两条指令被调整了顺序,乱序不当.为解决这样的问题,cpu 增加了用于控制乱序执行的指令,称为内存栅栏(memory barrier).在 1、2 之间插入栅栏就会强制 cpu 不做乱序,从而保证程序的正确性.

在leveldb中port文件夹下atomic_pointer.h里出现了memory barrier.

首先是memory barrier的定义问题,leveldb提供了不同环境下的多种定义方式:

// Define MemoryBarrier() if available
// Windows on x86
#if defined(OS_WIN) && defined(COMPILER_MSVC) && defined(ARCH_CPU_X86_FAMILY)
// windows.h already provides a MemoryBarrier(void) macro
// http://msdn.microsoft.com/en-us/library/ms684208(v=vs.85).aspx
#define LEVELDB_HAVE_MEMORY_BARRIER // Mac OS
#elif defined(__APPLE__)
inline void MemoryBarrier() {
OSMemoryBarrier();
}
#define LEVELDB_HAVE_MEMORY_BARRIER // Gcc on x86
#elif defined(ARCH_CPU_X86_FAMILY) && defined(__GNUC__)
inline void MemoryBarrier() {
// See http://gcc.gnu.org/ml/gcc/2003-04/msg01180.html for a discussion on
// this idiom. Also see http://en.wikipedia.org/wiki/Memory_ordering.
__asm__ __volatile__("" : : : "memory");
}
#define LEVELDB_HAVE_MEMORY_BARRIER // Sun Studio
#elif defined(ARCH_CPU_X86_FAMILY) && defined(__SUNPRO_CC)
inline void MemoryBarrier() {
// See http://gcc.gnu.org/ml/gcc/2003-04/msg01180.html for a discussion on
// this idiom. Also see http://en.wikipedia.org/wiki/Memory_ordering.
asm volatile("" : : : "memory");
}
#define LEVELDB_HAVE_MEMORY_BARRIER // ARM Linux
#elif defined(ARCH_CPU_ARM_FAMILY) && defined(__linux__)
typedef void (*LinuxKernelMemoryBarrierFunc)(void);
// The Linux ARM kernel provides a highly optimized device-specific memory
// barrier function at a fixed memory address that is mapped in every
// user-level process.
//
// This beats using CPU-specific instructions which are, on single-core
// devices, un-necessary and very costly (e.g. ARMv7-A "dmb" takes more
// than 180ns on a Cortex-A8 like the one on a Nexus One). Benchmarking
// shows that the extra function call cost is completely negligible on
// multi-core devices.
//
inline void MemoryBarrier() {
(*(LinuxKernelMemoryBarrierFunc)0xffff0fa0)();
}
#define LEVELDB_HAVE_MEMORY_BARRIER // ARM64
#elif defined(ARCH_CPU_ARM64_FAMILY)
inline void MemoryBarrier() {
asm volatile("dmb sy" : : : "memory");
}
#define LEVELDB_HAVE_MEMORY_BARRIER // PPC
#elif defined(ARCH_CPU_PPC_FAMILY) && defined(__GNUC__)
inline void MemoryBarrier() {
// TODO for some powerpc expert: is there a cheaper suitable variant?
// Perhaps by having separate barriers for acquire and release ops.
asm volatile("sync" : : : "memory");
}
#define LEVELDB_HAVE_MEMORY_BARRIER // MIPS
#elif defined(ARCH_CPU_MIPS_FAMILY) && defined(__GNUC__)
inline void MemoryBarrier() {
__asm__ __volatile__("sync" : : : "memory");
}
#define LEVELDB_HAVE_MEMORY_BARRIER #endif

例如,对于X86内存模型而言,__asm__ __volatile__(" : : : "memory")的意思就是告诉编译器,memory在执行这端inline 汇编代码之后,已经失效了.也就是告诉编译器,不要在这个地方优化有关访问内存的指令.

在atomic_pointer.h中 memory barrier的使用方式如下:

// AtomicPointer built using platform-specific MemoryBarrier()
#if defined(LEVELDB_HAVE_MEMORY_BARRIER)
class AtomicPointer {
private:
void* rep_;
public:
AtomicPointer() { }
explicit AtomicPointer(void* p) : rep_(p) {}
inline void* NoBarrier_Load() const { return rep_; }
inline void NoBarrier_Store(void* v) { rep_ = v; }
inline void* Acquire_Load() const {
void* result = rep_;
MemoryBarrier();
return result;
}
inline void Release_Store(void* v) {
MemoryBarrier();
rep_ = v;
}
};

DBImpl中的has_imm_就是上面描述的atomic pointer.

再次回到db_impl.cc文件,读完:DB::Open的源码:

Status DB::Open(const Options& options, const std::string& dbname,
DB** dbptr) {
*dbptr = NULL; // 设置结果默认值, 指针传值
DBImpl* impl = new DBImpl(options, dbname);
impl->mutex_.Lock(); // 数据恢复时上锁, 禁止所有可能的后台任务
VersionEdit edit;
// Recover handles create_if_missing, error_if_exists
bool save_manifest = false;
Status s = impl->Recover(&edit, &save_manifest); // //调用DBImpl的恢复数据接口,读取元数据,恢复日志数据
if (s.ok() && impl->mem_ == NULL) {
// Create new log and a corresponding memtable. 复位
uint64_t new_log_number = impl->versions_->NewFileNumber();
WritableFile* lfile;
//创建新的写操作日志文件
s = options.env->NewWritableFile(LogFileName(dbname, new_log_number),
&lfile);
if (s.ok()) {
edit.SetLogNumber(new_log_number);
impl->logfile_ = lfile;
impl->logfile_number_ = new_log_number;
impl->log_ = new log::Writer(lfile);
impl->mem_ = new MemTable(impl->internal_comparator_);
impl->mem_->Ref();
}
}
if (s.ok() && save_manifest) {
edit.SetPrevLogNumber(); // No older logs needed after recovery.
edit.SetLogNumber(impl->logfile_number_);
s = impl->versions_->LogAndApply(&edit, &impl->mutex_); //添加VersionEdit,初始化时会将现在的VersionSet的状态写入新的manifest文件,并更新Current文件
}
if (s.ok()) {
impl->DeleteObsoleteFiles(); // 清理无用文件
impl->MaybeScheduleCompaction(); // 有写入就有可能要compact
}
impl->mutex_.Unlock(); // 初始化完毕
if (s.ok()) {
assert(impl->mem_ != NULL);
*dbptr = impl;
} else {
delete impl;
}
return s;
}

参考文献:

1.https://zhuanlan.zhihu.com/jimderestaurant?topic=LevelDB

2.https://www.zhihu.com/question/24301047

3.https://www.zhihu.com/question/49039919

4.http://blog.csdn.net/joeyon1985/article/details/47154249

LevelDB的源码阅读(二) Open操作的更多相关文章

  1. LevelDB的源码阅读(三) Put操作

    在Linux上leveldb的安装和使用中我们写了这么一段测试代码,内容以及输出结果如下: #include <iostream> #include <string> #inc ...

  2. LevelDB的源码阅读(四) Compaction操作

    leveldb的数据存储采用LSM的思想,将随机写入变为顺序写入,记录写入操作日志,一旦日志被以追加写的形式写入硬盘,就返回写入成功,由后台线程将写入日志作用于原有的磁盘文件生成新的磁盘数据.Leve ...

  3. LevelDB的源码阅读(三) Get操作

    在Linux上leveldb的安装和使用中我们写了这么一段测试代码,内容以及输出结果如下: #include <iostream> #include <string> #inc ...

  4. LevelDB的源码阅读(一)

    源码下载 git clone https://github.com/google/leveldb.git 项目结构 db/, 数据库逻辑 doc/, MD文档 helpers/, LevelDB内存版 ...

  5. xxl-job源码阅读二(服务端)

    1.源码入口 xxl-job-admin是一个简单的springboot工程,简单翻看源码,可以很快发现XxlJobAdminConfig入口. @Override public void after ...

  6. Spring 源码阅读 二

    程序入口: 接着上一篇博客中看完了在AnnotationConfigApplicationContext的构造函数中的register(annotatedClasses);将我们传递进来的主配置类添加 ...

  7. SparkConf加载与SparkContext创建(源码阅读二)

    紧接着昨天,我们继续开搞了啊.. 1.下面,开始创建BroadcastManager,就是传说中的广播变量管理器.BroadcastManager用于将配置信息和序列化后的RDD.Job以及Shuff ...

  8. JDK源码阅读(二) AbstractList

    package java.util; public abstract class AbstractList<E> extends AbstractCollection<E> i ...

  9. Struts2源码阅读(一)_Struts2框架流程概述

    1. Struts2架构图  当外部的httpservletrequest到来时 ,初始到了servlet容器(所以虽然Servlet和Action是解耦合的,但是Action依旧能够通过httpse ...

随机推荐

  1. Java序列化框架性能比較

    博客: http://colobu.com jvm-serializers提供了一个非常好的比較各种Java序列化的的測试套件. 它罗列了各种序列化框架. 能够自己主动生成測试报告. 我在AWS c3 ...

  2. 数学之美?编程之美?数学 + 编程= unbelievable 美!

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:Rusu 导语 相信大家跟我一样,偶尔会疑惑:曾经年少的时候学习过的那么多的复杂的数学函数,牛逼的化学方程式,各种物理原理.公式,到底有 ...

  3. IOS学习5——属性与成员变量

    [转]iOS中属性与成员变量的区别 ios中属性修饰符的作用 1. 属性用property声明 2. 简而言之,对于目前的ios开发,属性和成员变量的区别,完全可以不管. 3. 这个是历史原因造成的. ...

  4. Xamarin截取/删除emoji表情bug解决方案

    大家都知道,一个英文=1字节,一个汉字2字节,而一个emoji表情=4个字节,在有这三种混用的时候,比如app聊天界面,那么删除和截取便成了很头痛的事情. 问题描述 截取导致乱码,如下图: 解决方案 ...

  5. ArcGIS API for JavaScript 4.2学习笔记[7] 鹰眼(缩略图的实现及异步处理、Promise、回调函数、监听的笔记)

    文前说明:关于style就是页面的css暂时不做评论,因为官方给的例子的样式实在太简单了,照抄阅读即可. 这篇文章有着大量AJS 4.x版本添加的内容,如监听watch.Promise对象.回调函数. ...

  6. UVALive 4490 Help Bubu

    题目大意:有n本书,高度值域为8,现可以把k本书拿出来再放进去,相邻的.高度相同的书算作一块,最小化块的个数.n=100. 强烈建议大家不要在做完区间DP后做别的DP题:区间DP是整体考虑,而一般DP ...

  7. vue入坑总结

    1.Do not mount Vue to <html> or <body> - mount to normal elements instead. Vue2.x之后不推荐挂载 ...

  8. INITTAB 配置文件

    Inittab 文件详解       init的进程号是1(ps -aux | less),从这一点就能看出,init进程是系统所有进程的起点,Linux在完成核内引导以后,就开始运行init程序. ...

  9. Golang中的坑二

    Golang中的坑二 for ...range 最近两周用Golang做项目,编写web服务,两周时间写了大概五千行代码(业务代码加单元测试用例代码).用Go的感觉很爽,编码效率高,运行效率也不错,用 ...

  10. struts2 使用filter解决中文乱码问题

    1.编写fliter的代码 import java.io.IOException;import javax.servlet.Filter;import javax.servlet.FilterChai ...