mongodb源码分析(五)

mongodb源码分析(十三)持久化

先来看看持久化的流程.默认情况下持久化是开启的,需要关闭启动时--nodur或者--nojournal.在开启journal

时mongodb保留了多数据库的两份映射,每一个文件有两个映射的初始地址_view_write和_view_private,

_view_private是为了持久化而生的.这就是为什么用mongostat查看系统信息时会看到vsize是mapped的2倍多

了,因为一份数据有两份映射.

       _view_private初始映射时是只读的,因为写时复制所以虽然其和_view_write都映射了数据,但是并未占用更多

的内存.当我们要修改数据时,mongodb会将要修改的数据所在页也就是_view_private部分的页修改属性修改为可

写,然后做实际的修改.这里的修改修改的部分并不是真正的数据库文件,也不是对_view_write映射部分的修改,而是

因为写时复制修改了一份数据库文件的拷贝.这时同一个数据可能在系统中存在两份数据,一份是没有修改的

_view_write,一份是已经修改的_view_private.当修改数据达到一个上限(32位是50M,64位是100M)或者显示的提交

调用(可能是持久化线程的调用会一般的调用),就会产生一次真正的提交,之前修改的_view_private中的记录将会被

压缩,然后存到日志文件中.然后修改_view_write映射的数据.最后由一个专门的线程DataFileSync将数据从

_view_write中刷到磁盘.这里有个问题就是随着写的持续性,_view_private实际占用的内存会不断扩大,所以一段时

间后需要释放映射,然后重新建立新的_view_private的映射.这样空间就被释放了,_view_private实际占用空间回

到0.一般操作如查询,删除,修改等操作使用的都是_view_private映射,所以可以保证最新的数据.

journal目录: 持久化目录.

j._0 j._1 ... j._n: 持久化的日志文件.

prealloc.0 prealloc.1: 预分配的持久化日志文件.

lsn:  记录上一次提交到磁盘的时间戳以及其位反的校验的文件.    

       需要注意如果在写了数据后持续化过程还没有生成日志时因为一些原因系统crash,那么这部分数据将丢失,

以明确一次最多丢失100M数据.下面我们实际来看代码.首先从写入动作开始.一次常规的写入如下:

[cpp]  view plain copy
  1. recNew = (Record *) getDur().writingPtr(recNew, lenWHdr);  
来看看这里的writingPtr的实现
[cpp]  view plain copy
  1. void* DurableImpl::writingPtr(void *x, unsigned len) {  
  2.     void *p = x;  
  3.     declareWriteIntent(p, len);  
  4.     return p;  
  5. }  
writingPtr->declareWriteIntent

[cpp]  view plain copy
  1. void DurableImpl::declareWriteIntent(void *p, unsigned len) {  
  2.     cc().writeHappened();  
  3.     MemoryMappedFile::makeWritable(p, len);//映射部分_view_private初始化为只读,所以将要写入部分的内存设置为可写  
  4.     ThreadLocalIntents *t = tlIntents.getMake();//得到tls变量ThreadLocalIntents指针  
  5.     t->push(WriteIntent(p,len));  
  6. }  
[cpp]  view plain copy
  1. void ThreadLocalIntents::push(const WriteIntent& x) { //每21次写unspool一次  
  2.      if( !commitJob._hasWritten )  
  3.          commitJob._hasWritten = true;  
  4.      if( n == 21 )//每21次写就将数据从本地线程传递到全局的commitjob中,并清空本地存储  
  5.          unspool();  
  6.      i[n++] = x;  
  7.  }  
writingPtr->declareWriteIntent->push->unspool->_unspool->CommitJob::note

[cpp]  view plain copy
  1. void CommitJob::note(void* p, int len) {  
  2.     // from the point of view of the dur module, it would be fine (i think) to only  
  3.     // be read locked here.  but must be at least read locked to avoid race with  
  4.     // remapprivateview  
  5.     if( !_intentsAndDurOps._alreadyNoted.checkAndSet(p, len) ) {//返回false表示新插入的,或者修改了长度的  
  6.         // remember intent. we will journal it in a bit  
  7.         _intentsAndDurOps.insertWriteIntent(p, len);//实际的记录操作,将修改的位置和修改长度记录到一个vector中  
  8.         {  
  9.             // a bit over conservative in counting pagebytes used  
  10.             static size_t lastPos; // note this doesn't reset with each commit, but that is ok we aren't being that precise  
  11.             size_t x = ((size_t) p) & ~0xfff; // round off to page address (4KB)  
  12.             if( x != lastPos ) {   
  13.                 lastPos = x;  
  14.                 unsigned b = (len+4095) & ~0xfff;  
  15.                 _bytes += b;//需要更新的byte数  
  16.                 if (_bytes > UncommittedBytesLimit * 3) {//超过了3倍需要更新的文件了  
  17.                     static time_t lastComplain;  
  18.                     static unsigned nComplains;  
  19.                     // throttle logging  
  20.                     if( ++nComplains < 100 || time(0) - lastComplain >= 60 ) {  
  21.                         lastComplain = time(0);  
  22.                         warning() << "DR102 too much data written uncommitted " << _bytes/1000000.0 << "MB" << endl;  
  23.                         if( nComplains < 10 || nComplains % 10 == 0 ) {  
  24.                             // wassert makes getLastError show an error, so we just print stack trace  
  25.                             printStackTrace();  
  26.                         }  
  27.                     }  
  28.                 }  
  29.             }  
  30.         }  
  31.     }  
  32. }  
下面再来看看创建删除文件的操作.
[cpp]  view plain copy
  1. void DurableImpl::createdFile(string filename, unsigned long long len) {  
  2.     shared_ptr<DurOp> op( new FileCreatedOp(filename, len) );//记录创建文件的地址以及其长度  
  3.     commitJob.noteOp(op);  
  4. }  
[cpp]  view plain copy
  1. void CommitJob::noteOp(shared_ptr<DurOp> p) {  
  2.     dassert( cmdLine.dur );  
  3.     // DurOp's are rare so it is ok to have the lock cost here  
  4.     SimpleMutex::scoped_lock lk(groupCommitMutex);  
  5.     cc().writeHappened();  
  6.     _hasWritten = true;  
  7.     _intentsAndDurOps._durOps.push_back(p);//将操作记录到持久化操作中  
  8. }  
上面的流程就是操作的记录,下面来看提交部分.

[cpp]  view plain copy
  1. bool NOINLINE_DECL DurableImpl::_aCommitIsNeeded() {  
  2.     if( !Lock::isLocked() ) {//数据的提交至少需要全局的读锁  
  3.         Lock::GlobalRead r;  
  4.         if( commitJob.bytes() < UncommittedBytesLimit ) {  
  5.             // someone else beat us to it  
  6.             return false;  
  7.         }  
  8.         commitNow();  
  9.     }  
  10.     else {   
  11.         // 'W'  
  12.         commitNow();  
  13.     }  
  14.     return true;  
  15. }  
[cpp]  view plain copy
  1. bool DurableImpl::commitNow() {  
  2.     stats.curr->_earlyCommits++;//统计信息  
  3.     groupCommit(0);  
  4.     return true;  
  5. }  
groupCommit简单的调用_groupCommit完成提交,然后可能的异常处理.

[cpp]  view plain copy
  1. static void _groupCommit(Lock::GlobalWrite *lgw) {  
  2.     // We are 'R' or 'W'  
  3.     assertLockedForCommitting();  
  4.  //将unspool中还未写到队列中的操作全部写到队列中去  
  5.     unspoolWriteIntents(); // in case we were doing some writing ourself  
  6.     {  
  7.         AlignedBuilder &ab = __theBuilder;  
  8.         // we need to make sure two group commits aren't running at the same time  
  9.         // (and we are only read locked in the dbMutex, so it could happen)  
  10.         SimpleMutex::scoped_lock lk(commitJob.groupCommitMutex);  
  11.         commitJob.commitingBegin();  
  12.         if( !commitJob.hasWritten() ) {//没有数据需要提交  
  13.             // getlasterror request could have came after the data was already committed  
  14.             commitJob.committingNotifyCommitted();  
  15.         }  
  16.         else {  
  17.             JSectHeader h;  
  18.             PREPLOGBUFFER(h,ab);//将所有操作buffer准备到ab中  
  19.             // todo : write to the journal outside locks, as this write can be slow.  
  20.             //        however, be careful then about remapprivateview as that cannot be done   
  21.             //        if new writes are then pending in the private maps.  
  22.             WRITETOJOURNAL(h, ab);//这里已经成功将日志写入了日志文件中  
  23.             // data is now in the journal, which is sufficient for acknowledging getLastError.  
  24.             // (ok to crash after that)  
  25.             commitJob.committingNotifyCommitted();  
  26.             WRITETODATAFILES(h, ab);//这里真正的将数据写入到了文件中  
  27.             debugValidateAllMapsMatch();//验证dur所在映射是否与文件本身映射相等  
  28.             commitJob.committingReset();  
  29.             ab.reset();  
  30.         }  
  31.     }  
  32.     // REMAPPRIVATEVIEW  
  33.     //  
  34.     // remapping private views must occur after WRITETODATAFILES otherwise  
  35.     // we wouldn't see newly written data on reads.  
  36.     //  
  37.     if( !Lock::isW() ) {//这里就是之前描述的_view_private的remap动作,释放内存,然后再次map  
  38.         // REMAPPRIVATEVIEW needs done in a write lock (as there is a short window during remapping when each view   
  39.         // might not exist) thus we do it later.  
  40.         //   
  41.         // if commitIfNeeded() operations are not in a W lock, you could get too big of a private map   
  42.         // on a giant operation.  for now they will all be W.  
  43.         //   
  44.         // If desired, perhaps this can be eliminated on posix as it may be that the remap is race-free there.  
  45.         //  
  46.         // For durthread, lgw is set, and we can upgrade to a W lock for the remap. we do this way as we don't want   
  47.         // to be in W the entire time we were committing about (in particular for WRITETOJOURNAL() which takes time).  
  48.         if( lgw ) {   
  49.             LOG(4) << "_groupCommit upgrade" << endl;  
  50.             lgw->upgrade();  
  51.             REMAPPRIVATEVIEW();  
  52.         }  
  53.     }  
  54.     else {  
  55.         stats.curr->_commitsInWriteLock++;  
  56.         // however, if we are already write locked, we must do it now -- up the call tree someone  
  57.         // may do a write without a new lock acquisition.  this can happen when MongoMMF::close() calls  
  58.         // this method when a file (and its views) is about to go away.  
  59.         //  
  60.         REMAPPRIVATEVIEW();  
  61.     }  
  62. }  
