Mongodb源代码阅读笔记:Journal机制

本文涉及的产品
云数据库 MongoDB,通用型 2核4GB
简介:

Mongodb源代码阅读笔记:Journal机制

Mongodb源代码阅读笔记:Journal机制

涉及的文件

一些说明

PREPLOGBUFFER

WRITETOJOURNAL

WRITETODATAFILES

REMAPPRIVATEVIEW

 

涉及的文件

mongoDB中和持久性相关的文件只要有以下几个:

 

dur.h: dur.cpp,dur_preplogbuffer.cpp,dur_writetodatafiles.cpp

,dur_commitjob.h: dur_commitjob.cpp

,dur_journal.h:dur_journal.cpp

,dur_recover.h:dur_recover.cpp

,durable_mapped_file.h:durable_mapped_file.cpp

,mmap.h:mmap.cpp,mmap_win.cpp,mmap_mm.cpp,mmap_posix.cpp

一些说明

Dur.cpp中提供了一些说明,结合mongodb手册看很有用。然后代码阅读也从PREPLOGBUFFERWRITETOJOURNALWRITETODATAFILESREMAPPRIVATEVIEW几个阶段开始。

/*phases:

     PREPLOGBUFFER

       we will build an output buffer ourself and then use O_DIRECT

       we could be in read lock for this

       for very large objects write directly to redo log in situ?

     WRITETOJOURNAL

       we could be unlocked (the main db lock that is...) for this, with sufficient care, but there is some complexity have to handle falling behind which would use too much ram (going back into a read lock would suffice to stop that).for now (1.7.5/1.8.0) we are in read lock which is not ideal.

     WRITETODATAFILES

       actually write to the database data files in this phase.  currently done by memcpy'ing the writes back to the non-private MMF.  alternatively one could write to the files the traditional way; however the way our storage engine works that isn't any faster (actually measured a tiny bit slower).

     REMAPPRIVATEVIEW

       we could in a write lock quickly flip readers back to the main view, then stay in read lock and do our real remapping. with many files (e.g., 1000), remapping could be time consuming (several ms), so we don't want to be too frequent.

       there could be a slow down immediately after remapping as fresh copy-on-writes for commonly written pages will be required.  so doing these remaps fractionally is helpful.

*/

PREPLOGBUFFER

dur_preplogbuffer.cpp中有函数:

复制代码
void PREPLOGBUFFER(/*out*/ JSectHeader& h, AlignedBuilder& ab) {
  assertLockedForCommitting();
  Timer t;
  j.assureLogFileOpen(); // so fileId is set
  _PREPLOGBUFFER(h, ab); //直接调用了这个函数
  stats.curr->_prepLogBufferMicros += t.micros();
 }
复制代码

注意函数是返回一个JSectHeader,ab是用来交互的buffer

复制代码
static void _PREPLOGBUFFER(JSectHeader& h, AlignedBuilder& bb) {
  ……
  resetLogBuffer(/*out*/h, bb); // adds JSectHeader section对应于group commit
  // ops other than basic writes (DurOp's) ops干嘛用还不清楚
  {
    for( vector< shared_ptr<DurOp> >::iterator i = commitJob.ops().begin(); i != commitJob.ops().end(); ++i ) {
      (*i)->serialize(bb);
    }
  }
  prepBasicWrites(bb); //从这里还是把东西写入到bb中
  return;
}
复制代码

写入在mongo源代码中被称为写入意向,会在prepBasicWrite中被写入到buffer中,也就是这里的bb变量。之后所有的都会使用这个bb变量。

接下来看prepBasicWrites

复制代码
static void prepBasicWrites(AlignedBuilder& bb) {
   ……
  RelativePath lastDbPath;
  ……
  const vector<WriteIntent>& _intents = commitJob.getIntentsSorted(); //取出要处理的JOB写入意向
  ……
  WriteIntent last;
  for( vector<WriteIntent>::const_iterator i = _intents.begin(); i != _intents.end(); i++ ) {
    //因为last为空所以第一遍的时候last=*i
    if( i->start() < last.end() ) { //若job之间重叠就通过absorb连接上变成一个。
      last.absorb(*i);
    }
    else { //若连不上则写入
      if( i != _intents.begin() )
        prepBasicWrite_inlock(bb, &last, lastDbPath); //对单个意向进行处理
        last = *i;
      }
    }
  prepBasicWrite_inlock(bb, &last, lastDbPath); 
 }
复制代码

prepBasicWrite主要的用处是对读取写入意向,然后对写入意向进行合并,然后单独处理某个写入意向。写入意向的处理在prepBasicWrite_inlock函数中。

进入prepBasicWrite_inlock函数

