网络模型。cli
内存结构
文件结构。写入的buf,flush,sync。页保证完整性
内存池
并发。网络,写入并发(批量写入,写log并发,写文件落盘并发,compact并发)
ACID 版本控制,日志
cache
复制
备份,快照
崩溃恢复

崩溃恢复

先从manifest恢复version.version_set,logid,seqid等,先合并删除再manifest再改current和versionset。再获取所有日志,比较log>manifest中的log的进行恢复。到mem可能还要compact。manifest若是未写成功则就当没有此次压缩。如果log未写成功,则mem不会有此数据。

并发

只有个批量写入log和mem

版本控制

见其他两篇文章,一个是读取内存时候比较seq。
一个是文件中compact后维护不同version。对不同version如果有引用则不删除,manifest中维护所有open以来的版本。详见另外两篇
若不带snapshot。赋值为 snapshot = versions_->LastSequence();
文件的 Version* current = versions_->current();
根据snapshot的seq构造key比较查找。每个的引用次数++,结束后--。每次compact结束后决定是否删除。

cache

http://www.pandademo.com/2016...
上面这个说的不错,两层,一个是文件索引的cache,调用Table::Open读取footer,index等,block_cache分片的,找到文件后定位到哪个block_cache,cache中是块信息。

内存结构

跳表,两个mem切换

文件结构

内存维护buf。4k一次调用write写入一个块。64k补一个sync。
页完整的保证:mysql的二次写,mongodb的固定块大小。
leveldb
1.log写入:若Block已经小于header,填充0下一个Block。否则写入block剩余和left小的,加入first等标识。一段调一次加入头信息/crc32c校验和和数据,走append和flush。每段日志都会write。block为32k一个大小
2.log读取:每32k读一次。校验和,读类型等,若未到结尾继续读,若校验和等出错,抛弃32k,继续读。考虑每次出问题的时候有一个块可能部分写。抛弃读取部分,但每次恢复遇到问题会指向下一个块或者读取部分就好。
3.数据写入:4k加一个index。调用一次write。一个table一次fsync
提供的file->append每64K一个buffer.但是数据和日志都分别在外面调用flush保证4k一次write,和一条日志一次write。
数据4k一个校验和写入,其他filter_block,meta_index_block,index_block,rooter算好最后在写入
每4看写入所有重启点(16个一个,重启点是指向重启点的offset),压缩,写入文件,写入检验和
4.数据读取:mm->imm->current->get
table_cache_->Get. sst文件放入table_cache
Iterator* iiter = rep_->index_block->NewIterator(rep_->options.comparator);
iiter->Seek(k);
Iterator* block_iter = BlockReader(this, options, iiter->value());
block_iter->Seek(k);
到 BlockReader:
Cache* block_cache = table->rep_->options.block_cache;
block_cache没有,ReadBlock
Status s = file->Read(handle.offset(), n + kBlockTrailerSize, &contents, buf);
校验,解压缩,校验不对直接返回错误。在一个块里搜索通过二分重启点,后线性找记录。数据校验有问题的直接错误并不处理。(看看recover会处理吗)

内存池:

glibc malloc和C++标准库的new返回的地址均是内存对齐的,这里由于是定制内存分配,指针预分配的大块内存(page页面大小),当连续分配小块内存时就需要做额外对齐。内存对齐有利于提高访存速度,因为内存按字节编址,根据CPU字长即sizeof(void *)大小做对齐,有利于减少访存指令次数,防止跨对齐边界访存,也能充分利用硬件体系,提高CPU性能。

 char* alloc_ptr_;
 size_t alloc_bytes_remaining_;
 std::vector<char*> blocks_;

inline char* Arena::Allocate(size_t bytes) {
  assert(bytes > 0);
  if (bytes <= alloc_bytes_remaining_) {
    char* result = alloc_ptr_;
    alloc_ptr_ += bytes;
    alloc_bytes_remaining_ -= bytes;
    return result;
  }
  return AllocateFallback(bytes);
}
char* Arena::AllocateFallback(size_t bytes) {
  if (bytes > kBlockSize / 4) {
    char* result = AllocateNewBlock(bytes);
    return result;
  }

  alloc_ptr_ = AllocateNewBlock(kBlockSize);
  alloc_bytes_remaining_ = kBlockSize;

  char* result = alloc_ptr_;
  alloc_ptr_ += bytes;
  alloc_bytes_remaining_ -= bytes;
  return result;
}
char* Arena::AllocateNewBlock(size_t block_bytes) {
  char* result = new char[block_bytes];
  blocks_.push_back(result);
  memory_usage_.fetch_add(block_bytes + sizeof(char*),
                          std::memory_order_relaxed);
  return result;
}

对其版本:每次从8申请
char* Arena::AllocateAligned(size_t bytes) {
  const int align = (sizeof(void*) > 8) ? sizeof(void*) : 8;
  size_t current_mod = reinterpret_cast<uintptr_t>(alloc_ptr_) & (align - 1);
  size_t slop = (current_mod == 0 ? 0 : align - current_mod);
  size_t needed = bytes + slop;
  char* result;
  if (needed <= alloc_bytes_remaining_) {
    result = alloc_ptr_ + slop;
    alloc_ptr_ += needed;
    alloc_bytes_remaining_ -= needed;
  } else {
    // AllocateFallback always returned aligned memory
    result = AllocateFallback(bytes);
  }
  assert((reinterpret_cast<uintptr_t>(result) & (align - 1)) == 0);
  return result;
}
  • rocksdb的slice

不管是 it->key() 还是 it->value(),其值类型都是 rocksdb::Slice。 Slice 自身由一个长度字段[ sizet size ]以及一个指向外部一个内存区域的指针[ const char* data_ ]构成,但是在离开相应的 scope 之后,其值就会被释放,rocksdb v5.4.5 版本引入一个 PinnableSlice,增加引用计数


梦想家
107 声望76 粉丝