_groupCommit->PREPLOGBUFFER
[cpp]  view plain copy
  1. void PREPLOGBUFFER(/*out*/ JSectHeader& h, AlignedBuilder& ab) {  
  2.     assertLockedForCommitting();  
  3.     Timer t;//如果日志文件没有打开则打开一个日志文件并向其写入日志头JHeader  
  4.     j.assureLogFileOpen(); // so fileId is set  
  5.     _PREPLOGBUFFER(h, ab);//实际的操作以及数据写入  
  6.     stats.curr->_prepLogBufferMicros += t.micros();  
  7. }  
_groupCommit->PREPLOGBUFFER->_PREPLOGBUFFER
[cpp]  view plain copy
  1. static void _PREPLOGBUFFER(JSectHeader& h, AlignedBuilder& bb) {  
  2.     resetLogBuffer(h, bb); // adds JSectHeader得到JSecHeader头  
  3.     // ops other than basic writes (DurOp's)  
  4.     {//记录如创建文件删除文件等操作  
  5.         for( vector< shared_ptr<DurOp> >::iterator i = commitJob.ops().begin(); i != commitJob.ops().end(); ++i ) {  
  6.             (*i)->serialize(bb);  
  7.         }  
  8.     }  
  9.     prepBasicWrites(bb);  
  10.     return;  
  11. }  
_groupCommit->PREPLOGBUFFER->_PREPLOGBUFFER->prepBasicWrites
[cpp]  view plain copy
  1. static void prepBasicWrites(AlignedBuilder& bb) {  
  2.     scoped_lock lk(privateViews._mutex());  
  3.     // each time events switch to a different database we journal a JDbContext  
  4.     // switches will be rare as we sort by memory location first and we batch commit.  
  5.     RelativePath lastDbPath;  
  6.     const vector<WriteIntent>& _intents = commitJob.getIntentsSorted();  
  7.     WriteIntent last;  
  8.     for( vector<WriteIntent>::const_iterator i = _intents.begin(); i != _intents.end(); i++ ) {   
  9.         if( i->start() < last.end() ) {//两个地址有重合,合并其  
  10.             // overlaps  
  11.             last.absorb(*i);//合并到last中  
  12.         }  
  13.         else {   
  14.             // discontinuous  
  15.             if( i != _intents.begin() )  
  16.                 prepBasicWrite_inlock(bb, &last, lastDbPath);  
  17.             last = *i;  
  18.         }  
  19.     }  
  20.     prepBasicWrite_inlock(bb, &last, lastDbPath);  
  21. }  
_groupCommit->PREPLOGBUFFER->_PREPLOGBUFFER->prepBasicWrites->prepBasicWrite_inlock

[cpp]  view plain copy
  1. /** put the basic write operation into the buffer (bb) to be journaled */  
  2. static void prepBasicWrite_inlock(AlignedBuilder&bb, const WriteIntent *i, RelativePath& lastDbPath) {  
  3.     size_t ofs = 1;  
  4.     MongoMMF *mmf = findMMF_inlock(i->start(), /*out*/ofs);//ofs表示从文件的ofs偏移量开始写  
  5.     if( unlikely(!mmf->willNeedRemap()) ) {  
  6.         // tag this mmf as needed a remap of its private view later.  
  7.         // usually it will already be dirty/already set, so we do the if above first  
  8.         // to avoid possibility of cpu cache line contention  
  9.         mmf->willNeedRemap() = true;  
  10.     }  
  11.     // since we have already looked up the mmf, we go ahead and remember the write view location  
  12.     // so we don't have to find the MongoMMF again later in WRITETODATAFILES()  
  13.     //   
  14.     // this was for WRITETODATAFILES_Impl2 so commented out now  
  15.     //  
  16.     /* 
  17.     i->w_ptr = ((char*)mmf->view_write()) + ofs; 
  18.     */  
  19.     JEntry e;//一段数据的修改就是一个JEntry结构  
  20.     e.len = min(i->length(), (unsigned)(mmf->length() - ofs)); //dont write past end of file  
  21.     verify( ofs <= 0x80000000 );  
  22.     e.ofs = (unsigned) ofs;  
  23.     e.setFileNo( mmf->fileSuffixNo() );  
  24.     if( mmf->relativePath() == local ) {  
  25.         e.setLocalDbContextBit();  
  26.     }  
  27.     else if( mmf->relativePath() != lastDbPath ) {  
  28.         lastDbPath = mmf->relativePath();  
  29.         JDbContext c;  
  30.         bb.appendStruct(c);  
  31.         bb.appendStr(lastDbPath.toString());  
  32.     }  
  33.     bb.appendStruct(e);  
  34.     bb.appendBuf(i->start(), e.len);//记录实际写的数据  
  35.     if (unlikely(e.len != (unsigned)i->length())) {  
  36.         log() << "journal info splitting prepBasicWrite at boundary" << endl;  
  37.         // This only happens if we write to the last byte in a file and  
  38.         // the fist byte in another file that is mapped adjacently. I  
  39.         // think most OSs leave at least a one page gap between  
  40.         // mappings, but better to be safe.  
  41.         WriteIntent next ((char*)i->start() + e.len, i->length() - e.len);  
  42.         prepBasicWrite_inlock(bb, &next, lastDbPath);  
  43.     }  
  44. }  
通过上面我们已经分析完完了持久化log的产生,下面继续WRITETOJOURNAL,持久化log的写入动作.

[cpp]  view plain copy
  1. void WRITETOJOURNAL(JSectHeader h, AlignedBuilder& uncompressed) {  
  2.     Timer t;  
  3.     j.journal(h, uncompressed);  
  4.     stats.curr->_writeToJournalMicros += t.micros();  
  5. }  
[cpp]  view plain copy
  1. void Journal::journal(const JSectHeader& h, const AlignedBuilder& uncompressed) {  
  2.     static AlignedBuilder b(32*1024*1024);  
  3.     /* buffer to journal will be 
  4.        JSectHeader 
  5.        compressed operations 
  6.        JSectFooter 
  7.     */  
  8.     const unsigned headTailSize = sizeof(JSectHeader) + sizeof(JSectFooter);  
  9.     const unsigned max = maxCompressedLength(uncompressed.len()) + headTailSize;  
  10.     b.reset(max);  
  11.     {  
  12.         dassert( h.sectionLen() == (unsigned) 0xffffffff ); // we will backfill later  
  13.         b.appendStruct(h);//添加JSectHeader头  
  14.     }  
  15.     size_t compressedLength = 0;//日志的压缩,使用snappy能达到很快的压缩速度,虽然压缩率不如zip之类的  
  16.     rawCompress(uncompressed.buf(), uncompressed.len(), b.cur(), &compressedLength);  
  17.     b.skip(compressedLength);  
  18.     // footer  
  19.     unsigned L = 0xffffffff;  
  20.     {  
  21.         // pad to alignment, and set the total section length in the JSectHeader  
  22.         unsigned lenUnpadded = b.len() + sizeof(JSectFooter);  
  23.         L = (lenUnpadded + Alignment-1) & (~(Alignment-1));  
  24.         ((JSectHeader*)b.atOfs(0))->setSectionLen(lenUnpadded);  
  25.         JSectFooter f(b.buf(), b.len()); // computes checksum  
  26.         b.appendStruct(f);//添加JSectFooter尾  
  27.         b.skip(L - lenUnpadded);  
  28.     }  
  29.      {  
  30.         SimpleMutex::scoped_lock lk(_curLogFileMutex);  
  31.         // must already be open -- so that _curFileId is correct for previous buffer building  
  32.         verify( _curLogFile );  
  33.         stats.curr->_uncompressedBytes += uncompressed.len();  
  34.         unsigned w = b.len();  
  35.         _written += w;  
  36.         verify( w <= L );  
  37.         stats.curr->_journaledBytes += L;//将日志记录写入日志文件中  
  38.         _curLogFile->synchronousAppend((const void *) b.buf(), L);  
  39.         _rotate();//  
  40.     }  
  41. }  
WRITETOJOURNAL->_rotate

[cpp]  view plain copy
  1. void Journal::_rotate() {  
  2.       j.updateLSNFile();//更新时间戳到上一次DataFileSync刷新的时间,以后系统故障还原时只需要还原比这个时间戳更新的日志就行了  
  3.       if( _curLogFile && _written < DataLimitPerJournalFile )//达到单个日志文件大小的上限,32位上限默认为256M,64位为1G,若设置了--smallfiles则大小为128M,达到这个上线后重新打开一个新的日志文件  
  4.           return;  
  5.       if( _curLogFile ) {  
  6.           _curLogFile->truncate();//截断日志文件,并开启新的日志文件  
  7.           closeCurrentJournalFile();  
  8.           removeUnneededJournalFiles();//移出过时的日志文件,可能将其加入到预分配的文件中  
  9.       }  
  10.           Timer t;  
  11.           _open();  
  12.           int ms = t.millis();  
  13.           if( ms >= 200 ) {  
  14.               log() << "DR101 latency warning on journal file open " << ms << "ms" << endl;  
  15.           }  
  16.   }  
上面完成了日志的写入工作,继续_groupCommit函数.

[cpp]  view plain copy
  1. void WRITETODATAFILES(const JSectHeader& h, AlignedBuilder& uncompressed) {  
  2.     Timer t;  
  3.     WRITETODATAFILES_Impl1(h, uncompressed);//写入到_view_write映射中用于DataFileSync刷到磁盘中  
  4.     unsigned long long m = t.micros();  
  5.     stats.curr->_writeToDataFilesMicros += m;  
  6. }  
[cpp]  view plain copy
  1. static void WRITETODATAFILES_Impl1(const JSectHeader& h, AlignedBuilder& uncompressed) {  
  2.     LockMongoFilesShared lk;//解析之前产生的日志信息,将记录写到_view_write映射中  
  3.     RecoveryJob::get().processSection(&h, uncompressed.buf(), uncompressed.len(), 0);  
  4. }  