复制代码
static void prepBasicWrite_inlock(AlignedBuilder&bb, const WriteIntent *i, RelativePath& lastDbPath) {
  size_t ofs = 1;
  DurableMappedFile *mmf = findMMF_inlock(i->start(), /*out*/ofs);//查找内存映射文件,应该是privare_view
  if( unlikely(!mmf->willNeedRemap()) ) {
    // tag this mmf as needed a remap of its private view later.
    // usually it will already be dirty/already set, so we do the if above first
    // to avoid possibility of cpu cache line contention
    mmf->willNeedRemap() = true; //标记等会儿要remap
  }
 
  JEntry e; //JEntry表示group commit中单个的写操祝,整个entry要不被执行,要不不被执行
  e.len = min(i->length(), (unsigned)(mmf->length() - ofs)); //don't write past end of file 不能超过mmf大小
  ……
  e.setFileNo( mmf->fileSuffixNo() );
  if( mmf->relativePath() == local ) {
    e.setLocalDbContextBit();
  }
  else if( mmf->relativePath() != lastDbPath ) {
    lastDbPath = mmf->relativePath();
    JDbContext c;
    bb.appendStruct(c); //把db上下文写入到bb
    bb.appendStr(lastDbPath.toString());//把路径写入到日志
  }
  bb.appendStruct(e);//把JEntry写入到日志
  ……
  bb.appendBuf(i->start(), e.len); //把写入意向的内容写入到bb
  ……
            
}
复制代码

这样PREPLOGBUFFER就结束了,主要就是把写入意向存到bb缓存里面。

WRITETOJOURNAL

写入WRITETOJOURNAL主要都是在dur_journal.cpp文件中。

void WRITETOJOURNAL(JSectHeader h, AlignedBuilder& uncompressed) {
  Timer t;
  j.journal(h, uncompressed); //调用Journal::jounal
  stats.curr->_writeToJournalMicros += t.micros();
}

没啥可看直接调用了journal函数

复制代码
void Journal::journal(const JSectHeader& h, const AlignedBuilder& uncompressed) {
  ……
  static AlignedBuilder b(32*1024*1024); //分配一个值用于写文件的buf
  const unsigned headTailSize = sizeof(JSectHeader) + sizeof(JSectFooter); //section头尾的大小
  const unsigned max = maxCompressedLength(uncompressed.len()) + headTailSize;//获取buffer未压缩的所有大小
  b.reset(max);//重置这部分大小的b
  {
    ……
    b.appendStruct(h);//写入section头到b这个buffer上
  }
  size_t compressedLength = 0;
  rawCompress(uncompressed.buf(), uncompressed.len(), b.cur(), &compressedLength); //把带job的buffer放到b中,返回压缩后的长度
  ……
  b.skip(compressedLength);//跳过compressedLength的大小,准备写下来的写入
  unsigned L = 0xffffffff;
  {
    ……
    JSectFooter f(b.buf(), b.len()); // computes checksum
    b.appendStruct(f);//写入section尾
    ……
    b.skip(L - lenUnpadded);//跳过尾的大小
    ……
  }
  try {
    SimpleMutex::scoped_lock lk(_curLogFileMutex);
    ……
    _curLogFile->synchronousAppend((constvoid *) b.buf(), L); //写入数据到日志文件,LogFile::synchronousAppend
    ……
  }
  catch(std::exception& e) {
    ……
  }
}
复制代码

真正的写入在LogFile::synchronousAppend完成,LogFile::synchronousAppend有一个函数WriteFile来完成整个写入journal的动作

WRITETODATAFILES

把数据写入到数据文件上,其实是把数据文件写入到数据文件的内存映射文件(_view_write),代码主要在dur_writetodatafiles.cpp上。

WRITETODATAFILESdur.cpp上直接调用了dur_writetodatafiles.cpp上的WRITETODATAFILES_Impl1

static void WRITETODATAFILES_Impl1(const JSectHeader& h, AlignedBuilder& uncompressed) {
  LOG(3) << "journal WRITETODATAFILES 1" << endl;
  RecoveryJob::get().processSection(&h, uncompressed.buf(), uncompressed.len(), 0); //在这里进入
  LOG(3) << "journal WRITETODATAFILES 2" << endl;
}

WRITETODATAFILES_Impl1调用了RecoveryJob::processSection

