leveldb自定义env

这篇具有很好参考价值的文章主要介绍了leveldb自定义env。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

由于项目需求,需要自定义LevelDB的env,也就是以块接口实现env中各个文件接口,在网上没找到类似的代码,就打算自己参照util/env_posix.cc实现一个简单的demo,等到功能实现差不多的时候,却发现leveldb有一个类似功能的代码helpers/memenv/memenv.cc,且各方面都写的比我好,故这篇博客主要记录实现过程中遇到的问题。
增加了四个文件 filesystem.h filesystem.c simple_env.h simple_env.c

代码链接:leveldb_env

修改记录

version0是自己写的版本,version1是我对照InMemoryEnv修改过的版本

主要的修改点在于:
1 头文件的放置
leveldb自定义env
刚开始我把自己定义的类都放在头文件中,而后放在include/leveldb,想要提供给测试程序使用,后面看InMemoryEnv发现并不需要这样,在程序使用自定义env时只需要Env*就可以了,并不需要那么多的实现细节,这也是虚函数的意义所在,故将头文件放在myenv目录下,env.h中添加函数声明

class LEVELDB_EXPORT Env {
  static Env* GetSimpleEnv();
}

在simle_env.cc中实现函数

Env* Env::GetSimpleEnv() { return SimpleEnv::GetInstance(); }

2 使用锁-引用计数保护数据结构

3 代码中的简单出错处理

4 不同文件类型的处理逻辑

NewSequentialFile: 文件不存在则返回错误
NewRandomAccessFile: 文件不存在则返回错误
NewWritableFile: 文件不存在则创建,存在则清空数据
NewAppendableFile: 文件不存在则创建

undefined reference to `typeinfo for leveldb::Env

编译测试程序的时候出现以下错误

/usr/bin/ld: /tmp/ccXgrLBz.o:(.data.rel.ro._ZTIN7leveldb9SimpleEnvE[_ZTIN7leveldb9SimpleEnvE]+0x10): undefined reference to `typeinfo for leveldb::Env'
/usr/bin/ld: /tmp/ccXgrLBz.o:(.data.rel.ro._ZTIN7leveldb18SimpleWritableFileE[_ZTIN7leveldb18SimpleWritableFileE]+0x10): undefined reference to `typeinfo for leveldb::WritableFile'
/usr/bin/ld: /tmp/ccXgrLBz.o:(.data.rel.ro._ZTIN7leveldb22SimpleRandomAccessFileE[_ZTIN7leveldb22SimpleRandomAccessFileE]+0x10): undefined reference to `typeinfo for leveldb::RandomAccessFile'
/usr/bin/ld: /tmp/ccXgrLBz.o:(.data.rel.ro._ZTIN7leveldb20SimpleSequentialFileE[_ZTIN7leveldb20SimpleSequentialFileE]+0x10): undefined reference to `typeinfo for leveldb::SequentialFile'

自然而然地,看到undefined我就使用nm命令列出了simple_env.o定义的符号,却发现没有我定义那些函数,我就认为问题出现在这里,实际上当时我将所有的实现都写在了头文件里,也就是说全都是内联函数,内联函数本来就不会出现在符号表中
简单测试:

class Test {
 public:
  int info() { return a; }  // 内联函数
  void print();             // 非内联函数

 private:
  int a;
};

void Test::print() {
  int a;
  int b = a + 1;
}
root@ubuntu ~/l/l/test (main)# g++ inline_test.cpp -c
root@ubuntu ~/l/l/test (main)# nm inline_test.o
0000000000000000 T _ZN4Test5printEv

C++内联函数的使用

而后调转方向,查看undefined reference to `typeinfo for leveldb::SequentialFile’错误信息的相关博客,找到了以下博客
Undefined Reference to Typeinfo
混用了no-RTTI代码和RTTI代码,查看LevelDB的CMakeLists.txt,发现确实禁用了RTTI

  # Disable RTTI.
  string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")

故编译测试程序时加上编译选项-fno-rtti

g++ test.cpp -o test -g -pthread -lleveldb -fno-rtti

编译成功!

阅读RTTI的相关博客
Is there life without RTTI or How we wrote our own dynamic_cast
谷歌C++风格指南 运行时类型识别
运行时类型信息RTTI

后面我调整了头文件的位置,只暴露Env* GetSimpleEnv()接口,即使不加-fno-rtti也不报错。

cmake相关

刚开始我是手动拷贝头文件与库文件,比较麻烦

cp build/libleveldb.a /usr/local/lib/
cp -r include/leveldb/ /usr/local/include/

看CMakeLists.txt里发现有install,故直接make install安装头文件与库文件