[cpp]  view plain copy
  1. void RecoveryJob::processSection(const JSectHeader *h, const void *p, unsigned len, const JSectFooter *f) {  
  2.     scoped_lock lk(_mx);  
  3.     /** todo: we should really verify the checksum to see that seqNumber is ok? 
  4.               that is expensive maybe there is some sort of checksum of just the header  
  5.               within the header itself  */  
  6.    //启动时的recovering,我们这里需要跳过,_lastDataSyncedFromLastRun就是之前写入的最新的刷新时间戳,  
  7.           //晚于这个时间的日志才需要恢复  
  8.     if( _recovering && _lastDataSyncedFromLastRun > h->seqNumber + ExtraKeepTimeMs ) {  
  9.         if( h->seqNumber != _lastSeqMentionedInConsoleLog ) {  
  10.             static int n;  
  11.             if( ++n < 10 ) {  
  12.                 log() << "recover skipping application of section seq:" << h->seqNumber << " < lsn:" << _lastDataSyncedFromLastRun << endl;  
  13.             }  
  14.             else if( n == 10 ) {   
  15.                 log() << "recover skipping application of section more..." << endl;  
  16.             }  
  17.             _lastSeqMentionedInConsoleLog = h->seqNumber;  
  18.         }  
  19.         return;  
  20.     }  
  21.     auto_ptr<JournalSectionIterator> i;  
  22.     if( _recovering ) {  
  23.         i = auto_ptr<JournalSectionIterator>(new JournalSectionIterator(*h, p, len, _recovering));  
  24.     }  
  25.     else {   
  26.         i = auto_ptr<JournalSectionIterator>(new JournalSectionIterator(*h, /*after header*/p, /*w/out header*/len));  
  27.     }  
  28.     // we use a static so that we don't have to reallocate every time through.  occasionally we   
  29.     // go back to a small allocation so that if there were a spiky growth it won't stick forever.  
  30.     static vector<ParsedJournalEntry> entries;  
  31.     entries.clear();  
  32.     // first read all entries to make sure this section is valid  
  33.     ParsedJournalEntry e;  
  34.     while( !i->atEof() ) {//读出所有entry,上面分析代码我们知道1个entry就对应于一片修改的区域  
  35.         i->next(e);  
  36.         entries.push_back(e);  
  37.     }  
  38.     // after the entries check the footer checksum  
  39.     if( _recovering ) {//恢复操作的校验  
  40.         verify( ((const char *)h) + sizeof(JSectHeader) == p );  
  41.         if( !f->checkHash(h, len + sizeof(JSectHeader)) ) {   
  42.             msgasserted(13594, "journal checksum doesn't match");  
  43.         }  
  44.     }  
  45.     // got all the entries for one group commit.  apply them:  
  46.     applyEntries(entries);  
  47. }  
_groupCommit->WRITETODATAFILES->WRITETODATAFILES_Impl1->processSection->applyEntries

[cpp]  view plain copy
  1. void RecoveryJob::applyEntries(const vector<ParsedJournalEntry> &entries) {  
  2.     bool apply = (cmdLine.durOptions & CmdLine::DurScanOnly) == 0;  
  3.     bool dump = cmdLine.durOptions & CmdLine::DurDumpJournal;//循环写每一个entry  
  4.     for( vector<ParsedJournalEntry>::const_iterator i = entries.begin(); i != entries.end(); ++i ) {  
  5.         applyEntry(*i, apply, dump);  
  6.     }  
  7. }  

_groupCommit->WRITETODATAFILES->WRITETODATAFILES_Impl1->processSection->applyEntries->applyEntry

[cpp]  view plain copy
  1. void RecoveryJob::applyEntry(const ParsedJournalEntry& entry, bool apply, bool dump) {  
  2.     if( entry.e ) {  
  3.         if( apply ) {//数据的更改  
  4.             write(entry);  
  5.         }  
  6.     }  
  7.     else if(entry.op) {  
  8.         // a DurOp subclass operation  
  9.         if( apply ) {//文件的创建或者删除  
  10.             if( entry.op->needFilesClosed() ) {  
  11.                 _close(); // locked in processSection  
  12.             }  
  13.             entry.op->replay();//重新执行一次上一次指定的操作,这里不再分析  
  14.         }  
  15.     }  
  16. }  

_groupCommit->WRITETODATAFILES->WRITETODATAFILES_Impl1->processSection->applyEntries->applyEntry->write

[cpp]  view plain copy
  1. void RecoveryJob::write(const ParsedJournalEntry& entry) {  
  2.     const string fn = fileName(entry.dbName, entry.e->getFileNo());  
  3.     MongoFile* file;  
  4.     {  
  5.         MongoFileFinder finder; // must release lock before creating new MongoMMF  
  6.         file = finder.findByPath(fn);//根据Entry中的信息得到对于的MongoMMF  
  7.     }  
  8.     MongoMMF* mmf;  
  9.     if (file) {  
  10.         verify(file->isMongoMMF());  
  11.         mmf = (MongoMMF*)file;  
  12.     }  
  13.     else {  
  14.         boost::shared_ptr<MongoMMF> sp (new MongoMMF);  
  15.         verify(sp->open(fn, false));  
  16.         _mmfs.push_back(sp);  
  17.         mmf = sp.get();  
  18.     }  
  19.     if ((entry.e->ofs + entry.e->len) <= mmf->length()) {  
  20.         verify(mmf->view_write());  
  21.         verify(entry.e->srcData());//这里出现实际的_view_write映射位置,将数据写入到该映射中,后面DataFileSync将数据从这个映射中的胀数据刷到磁盘中  
  22.         void* dest = (char*)mmf->view_write() + entry.e->ofs;  
  23.         memcpy(dest, entry.e->srcData(), entry.e->len);  
  24.         stats.curr->_writeToDataFilesBytes += entry.e->len;  
  25.     }  
  26.     else {  
  27.         massert(13622, "Trying to write past end of file in WRITETODATAFILES", _recovering);  
  28.     }  
  29. }  
        到这里写入到实际的映射的工作完成,commit最后可能为_view_private做重新的映射.下面我们来看看

日志文件的结构.其中的JEntry结构可能会换成文件的创建和删除操作,JHeader后的JSectHeader到JFooter

会不断的重复,图上并未画出来.


        通过上面上面的分析我们已经详细了解了日志的创建以及数据的提交,下面来看看启动时候日志的检查

与恢复.持久化的是在dur.cpp中的函数startup中进行的.

