设为首页 收藏本站
查看: 1472|回复: 0

[经验分享] Mongodb源代码阅读笔记:Journal机制

[复制链接]

尚未签到

发表于 2015-7-7 13:06:38 | 显示全部楼层 |阅读模式
Mongodb源代码阅读笔记:Journal机制

Mongodb源代码阅读笔记:Journal机制... 1
涉及的文件... 1
一些说明... 1
PREPLOGBUFFER. 2
WRITETOJOURNAL. 4
WRITETODATAFILES. 6
REMAPPRIVATEVIEW... 8


涉及的文件

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手册看很有用。然后代码阅读也从PREPLOGBUFFER,WRITETOJOURNAL,WRITETODATAFILES,REMAPPRIVATEVIEW几个阶段开始。
/*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 >::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& _intents = commitJob.getIntentsSorted(); //取出要处理的JOB写入意向
  ……
  WriteIntent last;
  for( vector::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上。
WRITETODATAFILES在dur.cpp上直接调用了dur_writetodatafiles.cpp上的WRITETODATAFILES_Impl1。



static void WRITETODATAFILES_Impl1(const JSectHeader& h, AlignedBuilder& uncompressed) {
  LOG(3) len) 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& files = MongoFile::getAllFiles(); //获取所有文件准备重新映射
  ……
  constset::iterator b = files.begin();
  constset::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
  ……
}
进入remapPrivateView为mmap_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)

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-84137-1-1.html 上篇帖子: Linux下启动mongodb(后台方式) 下篇帖子: MongoDB五种树形结构表示法
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表