if(LEVELDB_INSTALL)
  install(TARGETS leveldb
    EXPORT leveldbTargets
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
  )
  install(
    FILES
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/c.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/cache.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/comparator.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/db.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/dumpfile.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/env.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/export.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/filter_policy.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/iterator.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/options.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/slice.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/status.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/table_builder.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/table.h"
      "${LEVELDB_PUBLIC_INCLUDE_DIR}/write_batch.h"
    DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/leveldb"
  )

  include(CMakePackageConfigHelpers)
  configure_package_config_file(
    "cmake/${PROJECT_NAME}Config.cmake.in"
    "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake"
    INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
  )
  write_basic_package_version_file(
    "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake"
    COMPATIBILITY SameMajorVersion
  )
  install(
    EXPORT leveldbTargets
    NAMESPACE leveldb::
    DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
  )
  install(
    FILES
      "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}Config.cmake"
      "${PROJECT_BINARY_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake"
    DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
  )
endif(LEVELDB_INSTALL)

CMake之install方法的使用

CMAKE 里PRIVATE、PUBLIC、INTERFACE属性示例详解
leveldb自定义env

InMemoryEnv中引用计数的处理

leveldb中env 文件操作的使用示例

Status BuildTable(const std::string& dbname, Env* env, const Options& options,
                  TableCache* table_cache, Iterator* iter, FileMetaData* meta) {
  Status s;
  meta->file_size = 0;
  iter->SeekToFirst();

  std::string fname = TableFileName(dbname, meta->number);
  if (iter->Valid()) {
    WritableFile* file;
    s = env->NewWritableFile(fname, &file);
    if (!s.ok()) {
      return s;
    }

    TableBuilder* builder = new TableBuilder(options, file);
    meta->smallest.DecodeFrom(iter->key());
    Slice key;
    for (; iter->Valid(); iter->Next()) {
      key = iter->key();
      builder->Add(key, iter->value());
    }
    if (!key.empty()) {
      meta->largest.DecodeFrom(key);
    }

    // Finish and check for builder errors
    s = builder->Finish();
    if (s.ok()) {
      meta->file_size = builder->FileSize();
      assert(meta->file_size > 0);
    }
    delete builder;

    // Finish and check for file errors
    if (s.ok()) {
      s = file->Sync();
    }
    if (s.ok()) {
      s = file->Close();
    }
    delete file;
    file = nullptr;

    if (s.ok()) {
      // Verify that the table is usable
      Iterator* it = table_cache->NewIterator(ReadOptions(), meta->number,
                                              meta->file_size);
      s = it->status();
      delete it;
    }
  }

  // Check for input iterator errors
  if (!iter->status().ok()) {
    s = iter->status();
  }

  if (s.ok() && meta->file_size > 0) {
    // Keep it
  } else {
    env->RemoveFile(fname);
  }
  return s;
}

关注以下两行,阅读InMemoryEnv的相关实现

s = env->NewWritableFile(fname, &file);
delete file;
class FileState {
  // Increase the reference count.
  void Ref() {		// 增加文件引用计数
    MutexLock lock(&refs_mutex_);
    ++refs_;
  }

  // Decrease the reference count. Delete if this is the last reference.
  void Unref() {	// 减小文件引用计数,为0时删除指向对象	
    bool do_delete = false;

    {
      MutexLock lock(&refs_mutex_);
      --refs_;
      assert(refs_ >= 0);
      if (refs_ <= 0) {
        do_delete = true;
      }
    }

    if (do_delete) {
      delete this;
    }
  }
 private:
  int refs_ GUARDED_BY(refs_mutex_);
};

class InMemoryEnv : public EnvWrapper {
 public:
  explicit InMemoryEnv(Env* base_env) : EnvWrapper(base_env) {}

  ~InMemoryEnv() override {
    for (const auto& kvp : file_map_) {
      kvp.second->Unref();				// 减小现存文件的引用计数
    }
  }
 private:
  // Map from filenames to FileState objects, representing a simple file system.
  typedef std::map<std::string, FileState*> FileSystem;	// 文件名与文件对象映射

  port::Mutex mutex_;
  FileSystem file_map_ GUARDED_BY(mutex_);
};

class WritableFileImpl : public WritableFile {
 public:
  WritableFileImpl(FileState* file) : file_(file) { file_->Ref(); }

  ~WritableFileImpl() override { file_->Unref(); }
  
 private:
  FileState* file_;
};


Status NewWritableFile(const std::string& fname, WritableFile** result) override {
  MutexLock lock(&mutex_);
  FileSystem::iterator it = file_map_.find(fname);

  FileState* file;
  if (it == file_map_.end()) {
    // File is not currently open.
    file = new FileState(); // 此时文件引用计数为0
    file->Ref();			  // 此时文件引用计数为1
    file_map_[fname] = file;
  } else {  // 文件已存在,引用计数为1
    file = it->second;
    file->Truncate();
  }

  *result = new WritableFileImpl(file);	// 此时文件引用计数为2
  return Status::OK();
}