[cpp]  view plain copy
  1. void startup() {  
  2.     if( !cmdLine.dur )  
  3.         return;  
  4.     DurableInterface::enableDurability();  
  5.     journalMakeDir();//创建journal目录  
  6.     recover();//全局锁下调用_recover()做日志恢复工作  
  7.     preallocateFiles();//如果允许预分配日志文件这里就进行日志文件的预分配,默认为true,可以--nopreallocj关闭  
  8.     boost::thread t(durThread);//创建持久化线程,journalCommitInterval ms提交一次,设置为0则为默认,默认情况下日志文件与数据库同分区则100ms一次,否则30ms一次        }  
[cpp]  view plain copy
  1. void _recover() {  
  2.     boost::filesystem::path p = getJournalDir();  
  3.     vector<boost::filesystem::path> journalFiles;  
  4.     getFiles(p, journalFiles);//得到所有的日志文件  
  5.     RecoveryJob::get().go(journalFiles);//正式做恢复工作  
  6. }  
[cpp]  view plain copy
  1. void RecoveryJob::go(vector<boost::filesystem::path>& files) {  
  2.     _recovering = true;  
  3.     // load the last sequence number synced to the datafiles on disk before the last crash  
  4.     _lastDataSyncedFromLastRun = journalReadLSN();//读出要恢复的起始时间戳  
  5.     for( unsigned i = 0; i != files.size(); ++i ) {  
  6.   bool abruptEnd = processFile(files[i]);//实际做每一个文件的处理  
  7.     }  
  8.     close();  
  9.     removeJournalFiles();//移出日志文件,可能循环再利用将其变成预分配日志文件  
  10.     okToCleanUp = true;  
  11.     _recovering = false;  
  12. }  
[cpp]  view plain copy
  1. bool RecoveryJob::processFile(boost::filesystem::path journalfile) {  
  2.     MemoryMappedFile f;//首先将日志文件映射到内存  
  3.     void *p = f.mapWithOptions(journalfile.string().c_str(), MongoFile::READONLY | MongoFile::SEQUENTIAL);  
  4.     return processFileBuffer(p, (unsigned) f.length());  
  5. }  
[cpp]  view plain copy
  1. bool RecoveryJob::processFileBuffer(const void *p, unsigned len) {  
  2.     try {  
  3.         unsigned long long fileId;  
  4.         BufReader br(p,len);  
  5.         {  
  6.             // read file header  
  7.             JHeader h;  
  8.             br.read(h);//日志文件头,得到其特有的fileid  
  9.             /* [dm] not automatically handled.  we should eventually handle this automatically.  i think: 
  10.                (1) if this is the final journal file 
  11.                (2) and the file size is just the file header in length (or less) -- this is a bit tricky to determine if prealloced 
  12.                then can just assume recovery ended cleanly and not error out (still should log). 
  13.             */  
  14.             fileId = h.fileId;  
  15.         }  
  16.         // read sections  
  17.         while ( !br.atEof() ) {//上面说过一次提交形成一个JSectHeader,这里一个一个Section处理  
  18.             JSectHeader h;  
  19.             br.peek(h);  
  20.             if( h.fileId != fileId ) {//表明日志文件有错误  
  21.                 return true;  
  22.             }  
  23.             unsigned slen = h.sectionLen();  
  24.             unsigned dataLen = slen - sizeof(JSectHeader) - sizeof(JSectFooter);  
  25.             const char *hdr = (const char *) br.skip(h.sectionLenWithPadding());  
  26.             const char *data = hdr + sizeof(JSectHeader);  
  27.             const char *footer = data + dataLen;//这个函数上面已经分析,这里不再分析  
  28.             processSection((const JSectHeader*) hdr, data, dataLen, (const JSectFooter*) footer);  
  29.             // ctrl c check  
  30.             killCurrentOp.checkForInterrupt(false);  
  31.         }  
  32.     }  
  33.     return false// non-abrupt end  
  34. }  

        到这里,mongodb持久化的代码分析完了,需要注意的是通过代码的分析我们发现,在突然故障时mongodb

还是会丢失部分数据.双map中为了防止持久化专用的map _view_private过大,也会经常remap,来释放物理内存.


原文链接:mongodb源码分析(十三)持久化

作者:yhjj0108,杨浩


 

mongodb源码分析(十四)replication主从模式


    mongodb提供数据的复制机制,老的master/slave和新的replset模式,本文分析老的master/slave

机制,replset在下一篇文中分析.master/slave机制是一台主服务器,其它的从服务器,从服务器从主服务

器中读出操作记录,然后在自己这端重现操作,达到和主服务器一致的目的.主从服务器是启动时设定的,

之间无法动态的切换,其提供数据的备份机制,默认情况下从服务器是不能读写的,需要读操作那么可以调

用rs.slaveOk(),这样每次对从服务器的查询都会带上标志QueryOption_SlaveOk表示可以读从服务器.

        主从模式的流程,主服务器将每一次的操作记录到local.oplog.$main中,这个集合是capped,集合大

小固定,可以通过--oplogSize设置其大小,单位是M.默认情况下32位系统大小为50M,64位系统最小为

990M,最大为数据库所在磁盘的可用空间的5%.

       从服务器首先从主服务器复制一份数据库数据,然后就只从主服务器的local.oplog.$main集合中读

取操作记录然后replay了.如果由于local.oplog.$main上的操作时间戳超过了从服务器,这说明主服务器

的操作记录已经被更新的操作记录覆盖了,但是从服务器没有读取到做replay,从服务器只能再次完全从

主服务器中拷贝一份数据库了.下面是本文分析到的collection的作用.

local.sources: 记录从服务器要同步的主服务器地址.

local.oplog.$main: 主服务器的binlog.

       下面来看代码吧.主服务器的启动是通过--master完成的,入口函数为repl.cpp startReplication.删除

了不相关的代码.

[cpp]  view plain copy
  1. void startReplication() {  
  2.     oldRepl();//设置记录binlog的函数指针.  
  3.     {  
  4.         Lock::GlobalWrite lk;  
  5.         replLocalAuth();//增加本地用户local数据库_repl账户写权限  
  6.     }  
  7.     if ( replSettings.slave ) {//从服务器的线程完成读取local.oplog.$main并且replay  
  8.         boost::thread repl_thread(replSlaveThread);  
  9.     }  
  10.     if ( replSettings.master ) {  
  11.         replSettings.master = true;  
  12.         createOplog();//若未建立local.oplog.$main集合则在这里建立.  
  13.         boost::thread t(replMasterThread);//这个线程没做什么事  
  14.     }  
  15.     while( replSettings.fastsync ) // don't allow writes until we've set up from log  
  16.         sleepmillis( 50 );  
  17. }  
来看看主服务器的操作日志记录.操作日志分几种.

i: 插入操作.

u: 更新操作.

c db命令操作.

d: 删除操作.

n: 无操作,仅仅是一种心跳,告诉从服务器主服务器在正常运行.

继续logOp操作:

[cpp]  view plain copy
  1. void logOp(const char *opstr, const char *ns, const BSONObj& obj, BSONObj *patt, bool *b, bool fromMigrate) {  
  2.     if ( replSettings.master ) {//主服务器的log,记录到local.oplog.$main中  
  3.         _logOp(opstr, ns, 0, obj, patt, b, fromMigrate);  
  4.     }  
  5.   
  6.     logOpForSharding( opstr , ns , obj , patt );  
  7. }  
_logOp这种在初始化时设置为了_logOpOld.

[cpp]  view plain copy
  1. static void _logOpOld(const char *opstr, const char *ns, const char *logNS, const BSONObj& obj, BSONObj *o2, bool *bb, bool fromMigrate ) {  
  2.     Lock::DBWrite lk("local");  
  3.     static BufBuilder bufbuilder(8*1024); // todo there is likely a mutex on this constructor  
  4.     mutex::scoped_lock lk2(OpTime::m);  
  5.     const OpTime ts = OpTime::now(lk2);  
  6.     Client::Context context("",0,false);  
  7.     /* we jump through a bunch of hoops here to avoid copying the obj buffer twice -- 
  8.        instead we do a single copy to the destination position in the memory mapped file. 
  9.     */  
  10.     bufbuilder.reset();  
  11.     BSONObjBuilder b(bufbuilder);  
  12.     b.appendTimestamp("ts", ts.asDate());//记录日志时间,同步用  
  13.     b.append("op", opstr);  
  14.     b.append("ns", ns);  
  15.     if (fromMigrate)   
  16.         b.appendBool("fromMigrate"true);  
  17.     if ( bb )  
  18.         b.appendBool("b", *bb);  
  19.     if ( o2 )//只有update操作存在,为query对象  
  20.         b.append("o2", *o2);  
  21.     BSONObj partial = b.done(); // partial is everything except the o:... part.  
  22.     int po_sz = partial.objsize();  
  23.     int len = po_sz + obj.objsize() + 1 + 2 /*o:*/;  
  24.     Record *r;//这里完成空间分配  
  25.     if( logNS == 0 ) {  
  26.         logNS = "local.oplog.$main";  
  27.         if ( localOplogMainDetails == 0 ) {  
  28.             Client::Context ctx( logNS , dbpath, false);  
  29.             localDB = ctx.db();  
  30.             verify( localDB );  
  31.             localOplogMainDetails = nsdetails(logNS);  
  32.             verify( localOplogMainDetails );  
  33.         }  
  34.         Client::Context ctx( logNS , localDB, false );  
  35.         r = theDataFileMgr.fast_oplog_insert(localOplogMainDetails, logNS, len);  
  36.     }  
  37.     else {  
  38.         Client::Context ctx( logNS, dbpath, false );  
  39.         verify( nsdetails( logNS ) );  
  40.         // first we allocate the space, then we fill it below.  
  41.         r = theDataFileMgr.fast_oplog_insert( nsdetails( logNS ), logNS, len);  
  42.     }  
  43.     append_O_Obj(r->data(), partial, obj);//实际的数据插入  
  44.     context.getClient()->setLastOp( ts );//更新最后log的时间  
  45. }   
        下面来看看从服务器的同步工作,归结起来可以是加载同步的数据源,读取操作日志,replay.从服务

器的同步入口为replSlaveThread,其内部调用replMain做同步工作.下面直接从replMain开始分析.

[cpp]  view plain copy
  1. void replMain() {  
  2.     ReplSource::SourceVector sources;  
  3.     while ( 1 ) {  
  4.         int s = 0;  
  5.         {  
  6.             Lock::GlobalWrite lk;  
  7.             if ( replAllDead ) {//同步出现错误了,resync是删除数据库后再同步  
  8.                 // throttledForceResyncDead can throw  
  9.                 if ( !replSettings.autoresync || !ReplSource::throttledForceResyncDead( "auto" ) ) {  
  10.                     break;  
  11.                 }  
  12.             }  
  13.             verify( syncing == 0 ); // i.e., there is only one sync thread running. we will want to change/fix this.  
  14.             syncing++;  
  15.         }  
  16.         try {  
  17.             int nApplied = 0;  
  18.             s = _replMain(sources, nApplied);  
  19.             if( s == 1 ) {  
  20.                 if( nApplied == 0 ) s = 2;  
  21.                 else if( nApplied > 100 ) {  
  22.                     // sleep very little - just enought that we aren't truly hammering master  
  23.                     sleepmillis(75);  
  24.                     s = 0;  
  25.                 }  
  26.             }  
  27.         }  
  28.         catch (...) {  
  29.             out() << "caught exception in _replMain" << endl;  
  30.             s = 4;  
  31.         }  
  32.         {  
  33.             Lock::GlobalWrite lk;  
  34.             verify( syncing == 1 );  
  35.             syncing--;  
  36.         }  
  37.         if( relinquishSyncingSome )  {  
  38.             relinquishSyncingSome = 0;  
  39.             s = 1; // sleep before going back in to syncing=1  
  40.         }  
  41.         if ( s ) {  
  42.             stringstream ss;  
  43.             ss << "repl: sleep " << s << " sec before next pass";  
  44.             string msg = ss.str();  
  45.             if ( ! cmdLine.quiet )  
  46.                 log() << msg << endl;  
  47.             ReplInfo r(msg.c_str());  
  48.             sleepsecs(s);  
  49.         }  
  50.     }  
  51. }  
replMain->_replMain

[cpp]  view plain copy
  1. int _replMain(ReplSource::SourceVector& sources, int& nApplied) {  
  2.     {  
  3.         Lock::GlobalWrite lk;  
  4.         ReplSource::loadAll(sources);//加载要需要sync的源端  
  5.         replSettings.fastsync = false// only need this param for initial reset  
  6.     }  
  7.     int sleepAdvice = 1;  
  8.     for ( ReplSource::SourceVector::iterator i = sources.begin(); i != sources.end(); i++ ) {  
  9.         ReplSource *s = i->get();  
  10.         int res = -1;  
  11.         try {  
  12.             res = s->sync(nApplied);//从具体的主服务器端读取操作记录做同步工作  
  13.             bool moreToSync = s->haveMoreDbsToSync();  
  14.             if( res < 0 ) {  
  15.                 sleepAdvice = 3;  
  16.             }  
  17.             else if( moreToSync ) {  
  18.                 sleepAdvice = 0;  
  19.             }  
  20.             else if ( s->sleepAdvice() ) {  
  21.                 sleepAdvice = s->sleepAdvice();  
  22.             }  
  23.             else  
  24.                 sleepAdvice = res;  
  25.         }  
  26.         if ( res < 0 )  
  27.             s->oplogReader.resetConnection();  
  28.     }  
  29.     return sleepAdvice;  
  30. }  
replMain->_replMain->loadAll

[cpp]  view plain copy
  1. void ReplSource::loadAll(SourceVector &v) {  
  2.     Client::Context ctx("local.sources");  
  3.     SourceVector old = v;  
  4.     v.clear();  
  5.     if ( !cmdLine.source.empty() ) {  
  6.         // --source <host> specified.  
  7.         // check that no items are in sources other than that  
  8.         // add if missing  
  9.         shared_ptr<Cursor> c = findTableScan("local.sources", BSONObj());  
  10.         if ( n == 0 ) {//local.sources中不存在同步资源,这里加入  
  11.             // source missing.  add.  
  12.             ReplSource s;  
  13.             s.hostName = cmdLine.source;  
  14.             s.only = cmdLine.only;  
  15.             s.save();//将数据记录到local.sources集合中  
  16.         }  
  17.     }  
  18.  //这里加载的Cursor是Reverse的,加载最后一个需要同步的资源  
  19.     shared_ptr<Cursor> c = findTableScan("local.sources", BSONObj());  
  20.     while ( c->ok() ) {  
  21.         ReplSource tmp(c->current());  
  22.         if ( tmp.syncedTo.isNull() ) {  
  23.             DBDirectClient c;//这里从本地local.oplog.$main拿出当前同步到的时间点  
  24.             if ( c.exists( "local.oplog.$main" ) ) {//倒序查找最后一个操作不是n的记录并根据其记录sync时间  
  25.                 BSONObj op = c.findOne( "local.oplog.$main", QUERY( "op" << NE << "n" ).sort( BSON( "$natural" << -1 ) ) );  
  26.                 if ( !op.isEmpty() ) {  
  27.                     tmp.syncedTo = op[ "ts" ].date();  
  28.                 }  
  29.             }  
  30.         }  
  31.         addSourceToList(v, tmp, old);//加入每一个同步源  
  32.         c->advance();  
  33.     }  
  34. }  
replMain->_replMain->sync

[cpp]  view plain copy
  1. int ReplSource::sync(int& nApplied) {  
  2.     if ( !oplogReader.connect(hostName) ) {//连接master,并完成认证工作  
  3.         log(4) << "repl:  can't connect to sync source" << endl;  
  4.         return -1;  
  5.     }  
  6.     return sync_pullOpLog(nApplied);//获取操作日志与replay  
  7. }  

replMain->_replMain->sync->sync_pullOpLog

[cpp]  view plain copy
  1.   int ReplSource::sync_pullOpLog(int& nApplied) {  
  2.       int okResultCode = 1;  
  3.       string ns = string("local.oplog.$") + sourceName();  
  4.       bool tailing = true;  
  5.       oplogReader.tailCheck();  
  6.       bool initial = syncedTo.isNull();  
  7.       if ( !oplogReader.haveCursor() || initial ) {//初次同步数据  
  8.           if ( initial ) {  
  9.               // Important to grab last oplog timestamp before listing databases.  
  10.               syncToTailOfRemoteLog();//读取local.oplog.$main中的最新一条有用的操作数据,指定这次sync从哪个时间点开始  
  11.               BSONObj info;  
  12.               bool ok = oplogReader.conn()->runCommand( "admin", BSON( "listDatabases" << 1 ), info );  
  13.               BSONObjIterator i( info.getField( "databases" ).embeddedObject() );  
  14.               while( i.moreWithEOO() ) {//加入所有非空的并且不为local的数据库,若只指定了  
  15.                   BSONElement e = i.next();//only,则只加入only指定的数据库  
  16.                   string name = e.embeddedObject().getField( "name" ).valuestr();  
  17.                   if ( !e.embeddedObject().getBoolField( "empty" ) ) {  
  18.                       if ( name != "local" ) {  
  19.                           if ( only.empty() || only == name ) {  
  20.                               addDbNextPass.insert( name );  
  21.                           }  
  22.                       }  
  23.                   }  
  24.               }  
  25.               Lock::GlobalWrite lk;  
  26.               save();  
  27.           }  
  28. //初始化cursor,这里指定的查询条件是大于等于这个syncedTo,  
  29. //而这个syncedTo在slave第一次启动时第一次运行到这里时是  
  30. //master的指定的最后一条数据  
  31.           BSONObjBuilder q;  
  32.           q.appendDate("$gte", syncedTo.asDate());  
  33.           BSONObjBuilder query;  
  34.           query.append("ts", q.done());  
  35.           if ( !only.empty() ) {  
  36.               // note we may here skip a LOT of data table scanning, a lot of work for the master.  
  37.               // maybe append "\\." here?  
  38.               query.appendRegex("ns", string("^") + pcrecpp::RE::QuoteMeta( only ));  
  39.           }  
  40.           BSONObj queryObj = query.done();  
  41.           // e.g. queryObj = { ts: { $gte: syncedTo } }  
  42.   
  43.           oplogReader.tailingQuery(ns.c_str(), queryObj);  
  44.           tailing = false;  
  45.       }  
  46.       else {  
  47.           log(2) << "repl: tailing=true\n";  
  48.       }  
  49.       { // show any deferred database creates from a previous pass  
  50.           set<string>::iterator i = addDbNextPass.begin();  
  51.           if ( i != addDbNextPass.end() ) {//这里是待添加数据库的处理,一次处理一个  
  52.               BSONObjBuilder b;  
  53.               b.append("ns", *i + '.');  
  54.               b.append("op""db");  
  55.               BSONObj op = b.done();  
  56.               sync_pullOpLog_applyOperation(op, false);  
  57.           }  
  58.       }  
  59.       OpTime nextOpTime;  
  60.       {  
  61.           BSONObj op = oplogReader.next();  
  62.           BSONElement ts = op.getField("ts");  
  63.           nextOpTime = OpTime( ts.date() );  
  64.           if( tailing ) {  
  65.               oplogReader.putBack( op ); // op will be processed in the loop below  
  66.               nextOpTime = OpTime(); // will reread the op below  
  67.           }  
  68.       }  
  69.       {   // apply operations  
  70.           int n = 0;  
  71.           time_t saveLast = time(0);  
  72.           while ( 1 ) {  
  73.               bool moreInitialSyncsPending = !addDbNextPass.empty() && n; // we need "&& n" to assure we actually process at least one op to get a sync point recorded in the first place.  
  74.               if ( moreInitialSyncsPending || !oplogReader.more() ) {//还有数据库等待添加,这里只是保存了sync的时间戳  
  75.                   Lock::GlobalWrite lk;  
  76.                   if( oplogReader.awaitCapable() && tailing )  
  77.                       okResultCode = 0; // don't sleep  
  78.                   syncedTo = nextOpTime;  
  79.                   save(); // note how far we are synced up to now  
  80.                   nApplied = n;  
  81.                   break;  
  82.               }  
  83.               BSONObj op = oplogReader.next();  
  84.               unsigned b = replApplyBatchSize;  
  85.               bool justOne = b == 1;  
  86.               scoped_ptr<Lock::GlobalWrite> lk( justOne ? 0 : new Lock::GlobalWrite() );  
  87.               while( 1 ) {  
  88.                   BSONElement ts = op.getField("ts");  
  89.                   OpTime last = nextOpTime;  
  90.                   nextOpTime = OpTime( ts.date() );//这里sync的delay还没到,暂时不sync了,等待下一次sync  
  91.                   if ( replSettings.slavedelay && ( unsigned( time( 0 ) ) < nextOpTime.getSecs() + replSettings.slavedelay ) ) {  
  92.                       oplogReader.putBack( op );  
  93.                       _sleepAdviceTime = nextOpTime.getSecs() + replSettings.slavedelay + 1;  
  94.                       Lock::GlobalWrite lk;  
  95.                       if ( n > 0 ) {  
  96.                           syncedTo = last;  
  97.                           save();  
  98.                       }  
  99.                       return okResultCode;  
  100.                   }//实际的log处理  
  101.                   sync_pullOpLog_applyOperation(op, !justOne);  
  102.                   n++;  
  103.                   if( --b == 0 )  
  104.                       break;  
  105.                   // if to here, we are doing mulpile applications in a singel write lock acquisition  
  106.                   if( !oplogReader.moreInCurrentBatch() ) {  
  107.                       // break if no more in batch so we release lock while reading from the master  
  108.                       break;  
  109.                   }  
  110.                   op = oplogReader.next();  
  111.                   getDur().commitIfNeeded();  
  112.               }  
  113.           }  
  114.       }  
  115.       return okResultCode;  
  116.   }  

replMain->_replMain->sync->sync_pullOpLog->sync_pullOpLog_applyOperation

[cpp]  view plain copy
  1. void ReplSource::sync_pullOpLog_applyOperation(BSONObj& op, bool alreadyLocked) {  
  2.     if( op.getStringField("op")[0] == 'n' )  
  3.         return;  
  4.     char clientName[MaxDatabaseNameLen];  
  5.     const char *ns = op.getStringField("ns");  
  6.     nsToDatabase(ns, clientName);  
  7.     if ( !only.empty() && only != clientName )//slave启动时指定了only,只sync某一个数据库  
  8.         return;  
  9.   //将要更新的数据部分预加载到数据库中  
  10.     if( cmdLine.pretouch && !alreadyLocked/*doesn't make sense if in write lock already*/ ) {  
  11.         if( cmdLine.pretouch > 1 ) {  
  12.             /* note: this is bad - should be put in ReplSource.  but this is first test... */  
  13.             static int countdown;  
  14.             if( countdown > 0 ) {  
  15.                 countdown--; // was pretouched on a prev pass  
  16.             }  
  17.             else {  
  18.                 const int m = 4;  
  19.                 if( tp.get() == 0 ) {  
  20.                     int nthr = min(8, cmdLine.pretouch);  
  21.                     nthr = max(nthr, 1);  
  22.                     tp.reset( new ThreadPool(nthr) );  
  23.                 }  
  24.                 vector<BSONObj> v;  
  25.                 oplogReader.peek(v, cmdLine.pretouch);  
  26.                 unsigned a = 0;  
  27.                 while( 1 ) {  
  28.                     if( a >= v.size() ) break;  
  29.                     unsigned b = a + m - 1; // v[a..b]  
  30.                     if( b >= v.size() ) b = v.size() - 1;  
  31.                     tp->schedule(pretouchN, v, a, b);  
  32.                     a += m;  
  33.                 }  
  34.                 // we do one too...  
  35.                 pretouchOperation(op);  
  36.                 tp->join();  
  37.                 countdown = v.size();  
  38.             }  
  39.         }  
  40.         else {  
  41.             pretouchOperation(op);  
  42.         }  
  43.     }  
  44.     scoped_ptr<Lock::GlobalWrite> lk( alreadyLocked ? 0 : new Lock::GlobalWrite() );  
  45.   //如果待添加数据库与本地数据库同名,删除本地数据库  
  46.     if ( !handleDuplicateDbName( op, ns, clientName ) ) {  
  47.         return;     
  48.     }  
  49.     Client::Context ctx( ns );  
  50.     ctx.getClient()->curop()->reset();  
  51.     bool empty = ctx.db()->isEmpty();  
  52.     bool incompleteClone = incompleteCloneDbs.count( clientName ) != 0;  
  53.     // always apply admin command command  
  54.     // this is a bit hacky -- the semantics of replication/commands aren't well specified  
  55.     if ( strcmp( clientName, "admin" ) == 0 && *op.getStringField( "op" ) == 'c' ) {  
  56.         applyOperation( op );//admin的命令,直接执行了  
  57.         return;  
  58.     }  
  59.    //该数据库在本地(slave)才建立,这里克隆数据库到本地  
  60.     if ( ctx.justCreated() || empty || incompleteClone ) {  
  61.         // we must add to incomplete list now that setClient has been called  
  62.         incompleteCloneDbs.insert( clientName );  
  63.         if ( nClonedThisPass ) {//已经在克隆一个数据库了,下次再克隆另一个  
  64.             /* we only clone one database per pass, even if a lot need done.  This helps us 
  65.              avoid overflowing the master's transaction log by doing too much work before going 
  66.              back to read more transactions. (Imagine a scenario of slave startup where we try to 
  67.              clone 100 databases in one pass.) 
  68.              */  
  69.             addDbNextPass.insert( clientName );  
  70.         }  
  71.         else {  
  72.             save();  
  73.             Client::Context ctx(ns);  
  74.             nClonedThisPass++;  
  75.             resync(ctx.db()->name);//同步复制数据库,整个复制,也就是一个一个的collection复制,过程可能很慢  
  76.             addDbNextPass.erase(clientName);  
  77.             incompleteCloneDbs.erase( clientName );  
  78.         }  
  79.         save();  
  80.     }  
  81.     else {  
  82.         applyOperation( op );//这里将insert,update,delete等操作在本地执行一次,流程简单,不再分析  
  83.         addDbNextPass.erase( clientName );  
  84.     }  
  85. }  

        到这里master/slave模式分析完毕,主要需要注意的是数据库的复制,与当前同步的时间戳问题.

每一次都是查询从上一次同步到的时间戳到最新的时间戳,得到的结果必定是上一次同步到的时间

戳,否则说明主服务器操作太多,local.oplog.$main已经丢掉了老旧的操作日志,这时就只能重新复制

整个数据库了.


原文链接:mongodb源码分析(十四)replication主从模式

作者:yhjj0108,杨浩


 

mongodb源码分析(十五)replication replset模式的初始化


 相对于主从模式,replset模式复杂得多,其中的主从对应于这里的primary,secondary概念,primary和

secondary之间可以切换,primary掉线后能够自动的选取一个secondary成为新的primary,当然这里也是有

限制的,本文将会分析到.首先来看replset模式用到的几个集合.

local.oplog.rs: 记录replset模式下的操作日志,master/slave模式下为local.oplog.$main.

local.system.replset  replset模式的配置.就是rs.initiate,rs.add等设置的信息.

先来看看一个典型的replset 配置.

当我们写一个数据时如:

[cpp]  view plain copy
  1. db.foo.insert({x:1})  
  2. db.runCommand({getLastError:1,w:"veryImportant"})  
只有当这次写被写到了veryImportant指定的三个地方,如ny sf cloud时,getLastError才会返回成功,否则其

会一直等待.这种方式可以确保一份数据被写到了不同的服务器上.来看看另一种的replset配置.

[cpp]  view plain copy
  1. {_id:'myset', members:[{_id:0,host:'192.168.136.1:27040'},{_id:1,host:'192.168.136.1:27050',votes:0}]}  

这里只有两台服务器,若端口为27050的服务器关闭,那么27040端口的服务器还是primary.并不会转成secondary

并且无法工作.但是如下配置:

[cpp]  view plain copy
  1. {_id:'myset', members:[{_id:0,host:'192.168.136.1:27040'},{_id:1,host:'192.168.136.1:27050'}]}  
那么当27050关闭后27040将从primary转成secondary,整个replset将无法工作.原因在于这里的votes.mongodb的

replset规定在线的服务器的votes总和的两倍要大于所有replset中配置的服务器votes的总和,

2*online_votes>all_replset_config_vote,这时replset才能正常的工作,否则将无法正常的工作.如果不设置votes默

认其值为1.讨论另外一种情况,当27040的服务器掉线时那么27050的服务器将无法成为primary,系统将不再工作.

若一开始配置如下,27040的服务器成为primary,这个时候若27040掉线,27050将接管工作成为primary.但是若

27050掉线,那么服务器将变得不可用,因为votes值为0了.这里最好通过添加仲裁来解决问题,仲裁虽然只做投票,并

[cpp]  view plain copy
  1. {_id:'myset', members:[{_id:0,host:'192.168.136.1:27040',votes:0},{_id:1,host:'192.168.136.1:27050'}]}  

不会成为primary,secondary,但是其可以在一些服务器掉线时通过保证votes值让整个系统保持正常运行,所以

10gen也建议:

Deploy an arbiter to ensure that a replica set will have a sufficient number of members to elect a primary. While having replica sets with 2 members is not recommended for production environments, if you have just two members, deploy an arbiter. Also, for any replica set with an even number of members, deploy an arbiter.

继续看replset的流程.

1. 初始化时如果启动参数不配置replset那么启动时replset会不断的加载config.config的来源有三个.一是本地

local.system.replset集合中保存的数据,二是调用rs.initiate函数设置的config,三是来自其它replset集的心跳协

议传过来的.

2. 得到配置信息后初始化,和其它服务器建立心跳连接.

3. 启动同步线程,replset集都需要启动同步线程,但是只有secondary会去同步primary的数据.

4. 启动produce线程,这个线程负责向primary请求数据,同步线程从这个线程得到操作log然后在本地replay.

5. 启动时和后面的心态协议部分会调用msgCheckNewState更改服务器状态,从secondary转primary或者反之.

下面来看代码.首先从rs.initiate(cfg)初始化开始.初始化时执行replSetInitiate命令.直接转到该命令的执行.

[cpp]  view plain copy
  1. virtual bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) {  
  2.     if( cmdObj["replSetInitiate"].type() != Object ) {//配置数据来自于启动命令行  
  3.         string name;  
  4.         vector<HostAndPort> seeds;  
  5.         set<HostAndPort> seedSet;  
  6.         parseReplsetCmdLine(cmdLine._replSet, name, seeds, seedSet); // may throw...  
  7.         bob b;  
  8.         b.append("_id", name);  
  9.         bob members;  
  10.         members.append("0", BSON( "_id" << 0 << "host" << HostAndPort::me().toString() ));  
  11.         result.append("me", HostAndPort::me().toString());  
  12.         for( unsigned i = 0; i < seeds.size(); i++ )  
  13.             members.append(bob::numStr(i+1), BSON( "_id" << i+1 << "host" << seeds[i].toString()));  
  14.         b.appendArray("members", members.obj());  
  15.         configObj = b.obj();  
  16.     }  
  17.     else {//得到配置  
  18.         configObj = cmdObj["replSetInitiate"].Obj();  
  19.     }  
  20.     bool parsed = false;  
  21.     ReplSetConfig newConfig(configObj);//从配置数据中得到配置结构.  
  22.     parsed = true;  
  23.     checkMembersUpForConfigChange(newConfig, result, true);//查看配置的服务器是否能够连接  
  24.     createOplog();//建立local.system.replset集合.  
  25.     Lock::GlobalWrite lk;  
  26.     bo comment = BSON( "msg" << "initiating set");  
  27.     newConfig.saveConfigLocally(comment);//将配置保存到local.system.replset  
  28.     result.append("info""Config now saved locally.  Should come online in about a minute.");  
  29.     ReplSet::startupStatus = ReplSet::SOON;  
  30.     ReplSet::startupStatusMsg.set("Received replSetInitiate - should come online shortly.");  
  31.     return true;  
  32. }  