复制代码
void RecoveryJob::processSection(const JSectHeader *h, constvoid *p, unsigned len, const JSectFooter *f) {
  ……
  auto_ptr<JournalSectionIterator> i;
  if( _recovering /*表示recovering 或WRITETODATAFILES*/ ) {
    ……   
  }
  else { //如果是WRITETODATAFILES
    i = auto_ptr<JournalSectionIterator>(new JournalSectionIterator(*h, /*after header*/p, /*w/out header*/len));
    //把buffer转化为JournalSectionIterator类型
  }
  static vector<ParsedJournalEntry> entries; //解析JEntry,然后放到entries上
  entries.clear();
  ParsedJournalEntry e;
  while( !i->atEof() ) {
    i->next(e);          //把bb中的数据转化为ParsedJournalEntry
    entries.push_back(e);//一个一个写入到entries中
  }
  ……
  // got all the entries for one group commit.  apply them:
  applyEntries(entries); //应用这些entries
}
复制代码

buffer里面的写入意向,转化为ParsedJournalEntry,然后一个一个的应用。在applyEntries中,

复制代码
void RecoveryJob::applyEntries(const vector<ParsedJournalEntry> &entries) {
          ……
            for( vector<ParsedJournalEntry>::const_iterator i = entries.begin(); i != entries.end(); ++i ) {
                applyEntry(last, *i, apply, dump); //一个一个应用
            }
        ……
}
复制代码

进入applyEntry

复制代码
void RecoveryJob::applyEntry(Last& last, const ParsedJournalEntry& entry, bool apply, bool dump) {
  if( entry.e ) { //如果e存在写入操作
    ……
  if( apply ) {//WRITETODATAFILES
    write(last, entry); //在这里一个一个的写入
  }
}
          ……
 }
复制代码

进入到write函数里面

复制代码
void RecoveryJob::write(Last& last, const ParsedJournalEntry& entry) {
  ……
  DurableMappedFile *mmf = last.newEntry(entry, *this); //获取要写入的对象
  if ((entry.e->ofs + entry.e->len) <= mmf->length()) {
    ……
    void* dest = (char*)mmf->view_write() + entry.e->ofs;//目标位_view_write
    memcpy(dest, entry.e->srcData(), entry.e->len); //通过memcopy写入日志到_view_write,也就是datafile
    ……
  }
  ……
}
复制代码

这样写入到datafile就完成了。

REMAPPRIVATEVIEW

写完了datafile之后,要对private view(_view_private)做重新映射

void REMAPPRIVATEVIEW() {//重新映射privare_view
  Timer t;
  _REMAPPRIVATEVIEW(); //直接进入
  stats.curr->_remapPrivateViewMicros += t.micros();
}

直接进入_REMAPPRIVATEVIEW

复制代码
static void _REMAPPRIVATEVIEW() {
  ……
  set<MongoFile*>& files = MongoFile::getAllFiles(); //获取所有文件准备重新映射
  ……
  constset<MongoFile*>::iterator b = files.begin();
  constset<MongoFile*>::iterator e = files.end();
 
  Timer t;
  for( unsigned x = 0; x < ntodo; x++ ) {
    ……
    if( (*i)->isDurableMappedFile() ) { //判断是不是DurableMappedFile,继承在DurableMappedFile中重写
      DurableMappedFile *mmf = (DurableMappedFile*) *i; //有继承关系所以可以直接把MongoFile转化为DurableMappedFile
      verify(mmf);
      if( mmf->willNeedRemap() ) { //如果需要重新映射
        mmf->willNeedRemap() = false;
        mmf->remapThePrivateView(); //重新映射
      }
      i++;
      if( i == e ) i = b;
    }
  }
            ……
}
复制代码

重新映射在函数remapThePrivateView中完成,位于文件durable_mapped_file.cpp下。

void DurableMappedFile::remapThePrivateView() {
  ……
  void *old = _view_private;
  _view_private = remapPrivateView(_view_private); //对private_view进行重新映射,也就是_view_pirvate
  ……
}

进入remapPrivateViewmmap_win.cpp下的方法,当然在mmap_posix中也有这个方法,哪我就用mmap_win.cpp下的方法。

复制代码
void* MemoryMappedFile::remapPrivateView(void *oldPrivateAddr) {
  ……
  void* newPrivateView = MapViewOfFileEx(  //重新映射
        maphandle,          // file mapping handle
        FILE_MAP_READ,      // access
        0, 0,               // file offset, high and low
        0,                  // bytes to map, 0 == all
        oldPrivateAddr );   // we want the same address we had before
  ……
  return newPrivateView;
}
复制代码

可以发现,进行了重新映射,但是里面有个maphandle,为了check一下手册里面说的,private view重新映射到shared view_view_write)我们继续往下看。

重新回到durable_mapped_file.cpp,有个方法create,用来打开映射文件

bool DurableMappedFile::create(const std::string& fname, unsigned longlong& len, bool sequentialHint) {
  LOG(3) << "mmf create " << fname << endl;
  setPath(fname);
  _view_write = map(fname.c_str(), len, sequentialHint ? SEQUENTIAL : 0);
  return finishOpening();
}