可以看出,在leveldb使用过程中,只要文件未删除(Env的RemoveFile),即使调用delete语句删除文件对象,文件对象的引用计数只是从2变成1,仍然存在于内存中

单线程写入

leveldb使用单线程合并sst文件

void DBImpl::MaybeScheduleCompaction() {
  mutex_.AssertHeld();
  if (background_compaction_scheduled_) {
    // Already scheduled
  } else if (shutting_down_.load(std::memory_order_acquire)) {
    // DB is being deleted; no more background compactions
  } else if (!bg_error_.ok()) {
    // Already got an error; no more changes
  } else if (imm_ == nullptr && manual_compaction_ == nullptr && !versions_->NeedsCompaction()) {
    // No work to be done
  } else {
    background_compaction_scheduled_ = true;
    env_->Schedule(&DBImpl::BGWork, this);
  }
}

// 关注Schedule posix实现
void PosixEnv::Schedule(
    void (*background_work_function)(void* background_work_arg),
    void* background_work_arg) {
  background_work_mutex_.Lock();

  // Start the background thread, if we haven't done so already.
  if (!started_background_thread_) {  	// 只开启一个线程进行合并操作
    started_background_thread_ = true;
    std::thread background_thread(PosixEnv::BackgroundThreadEntryPoint, this);
    background_thread.detach();
  }

  // If the queue is empty, the background thread may be waiting for work.
  if (background_work_queue_.empty()) {
    background_work_cv_.Signal();
  }

  background_work_queue_.emplace(background_work_function, background_work_arg);
  background_work_mutex_.Unlock();
}

// 线程主函数
static void BackgroundThreadEntryPoint(PosixEnv* env) {
  env->BackgroundThreadMain();
}
// 没有任务时休眠,有任务时一次执行一个任务
void PosixEnv::BackgroundThreadMain() {
  while (true) {
    background_work_mutex_.Lock();

    // Wait until there is work to be done.
    while (background_work_queue_.empty()) {
      background_work_cv_.Wait();
    }

    assert(!background_work_queue_.empty());
    auto background_work_function = background_work_queue_.front().function;
    void* background_work_arg = background_work_queue_.front().arg;
    background_work_queue_.pop();

    background_work_mutex_.Unlock();
    background_work_function(background_work_arg);
  }
}

GetChildren

该方法返回相当于目录的相对路径

  // Store in *result the names of the children of the specified directory.
  // The names are relative to "dir".
  // Original contents of *results are dropped.
  virtual Status GetChildren(const std::string& dir,
                             std::vector<std::string>* result) = 0;
 // 使用场景,若GetChildren实现错误则RemoveObsoleteFiles无法移除无用文件
 void DBImpl::RemoveObsoleteFiles() {
  mutex_.AssertHeld();

  if (!bg_error_.ok()) {
    // After a background error, we don't know whether a new version may
    // or may not have been committed, so we cannot safely garbage collect.
    return;
  }

  // Make a set of all of the live files
  std::set<uint64_t> live = pending_outputs_;
  versions_->AddLiveFiles(&live);

  std::vector<std::string> filenames;
  env_->GetChildren(dbname_, &filenames);  // Ignoring errors on purpose
  uint64_t number;
  FileType type;
  std::vector<std::string> files_to_delete;
  for (std::string& filename : filenames) {
    if (ParseFileName(filename, &number, &type)) {
      bool keep = true;
      switch (type) {
        case kLogFile:
          keep = ((number >= versions_->LogNumber()) || (number == versions_->PrevLogNumber()));
          break;
        case kDescriptorFile:
          // Keep my manifest file, and any newer incarnations'
          // (in case there is a race that allows other incarnations)
          keep = (number >= versions_->ManifestFileNumber());
          break;
        case kTableFile:
          keep = (live.find(number) != live.end());
          break;
        case kTempFile:
          // Any temp files that are currently being written to must
          // be recorded in pending_outputs_, which is inserted into "live"
          keep = (live.find(number) != live.end());
          break;
        case kCurrentFile:
        case kDBLockFile:
        case kInfoLogFile:
          keep = true;
          break;
      }

      if (!keep) {
        files_to_delete.push_back(std::move(filename));
        if (type == kTableFile) {
          table_cache_->Evict(number);
        }
        Log(options_.info_log, "Delete type=%d #%lld\n", static_cast<int>(type), static_cast<unsigned long long>(number));
      }
    }
  }

  // While deleting all files unblock other threads. All files being deleted
  // have unique names which will not collide with newly created files and
  // are therefore safe to delete while allowing other threads to proceed.
  mutex_.Unlock();
  for (const std::string& filename : files_to_delete) {
    env_->RemoveFile(dbname_ + "/" + filename);
  }
  mutex_.Lock();
}