run->ReplSetConfig

[cpp]  view plain copy
  1. ReplSetConfig::ReplSetConfig(BSONObj cfg, bool force) :  
  2.     _ok(false),_majority(-1)  
  3. {  
  4.     _constructed = false;  
  5.     clear();  
  6.     from(cfg);//具体的读取配置,每一个服务器得到一个MemberCfg,解析可能的setting设置.  
  7.     if( force ) {  
  8.         version += rand() % 100000 + 10000;  
  9.     }  
  10.     if( version < 1 )  
  11.         version = 1;  
  12.     _ok = true;  
  13.     _constructed = true;  
  14. }  
run->checkMembersUpForConfigChange

[cpp]  view plain copy
  1. void checkMembersUpForConfigChange(const ReplSetConfig& cfg, BSONObjBuilder& result, bool initial) {  
  2.     int failures = 0, allVotes = 0, allowableFailures = 0;  
  3.     int me = 0;  
  4.     for( vector<ReplSetConfig::MemberCfg>::const_iterator i = cfg.members.begin(); i != cfg.members.end(); i++ ) {  
  5.         allVotes += i->votes;//得到投票总数  
  6.     }  
  7.     allowableFailures = allVotes - (allVotes/2 + 1);//允许丢掉的投票数  
  8.     vector<string> down;  
  9.     for( vector<ReplSetConfig::MemberCfg>::const_iterator i = cfg.members.begin(); i != cfg.members.end(); i++ ) {  
  10.         // we know we're up  
  11.         if (i->h.isSelf()) {  
  12.             continue;  
  13.         }  
  14.         BSONObj res;  
  15.         {  
  16.             bool ok = false;  
  17.              {  
  18.                 int theirVersion = -1000;//心跳协议查看配置的服务器是否能够连接  
  19.                 ok = requestHeartbeat(cfg._id, "", i->h.toString(), res, -1, theirVersion, initial/*check if empty*/);  
  20.                 if( theirVersion >= cfg.version ) {  
  21.                     stringstream ss;  
  22.                     ss << "replSet member " << i->h.toString() << " has too new a config version (" << theirVersion << ") to reconfigure";  
  23.                     uasserted(13259, ss.str());  
  24.                 }  
  25.             }  
  26.             if( !ok && !res["rs"].trueValue() ) {//不能连接  
  27.                 down.push_back(i->h.toString());  
  28.                 bool allowFailure = false;  
  29.                 failures += i->votes;  
  30.                 if( !initial && failures <= allowableFailures ) {  
  31.                     const Member* m = theReplSet->findById( i->_id );  
  32.                     // it's okay if the down member isn't part of the config,  
  33.                     // we might be adding a new member that isn't up yet  
  34.                     allowFailure = true;  
  35.                 }  
  36.                 if( !allowFailure ) {//初始化时要求所有配置的服务器能够被连接  
  37.                     string msg = string("need all members up to initiate, not ok : ") + i->h.toString();  
  38.                     if( !initial )  
  39.                         msg = string("need most members up to reconfigure, not ok : ") + i->h.toString();  
  40.                     uasserted(13144, msg);  
  41.                 }  
  42.             }  
  43.         }  
  44.         if( initial ) {  
  45.             bool hasData = res["hasData"].Bool();  
  46.             uassert(13311, "member " + i->h.toString() + " has data already, cannot initiate set.  All members except initiator must be empty.",  
  47.                     !hasData || i->h.isSelf());  
  48.         }  
  49.     }  
  50.     if (down.size() > 0) {  
  51.         result.append("down", down);  
  52.     }  
  53. }  