然后调用map,并把结果赋值给_view_writemap函数我们还是以mmap_win.cpp为例

复制代码
void* MemoryMappedFile::map(constchar *filenameIn, unsigned longlong &length, int options) {
  …… 
  DWORD flProtect = PAGE_READWRITE; //(options & READONLY)?PAGE_READONLY:PAGE_READWRITE;
  maphandle = CreateFileMappingW(fd, NULL, flProtect,
                      length >> 32/*maxsizehigh*/,
                      (unsigned) length /*maxsizelow*/,
                       NULL/*lpName*/); //在map数据文件的时候把返回结果复制给maphandle
  ……
  void *view = 0;
  {
    ……
    view = MapViewOfFileEx(//创建了map,最后返回view
                    maphandle,      // file mapping handle
                    access,         // access
                    0, 0,           // file offset, high and low
                    0,              // bytes to map, 0 == all
                    thisAddress );  // address to place file
    ……
  }
  ……
  return view;
}
复制代码

所以这个maphandle_view_writemaphandle也就是shared view(数据库文件)。




    本文转自 Fanr_Zh 博客园博客,原文链接:http://www.cnblogs.com/Amaranthus/p/3557475.html,如需转载请自行联系原作者






相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
8月前
|
JSON NoSQL MongoDB
mongodb基本操作,增删改查,查询,索引,权限机制
mongodb基本操作,增删改查,查询,索引,权限机制
|
1天前
|
NoSQL MongoDB 数据库
【MongoDB 专栏】MongoDB 的并发控制与锁机制
【5月更文挑战第11天】MongoDB的并发控制和锁机制保证数据一致性和性能。全局锁用于特殊情况如数据库初始化,限制并发性能;文档级锁提供更高的并发性,针对单个文档锁定。乐观并发控制利用版本号检查减少锁竞争。在分布式环境下,需协调多节点锁,优化包括合理设计数据模型、调整锁配置和利用分布式事务。未来,MongoDB将持续改进这些机制以应对复杂需求。了解并发控制原理对于数据库开发者至关重要。
【MongoDB 专栏】MongoDB 的并发控制与锁机制
|
11天前
|
存储 NoSQL 关系型数据库
【MongoDB系列笔记】索引
索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档,以选择与查询语句匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
22 1
|
11天前
|
存储 JSON NoSQL
【MongoDB系列相关笔记】常用命令
本文主要介绍了常见的MongoDB命令操作;结合某个案例需求,将数据库操作,集合操作,文档基本的CURD以及分页查询等命令进行详细说明。
65 0
|
11天前
|
NoSQL JavaScript Linux
【MongoDB系列相关笔记】单机部署
本文主要介绍了Windows和Linux系统中安装和启动MongoDB的步骤。
50 0
|
11天前
|
存储 NoSQL 关系型数据库
【MongoDB系列笔记】MongoDB相关概念
MongoDB 是一个开源、高性能、无模式的文档型数据库,常用于处理高并发、海量数据的场景,尤其适合社交、游戏、物流、物联网和视频直播等领域。与传统的关系型数据库相比,MongoDB 更适合存储结构较为灵活、数据量大且事务性要求不高的数据。当面临高读写需求、大规模数据存储和高可扩展性需求时,可以选择 MongoDB。MongoDB 支持类似于 JSON 的 BSON 数据格式,具有丰富的数据模型,如文档、集合和数据库,以及强大的查询和索引功能。此外,MongoDB 提供复制集以实现高可用性和水平扩展性,以适应业务发展和数据增长。
26 0
|
12天前
|
NoSQL 安全 MongoDB
MongoDB安全机制:认证、授权与加密
【4月更文挑战第30天】MongoDB提供全面的安全机制,包括认证(用户名/密码、LDAP、Kerberos、x.509证书)、授权(基于角色的访问控制,RBAC)和加密(TLS/SSL、透明数据加密TDE、字段级加密FLE),确保数据保密性、完整性和可用性。通过合理配置这些机制,企业可保障数据安全,应对不断变化的安全威胁。
|
12天前
|
监控 NoSQL MongoDB
MongoDB索引机制与优化策略详解
【4月更文挑战第30天】本文深入解析MongoDB的索引机制,包括单字段、复合、地理空间、全文及哈希索引。介绍了创建与查看索引的方法,并提出了优化策略:选择性创建、使用复合索引、定期审查优化、避免不必要的索引扫描、利用索引前缀与覆盖索引,以及监控索引使用。通过这些策略,可提升MongoDB查询性能。
|
1月前
|
存储 NoSQL 大数据
【MongoDB】GridFS机制
【4月更文挑战第2天】【MongoDB】GridFS机制
|
2月前
|
NoSQL Java MongoDB
MongoDB笔记
MongoDB笔记
18 0