修改leveldb实现后记得make && make install,保证应用程序使用的库已更新文章来源地址https://www.toymoban.com/news/detail-434643.html

到了这里,关于leveldb自定义env的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • vue 2.0需求拍摄证件照(需要设置人头取景框,鼠标也拖拽,键盘方向键可调整位置)

    继续昨天的代码更新一下 需求是: vue2 主页面是一个表格,存在按钮点击进行调取摄像头拍照,拍照呢要有头像的取景框,鼠标可移动,键盘通过方向键也可移动,调整了昨天的代码存在的问题,拖动会拍照一部分外面的内容, 今天,疑惑点在获取更新后的节点,及怎么设

    2024年01月18日
    浏览(46)
  • 大白话解析LevelDB: VersionSet

    在 LevelDB 中, VersionSet 类是一个关键的内部组件,负责管理数据库的不同版本。这个类跟踪了所有的 SSTables(排序字符串表)和它们在数据库中的布局。每次对数据库进行修改时(如添加、删除数据),LevelDB 会创建一个新的 Version 对象,这个对象由 VersionSet 管理。 VersionSe

    2024年01月19日
    浏览(48)
  • 定制 ChatGPT 以满足您的需求 自定义说明

    推荐:使用 NSDT场景编辑器 快速助你搭建可二次编辑的3D应用场景 20 月 日,OpenAI 宣布他们正在引入带有自定义说明的新流程,以根据您的特定需求定制 ChatGPT。 新的测试版自定义指令功能旨在通过防止用户在聊天会话之间重复常用指令来帮助用户充分利用 ChatGPT。展望未来

    2024年02月13日
    浏览(49)
  • 项目管理:项目需求变更对项目的影响分析

    在项目的推进过程中,我们常常遭遇需求变更,作为项目经理,我们需要掌握的一项重要技能便是如何妥善应对这些变更,确保项目能够有序进行并按时交付。 项目变更管理,它不仅仅是一个简单的流程,而是一个涵盖了评估、批准、更新、实施、监控和记录的全面的过程。

    2024年02月03日
    浏览(49)
  • 外卖项目需求说明书

    三、需求规格说明书 1 .引言 1.1 编写目的 1.2 项目背景 1.3 定义 1.4 参考资料 2 .任务概述 2.1 目标 2.2 运行环境 2.3 条件与限制 3 .数据描述 3.1 静态数据 3.2 动态数据 3.3 数据库介绍 3.4 数据词典 3.5 数据采集 4 .功能需求 4.1 功能划分 4.2 功能描述 5 .性能需求 5.1 数据精确度

    2024年02月06日
    浏览(48)
  • vue 实现内容超出两行显示展开更多功能,可依据需求自定义任意行数!

    平时开发中我们经常会遇到这样的需求,在一个不限高度的盒子中会有很多内容,如果全部显示用户体验会非常不好,所以可以先折叠起来,当内容达到一定高度时,显示 展开更多 按钮, 点击即可显示全部内容 ,先来看看效果图:  这样做用户体验瞬间得到提升,接下来看

    2023年04月24日
    浏览(43)
  • ActiveMq学习⑨__基于zookeeper和LevelDB搭建ActiveMQ集群

    引入消息中间件后如何保证其高可用? 基于zookeeper和LevelDB搭建ActiveMQ集群。集群仅 提供主备方式的高可用集群功能,避免单点故障 。 http://activemq.apache.org/masterslave LevelDB,5.6版本之后推出了LecelDB的持久化引擎,它使用了自定义的索引代替常用的BTree索引,其持久化性能高于

    2024年02月05日
    浏览(56)
  • 项目实战 — 消息队列(1) {需求分析}

    目录 一、什么是消息队列 二、需求分析 🍅1、核心概念  🍅2、核心API 🍅3、交换机类型 🍅 4、持久化 🍅5、网络通信  🍅6、应答模式 三、模块划分 四、测试用例设计 五、总结 消息队列(Message Queue ,MQ),就是将阻塞队列的数据结构,提取成了一个程序,独立进行部署。

    2024年02月15日
    浏览(40)
  • 需求文档中的“项目前景与范围”

    ​在进行产品需求分析时,业务需求、高层解决方案和系统特性等都应该被记录下来。定义为项目的前景与范围文档。 l前景:描述了产品的作用和最终的功能,它将所有的涉众都统一到一个方向上。 l范围:指出了当前项目是要解决产品长远规划的那一部分,它为项目规定了

    2023年04月12日
    浏览(36)
  • 软件测试之项目立项与需求评审

     📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📢软件测试面试题分享: 1000道软件测试面试题及答案 📢软件测试实战项目分享: 纯接口项目-完整接口文档 📢软件测试实战项目分享:WEB 测试自动化项目实战 📢软件测试学习教程推

    2024年01月22日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包