run->saveConfigLocally

[cpp]  view plain copy
  1. void ReplSetConfig::saveConfigLocally(bo comment) {  
  2.     checkRsConfig();  
  3.     {  
  4.         Lock::GlobalWrite lk; // TODO: does this really need to be a global lock?  
  5.         Client::Context cx( rsConfigNs );  
  6.         cx.db()->flushFiles(true);  
  7.         //theReplSet->lastOpTimeWritten = ??;  
  8.         //rather than above, do a logOp()? probably  
  9.         BSONObj o = asBson();//得到实际的配置,下面的putSingletonGod将配置保存到local.system.replset中  
  10.         Helpers::putSingletonGod(rsConfigNs.c_str(), o, false/*logOp=false; local db so would work regardless...*/);  
  11.         if( !comment.isEmpty() && (!theReplSet || theReplSet->isPrimary()) )  
  12.             logOpInitiate(comment);  
  13.         cx.db()->flushFiles(true);  
  14.     }  
  15. }  

到这里初始化配置完成,下面看mongod启动时的初始化过程.启动部分是在repl.cpp startReplication

[cpp]  view plain copy
  1. void startReplication() {//和master/slave一样,启动都是在这个函数,只是流程不一样  
  2.     /* if we are going to be a replica set, we aren't doing other forms of replication. */  
  3.     if( !cmdLine._replSet.empty() ) {//replset指定了--replSet xxx,这里不为空表面是启动replSet模式  
  4.         newRepl();  
  5.         replSet = true;  
  6.         ReplSetCmdline *replSetCmdline = new ReplSetCmdline(cmdLine._replSet);//解析cmdline,cmdline可能是<setname>/<seedhost1>,<seedhost2>,那么启动的时候就指定了replSet的配置  
  7.         boost::thread t( boost::bind( &startReplSets, replSetCmdline) );//开启一个线程来做replSet的初始化  
  8.         return;  
  9.     }  
  10. }  
[cpp]  view plain copy
  1. void startReplSets(ReplSetCmdline *replSetCmdline) {  
  2.     Client::initThread("rsStart");  
  3.     replLocalAuth();  
  4.     (theReplSet = new ReplSet(*replSetCmdline))->go();//真正的初始化过程  
  5.     cc().shutdown();//关闭这个线程的client  
  6. }  
[cpp]  view plain copy
  1. ReplSet::ReplSet(ReplSetCmdline& replSetCmdline) : ReplSetImpl(replSetCmdline) {}  
[cpp]  view plain copy
  1. ReplSetImpl::ReplSetImpl(ReplSetCmdline& replSetCmdline) :   
  2.     elect(this),  
  3.     _forceSyncTarget(0),  
  4.     _blockSync(false),  
  5.     _hbmsgTime(0),  
  6.     _self(0),  
  7.     _maintenanceMode(0),  
  8.     mgr( new Manager(this) ),  
  9.     ghost( new GhostSync(this) ),  
  10.     _writerPool(replWriterThreadCount),  
  11.     _prefetcherPool(replPrefetcherThreadCount),  
  12.     _indexPrefetchConfig(PREFETCH_ALL) {  
  13.     _cfg = 0;  
  14.     memset(_hbmsg, 0, sizeof(_hbmsg));  
  15.     strcpy( _hbmsg , "initial startup" );  
  16.     lastH = 0;  
  17.     changeState(MemberState::RS_STARTUP);  
  18.     loadConfig();//加载replset的config,若config为空,则一直在其中循环加载,直到找到真正的config  
  19.     // Figure out indexPrefetch setting  
  20.     std::string& prefetch = cmdLine.rsIndexPrefetch;//通过--replIndexPrefetch启动设置,同步操作时首先预加载索引  
  21.     if (!prefetch.empty()) {  
  22.         IndexPrefetchConfig prefetchConfig = PREFETCH_ALL;  
  23.         if (prefetch == "none")  
  24.             prefetchConfig = PREFETCH_NONE;  
  25.         else if (prefetch == "_id_only")  
  26.             prefetchConfig = PREFETCH_ID_ONLY;  
  27.         else if (prefetch == "all")  
  28.             prefetchConfig = PREFETCH_ALL;  
  29.         else  
  30.             warning() << "unrecognized indexPrefetch setting: " << prefetch << endl;  
  31.         setIndexPrefetchConfig(prefetchConfig);  
  32.     }  
  33. }  
继续来看loadConfig的加载配置部分.

[cpp]  view plain copy
  1. void ReplSetImpl::loadConfig() {  
  2.     startupStatus = LOADINGCONFIG;  
  3.     while( 1 ) {  
  4.          {  
  5.             vector<ReplSetConfig> configs;  
  6.             configs.push_back( ReplSetConfig(HostAndPort::me()) );//从本地的local.system.replset查找配置,  
  7.                                                       //这里可能是上一次设置的或者是rs.initiate初始化时保存下来的设置  
  8.             for( vector<HostAndPort>::const_iterator i = _seeds->begin(); i != _seeds->end(); i++ )  
  9.                 configs.push_back( ReplSetConfig(*i) );//从启动时设置的位置查找配置  
  10.             {  
  11.                 scoped_lock lck( replSettings.discoveredSeeds_mx );  
  12.                 if( replSettings.discoveredSeeds.size() > 0 ) {//来自远端的心跳协议,通过心跳协议知道远端  
  13.                                                   //存在同一个replset集的服务器,从远端读取配置  
  14.                     for (set<string>::iterator i = replSettings.discoveredSeeds.begin();   
  15.                          i != replSettings.discoveredSeeds.end();   
  16.                          i++) {  
  17.                             configs.push_back( ReplSetConfig(HostAndPort(*i)) );  
  18.                     }  
  19.                 }  
  20.             }  
  21.             if (!replSettings.reconfig.isEmpty())//来自本地配置如rs.add等的新的配置  
  22.                 configs.push_back(ReplSetConfig(replSettings.reconfig, true));  
  23.             int nok = 0;  
  24.             int nempty = 0;  
  25.             for( vector<ReplSetConfig>::iterator i = configs.begin(); i != configs.end(); i++ ) {  
  26.                 if( i->ok() )//成功的配置个数  
  27.                     nok++;  
  28.                 if( i->empty() )  
  29.                     nempty++;  
  30.             }  
  31.             if( nok == 0 ) {//没有配置是可用的  
  32.                 if( nempty == (int) configs.size() ) {  
  33.                     startupStatus = EMPTYCONFIG;  
  34.                     static unsigned once;  
  35.                     if( ++once == 1 ) {  
  36.                         log() << "replSet info you may need to run replSetInitiate -- rs.initiate() in the shell -- if that is not already done" << rsLog;  
  37.                     }  
  38.                 }  
  39.                 sleepsecs(10);  
  40.                 continue;  
  41.             }  
  42.             if( !_loadConfigFinish(configs) ) {  
  43.                 sleepsecs(20);  
  44.                 continue;  
  45.             }  
  46.         }  
  47.         break;  
  48.     }  
  49.     startupStatus = STARTED;  
  50. }  
继续看_loadConfigFinish,这个函数从可用配置中找出版本最高的一个配置,然后使用其做初始化.

[cpp]  view plain copy
  1. bool ReplSetImpl::_loadConfigFinish(vector<ReplSetConfig>& cfgs) {  
  2.     int v = -1;  
  3.     ReplSetConfig *highest = 0;  
  4.     int myVersion = -2000;  
  5.     int n = 0;//选择一个版本最高的config,每当修改一次配置,如rs.add,rs.remove,version加一  
  6.     for( vector<ReplSetConfig>::iterator i = cfgs.begin(); i != cfgs.end(); i++ ) {  
  7.         ReplSetConfig& cfg = *i;  
  8.         if( ++n == 1 ) myVersion = cfg.version;  
  9.         if( cfg.ok() && cfg.version > v ) {  
  10.             highest = &cfg;  
  11.             v = cfg.version;  
  12.         }  
  13.     }  
  14.     if( !initFromConfig(*highest) )//使用该config初始化replset  
  15.         return false;  
  16.     if( highest->version > myVersion && highest->version >= 0 ) {//保存该配置  
  17.         highest->saveConfigLocally(BSONObj());//保存该config  
  18.     }  
  19.     return true;  
  20. }  
_loadConfigFinish->initFromConfig,主要流程是对于每一个服务器建立一个MemberCfg的结构,并对其启动心跳协议.

[cpp]  view plain copy
  1. bool ReplSetImpl::initFromConfig(ReplSetConfig& c, bool reconf/*=false*/) {  
  2.     lock lk(this);  
  3.     if( getLastErrorDefault || !c.getLastErrorDefaults.isEmpty() ) {  
  4.         // see comment in dbcommands.cpp for getlasterrordefault  
  5.         getLastErrorDefault = new BSONObj( c.getLastErrorDefaults );  
  6.     }  
  7.     list<ReplSetConfig::MemberCfg*> newOnes;  
  8.     // additive short-cuts the new config setup. If we are just adding a  
  9.     // node/nodes and nothing else is changing, this is additive. If it's  
  10.     // not a reconfig, we're not adding anything  
  11.     bool additive = reconf;  
  12.     {  
  13.         unsigned nfound = 0;  
  14.         int me = 0;  
  15.         for( vector<ReplSetConfig::MemberCfg>::iterator i = c.members.begin(); i != c.members.end(); i++ ) {  
  16.             ReplSetConfig::MemberCfg& m = *i;  
  17.             if( m.h.isSelf() ) {  
  18.                 me++;  
  19.             }  
  20.             if( reconf ) {//从新的配置  
  21.                 const Member *old = findById(m._id);  
  22.                 if( old ) {  
  23.                     nfound++;  
  24.                     if( old->config() != m ) {//同一台服务器配置配置更改了,如vote,priority更改  
  25.                         additive = false;  
  26.                     }  
  27.                 }  
  28.                 else {  
  29.                     newOnes.push_back(&m);  
  30.                 }  
  31.             }  
  32.         }//配置中没有本机地址,进入RS_SHUNNED状态,关闭所有连接,关闭心跳协议,重新进入加载配置状态  
  33.         if( me == 0 ) { // we're not in the config -- we must have been removed  
  34.             if (state().shunned()) {  
  35.                 // already took note of our ejection from the set  
  36.                 // so just sit tight and poll again  
  37.                 return false;  
  38.             }  
  39.             _members.orphanAll();  
  40.             // kill off rsHealthPoll threads (because they Know Too Much about our past)  
  41.             endOldHealthTasks();  
  42.             // close sockets to force clients to re-evaluate this member  
  43.             MessagingPort::closeAllSockets(0);  
  44.             // take note of our ejection  
  45.             changeState(MemberState::RS_SHUNNED);  
  46.             loadConfig();  // redo config from scratch  
  47.             return false;   
  48.         }  
  49.         // if we found different members that the original config, reload everything  
  50.         if( reconf && config().members.size() != nfound )  
  51.             additive = false;  
  52.     }  
  53.     _cfg = new ReplSetConfig(c);  
  54.     _name = config()._id;  
  55.     // this is a shortcut for simple changes  
  56.     if( additive ) {//reconfig配置的路径  
  57.         for( list<ReplSetConfig::MemberCfg*>::const_iterator i = newOnes.begin(); i != newOnes.end(); i++ ) {  
  58.             ReplSetConfig::MemberCfg *m = *i;  
  59.             Member *mi = new Member(m->h, m->_id, m, false);  
  60.             /** we will indicate that new members are up() initially so that we don't relinquish our 
  61.                 primary state because we can't (transiently) see a majority.  they should be up as we 
  62.                 check that new members are up before getting here on reconfig anyway. 
  63.                 */  
  64.             mi->get_hbinfo().health = 0.1;  
  65.             _members.push(mi);//新添加的member,启动心跳协议  
  66.             startHealthTaskFor(mi);  
  67.         }  
  68.         // if we aren't creating new members, we may have to update the  
  69.         // groups for the current ones  
  70.         _cfg->updateMembers(_members);//更新replset集中的member  
  71.         return true;  
  72.     }  
  73.     // start with no members.  if this is a reconfig, drop the old ones.  
  74.     _members.orphanAll();//这里不只是初始化的配置,还可能是因为修改了某些member的配置来到这里  
  75.     endOldHealthTasks();//所以结束所有心跳协议  
  76.     int oldPrimaryId = -1;  
  77.     {  
  78.         const Member *p = box.getPrimary();  
  79.         if( p )  
  80.             oldPrimaryId = p->id();  
  81.     }  
  82.     forgetPrimary();//重置primary为空,后面primary将重新设置  
  83.     // not setting _self to 0 as other threads use _self w/o locking  
  84.     int me = 0;  
  85.     string members = "";  
  86.     for( vector<ReplSetConfig::MemberCfg>::const_iterator i = config().members.begin(); i != config().members.end(); i++ ) {  
  87.         const ReplSetConfig::MemberCfg& m = *i;  
  88.         Member *mi;  
  89.         members += ( members == "" ? "" : ", " ) + m.h.toString();  
  90.         if( m.h.isSelf() ) {//该member是自己,且自己在配置前是primary,则再次将自己设置为primary,初始化时primary并不在这里决定  
  91.             mi = new Member(m.h, m._id, &m, true);  
  92.             setSelfTo(mi);  
  93.             if( (int)mi->id() == oldPrimaryId )  
  94.                 box.setSelfPrimary(mi);  
  95.         }  
  96.         else {  
  97.             mi = new Member(m.h, m._id, &m, false);  
  98.             _members.push(mi);  
  99.             if( (int)mi->id() == oldPrimaryId )  
  100.                 box.setOtherPrimary(mi);  
  101.         }  
  102.     }  
  103.     if( me == 0 ){  
  104.         log() << "replSet warning did not detect own host in full reconfig, members " << members << " config: " << c << rsLog;  
  105.     }  
  106.     else {//启动心跳设置,每有一个member就需要一个线程与之通信,每2s启动一次连接  
  107.         // Do this after we've found ourselves, since _self needs  
  108.         // to be set before we can start the heartbeat tasks  
  109.         for( Member *mb = _members.head(); mb; mb=mb->next() ) {  
  110.             startHealthTaskFor( mb );  
  111.         }  
  112.     }  
  113.     return true;  
  114. }  
_loadConfigFinish->initFromConfig->startHealthTaskFor
[cpp]  view plain copy
  1. void ReplSetImpl::startHealthTaskFor(Member *m) {  
  2.     ReplSetHealthPollTask *task = new ReplSetHealthPollTask(m->h(), m->hbinfo());  
  3.     healthTasks.insert(task);  
  4.     task::repeat(task, 2000);//这里开启一个新的线程,并与m指定的服务器建立连接2000ms,执行一次replSetHeartbeat,查看远端服务器是否可达  
  5. }  
继续来看ReplSetHealthPollTask执行命令的函数ReplSetHealthPollTask::doWork

[cpp]  view plain copy
  1. void doWork() {  
  2.     HeartbeatInfo mem = m;  
  3.     HeartbeatInfo old = mem;  
  4.     try {  
  5.         BSONObj info;  
  6.         int theirConfigVersion = -10000;//心跳协议查看是否能够连接远端服务器  
  7.         bool ok = _requestHeartbeat(mem, info, theirConfigVersion);  
  8.         // weight new ping with old pings  
  9.         // on the first ping, just use the ping value  
  10.         if (old.ping != 0) {//设置ping一次的时间  
  11.             mem.ping = (unsigned int)((old.ping * .8) + (mem.ping * .2));  
  12.         }  
  13.         if( ok ) {//远端服务器可达,则尝试将其加入到候选名单  
  14.             up(info, mem);  
  15.         }  
  16.         else if (!info["errmsg"].eoo() &&//心跳协议显示该机有问题,从候选名单中删除  
  17.                  info["errmsg"].str() == "need to login") {//无法成为primary了  
  18.             authIssue(mem);  
  19.         }  
  20.         else {//无法连接该机器  
  21.             down(mem, info.getStringField("errmsg"));  
  22.         }  
  23.     }  
  24.     catch(DBException& e) {  
  25.         down(mem, e.what());  
  26.     }  
  27.     catch(...) {  
  28.         down(mem, "replSet unexpected exception in ReplSetHealthPollTask");  
  29.     }  
  30.     m = mem;//更新该member的信息,包括状态如RS_STARTUP,RS_SECONDARY等  
  31.     theReplSet->mgr->send( boost::bind(&ReplSet::msgUpdateHBInfo, theReplSet, mem) );  
  32.     static time_t last = 0;  
  33.     time_t now = time(0);  
  34.     bool changed = mem.changed(old);  
  35.     if( changed ) {  
  36.         if( old.hbstate != mem.hbstate )  
  37.             log() << "replSet member " << h.toString() << " is now in state " << mem.hbstate.toString() << rsLog;  
  38.     }  
  39.     if( changed || now-last>4 ) {//需要进行一次状态检查.  
  40.         last = now;  
  41.         theReplSet->mgr->send( boost::bind(&Manager::msgCheckNewState, theReplSet->mgr) );  
  42.     }  
  43. }  
_loadConfigFinish->initFromConfig->startHealthTaskFor->up

[cpp]  view plain copy
  1. void up(const BSONObj& info, HeartbeatInfo& mem) {  
  2.     HeartbeatInfo::numPings++;  
  3.     mem.authIssue = false;  
  4.     if( mem.upSince == 0 ) {  
  5.         mem.upSince = mem.lastHeartbeat;  
  6.     }  
  7.     mem.health = 1.0;  
  8.     mem.lastHeartbeatMsg = info["hbmsg"].String();  
  9.     if( info.hasElement("opTime") )  
  10.         mem.opTime = info["opTime"].Date();  
  11.     // see if this member is in the electable set  
  12.     if( info["e"].eoo() ) {  
  13.         // for backwards compatibility  
  14.         const Member *member = theReplSet->findById(mem.id());  
  15.         if (member && member->config().potentiallyHot()) {//不是仲裁,且priority设置不为0,默认是1,为0则不可能成为primary  
  16.             theReplSet->addToElectable(mem.id());  
  17.         }  
  18.         else {  
  19.             theReplSet->rmFromElectable(mem.id());  
  20.         }  
  21.     }  
  22.     // add this server to the electable set if it is within 10  
  23.     // seconds of the latest optime we know of  
  24.     else if( info["e"].trueValue() &&  
  25.              mem.opTime >= theReplSet->lastOpTimeWritten.getSecs() - 10) {  
  26.         unsigned lastOp = theReplSet->lastOtherOpTime().getSecs();  
  27.         if (lastOp > 0 && mem.opTime >= lastOp - 10) {  
  28.             theReplSet->addToElectable(mem.id());  
  29.         }  
  30.     }  
  31.     else {  
  32.         theReplSet->rmFromElectable(mem.id());  
  33.     }  
  34.     be cfg = info["config"];  
  35.     if( cfg.ok() ) {//有新的config配置到来,更新配置  
  36.         // received a new config  
  37.         boost::function<void()> f =  
  38.             boost::bind(&Manager::msgReceivedNewConfig, theReplSet->mgr, cfg.Obj().copy());  
  39.         theReplSet->mgr->send(f);  
  40.     }  
  41. }  
[cpp]  view plain copy
  1. void down(HeartbeatInfo& mem, string msg) {  
  2.     mem.authIssue = false;//无法连接的服务器,将其标志为RS_DOWN,无法成为primary候选.  
  3.     mem.health = 0.0;  
  4.     mem.ping = 0;  
  5.     if( mem.upSince || mem.downSince == 0 ) {  
  6.         mem.upSince = 0;  
  7.         mem.downSince = jsTime();  
  8.         mem.hbstate = MemberState::RS_DOWN;  
  9.         log() << "replSet info " << h.toString() << " is down (or slow to respond): " << msg << rsLog;  
  10.     }  
  11.     mem.lastHeartbeatMsg = msg;  
  12.     theReplSet->rmFromElectable(mem.id());  
  13. }  
回到initFromConfig,该函数执行完毕,继续回到ReplSetImpl,该对象构造完毕.回到startReplSets继续

执行

[cpp]  view plain copy
  1. (theReplSet = new ReplSet(*replSetCmdline))->go();  
其执行的是ReplSetImpl::_go函数,继续来看这里的_go函数.

[cpp]  view plain copy
  1. void ReplSetImpl::_go() {  
  2.     loadLastOpTimeWritten();//得到最近一次写local.oplog.rs的时间,初始化时在saveConfigLocally时第一次写  
  3.     changeState(MemberState::RS_STARTUP2);  
  4.     startThreads();//开启同步线程,读取操作日志的线程.  
  5.     newReplUp(); // oplog.cpp设置新的log函数  
  6. }  

[cpp]  view plain copy
  1. void ReplSetImpl::startThreads() {  
  2.     task::fork(mgr);//这里启动管理服务,可以通过如下mgr->send让其执行send指定的函数,其内部是一个做服务的线程,接收执行任务,然后执行  
  3.     mgr->send( boost::bind(&Manager::msgCheckNewState, theReplSet->mgr) );  
  4.     if (myConfig().arbiterOnly) {//该服务器只执行仲裁动作  
  5.         return;  
  6.     }  
  7.     boost::thread t(startSyncThread);//这个线程除了sync外还有一个功能将当前服务器设置为secondary,初始化时到这里其状态为RS_STARTUP2  
  8.     replset::BackgroundSync* sync = replset::BackgroundSync::get();  
  9.     boost::thread producer(boost::bind(&replset::BackgroundSync::producerThread, sync));//为syncThread获取同步数据  
  10.     boost::thread notifier(boost::bind(&replset::BackgroundSync::notifierThread, sync));//为tags准备的,后面会有一篇文章专门讲到replset tags  
  11.     task::fork(ghost);  
  12.     // member heartbeats are started in ReplSetImpl::initFromConfig  
  13. }  

本文就分析到这里,几个线程的作用以及状态的切换留待下文.总结:

        本文分析replication replset模式初始化流程,初始化过程中主要是根据配置信息在各个服务器间建

立心跳协议,保证连接可达,根据连接信息更新各个服务器的状态,为下一步选取primary做准备.


本文链接:mongodb源码分析(十五)replication replset模式的初始化

作者:yhjj0108,杨浩


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值