mongodb源码分析(十八)replication replset tags

        本无意写这篇文章,但是之前在分析replset时有一个线程一直没弄明白其作用,后来偶然间在阅读tags时搞明白了notifierthread的作用,tags的实现过程很隐晦.不仔细阅读,很难弄明白,所以这里专门写一篇文章来分析replset tags的实现.首先来看看一份带tags的replset config.

mongodb源码分析(十八)replication replset tags_第1张图片

这里的dc可以理解为datacenter或者任何自己觉得好理解的词,dc后的ny sf cloud可以理解为不同的服务器,settings下面的veryImportant中dc:3,表示在调用getLastError调用如:

db.runCommand({getLastError:1,w:"veryImportant"})

这个调用表示getLastError命令要等带上一个操作完成veryImportant指定的将上一个操作更改到3个不同的datacenter中才会返回,这里对应ny sf cloud.那么这里是怎么实现的呢,这就是这篇文章探讨的内容.下面先介绍下本文将会遇到的一些内容.

collection: local.me: 这个collection只保留一个OID,这个OID类型和mongodb中每一条数据中的_id是一样的,是自动生成的.生成了后将其保存到local.me中,以后就不需要再次生成了,读取就行了.这个OID就是来标识这台服务器的,在secondary同步另外一台服务器数据时notifierThread将作为握手协议将这个值发送给对方,用来标识自己.

collection: local.slaves: 用来保存同步自己的服务器当前同步到的操作日志时间戳.

thread: notifierThread:  告诉同步服务器自己当前同步到的操作日志时间戳.

下面简单描述一下tags的执行流程.

1. 在设置replset配置时首先会解析settings中的getLastErrorMode,和members中的tags,将其分组.

2. secondary的producethread选取一个服务器(这里姑且叫做A)同步数据,向其发出连接请求,其连接请求是不需要握手的,当这里produceThread选取了同步对象A后,notifierThread也将去连接A,并且需要握手,就是将自己的表示OID发送给对方,对方拿到自己的OID以及members中的_id将建立一个结构记录和更新这台服务器(姑且叫做B)当天同步到的时间点.

3. 当secondary向服务器请求操作日志并replay后将会更新本地的lastwriten时间,这时notifierThread就将向服务器发送请求,同样是请求数据,但是因为其之前带了握手协议,在服务端处理该请求的连接线程中保存了OID,这里服务端就会认为是notifierThread发过来的消息,表示B服务器已经同步到了notifierThread上一次请求操作日志时最后一条操作日志的时间戳.

4. 这里就可以根据OID以及_id和这个时间戳修改这台服务器同步到的时间戳,然后根据1中该服务器的tags中的分组情况,更新该服务器所在的组中的同步时间戳.

5.  调用getLastError时首先就可以得到本机的上一条操作的时间戳(这里姑且叫做H1),找到getLastError中的w,得到其中的值,根据其值得到replset config中settings.getLastErrorModes中对应的规则,然后处理其中的规则,找出其中的每一条规则(这里叫做C1....Cn)如C1,找到C1规则需要满足的分组中的时间戳(H2),找出所有的规则中的时间戳取其最小值,表示当前规则同步到的时间戳(H3),将H1与H3比较,H1<=H3表示操作已经按照要求完成(举例中已经写到了三个datacenter的服务器上),否则表示规则没满足,则getLastError命令睡眠等待,一段时间醒来检查一下,直到满足要求才返回(getLastError命令可以设置超时).

下面进入代码分析,先来看看replset config的处理,其处理代码为repl\rs_config.cpp的parseRules.

    void ReplSetConfig::parseRules(const BSONObj& modes) {
        map<string,TagClause> tagMap;
        _populateTagMap(tagMap);//将members中的tags归类
        for (BSONObj::iterator i = modes.begin(); i.more(); ) {
            unsigned int primaryOnly = 0;
            // ruleName : {dc : 2, m : 3}
            BSONElement rule = i.next();
            TagRule* r = new TagRule();
            BSONObj clauseObj = rule.Obj();
            for (BSONObj::iterator c = clauseObj.begin(); c.more(); ) {
                BSONElement clauseElem = c.next();
                // get the clause, e.g., "x.y" : 3
                const char *criteria = clauseElem.fieldName();//得到每一条规则中的子句
                int value = clauseElem.numberInt();
                TagClause* node = new TagClause(tagMap[criteria]);
                int numGroups = node->subgroups.size();//当前存在的subgroup数要大于规则中指定的数目,要不无法满足要求
                uassert(14831, str::stream() << "mode " << clauseObj << " requires "
                        << value << " tagged with " << criteria << ", but only "
                        << numGroups << " with this tag were found", numGroups >= value);
                node->name = criteria;
                node->target = value;
                // if any subgroups contain "me", we can decrease the target
                node->actualTarget = node->target;
                // then we want to add pointers between clause & subgroup
                for (map<string,TagSubgroup*>::iterator sgs = node->subgroups.begin();
                     sgs != node->subgroups.end(); sgs++) {
                    bool foundMe = false;
                    (*sgs).second->clauses.push_back(node);//记录每一个subgroup上的规则,当subgroup时间戳更新后能够找到相应规则
                    // if this subgroup contains the primary, it's automatically always up-to-date
                    for( set<MemberCfg*>::const_iterator cfg = (*sgs).second->m.begin();
                         cfg != (*sgs).second->m.end(); 
                         cfg++) 
                    {
                        if ((*cfg)->h.isSelf()) {
                            node->actualTarget--;
                            foundMe = true;
                        }
                    }
                    scoped_lock lk(groupMx);//foundme为true,表示这个subgroup已经更新了,需要检查其他
                    for (set<MemberCfg *>::iterator cfg = (*sgs).second->m.begin();//group是否更新,所以这里在memcfg中插入
                         !foundMe && cfg != (*sgs).second->m.end(); cfg++) {//TagSubgroup时是在!foundme的情况下
                        (*cfg)->groupsw().insert((*sgs).second);
                    }
                }
                // if all of the members of this clause involve the primary, it's always up-to-date
                if (node->actualTarget == 0) {
                    node->last = OpTime(INT_MAX, INT_MAX);
                    primaryOnly++;
                }
                // this is a valid clause, so we want to add it to its rule
                node->rule = r;
                r->clauses.push_back(node);
            }
            // if all of the clauses are satisfied by the primary, this rule is trivially true
            if (primaryOnly == r->clauses.size()) {
                r->last = OpTime(INT_MAX, INT_MAX);
            }
            rules[rule.fieldName()] = r;
        }
    }
parseRules->_populateTagMap

    void ReplSetConfig::_populateTagMap(map<string,TagClause> &tagMap) {
        // create subgroups for each server corresponding to each of
        // its tags. E.g.:
        // A is tagged with {"server" : "A", "dc" : "ny"}
        // B is tagged with {"server" : "B", "dc" : "ny"}
        // At the end of this step, tagMap will contain:
        // "server" => {"A" : [A], "B" : [B]}
        // "dc" => {"ny" : [A,B]}
        for (unsigned i=0; i<members.size(); i++) {
            MemberCfg member = members[i];
            for (map<string,string>::iterator tag = member.tags.begin(); tag != member.tags.end(); tag++) {
                string label = (*tag).first;
                string value = (*tag).second;
                TagClause& clause = tagMap[label];
                clause.name = label;
                TagSubgroup* subgroup;
                // search for "ny" in "dc"'s clause
                if (clause.subgroups.find(value) == clause.subgroups.end()) {//有同一个值如dc:ny,这里ny表示其对应的服务器在
                    clause.subgroups[value] = subgroup = new TagSubgroup(value);//同一个subgroup中,subgroup中一台服务器时间戳更新
                }//表明这个subgroup时间戳更新.
                else {
                    subgroup = clause.subgroups[value];
                }
                subgroup->m.insert(&members[i]);//记录subgroup中的成员.
            }
        }
    }
下面来看看notiferThread:

    void BackgroundSync::notifierThread() {//只干一件事情,从服务端读出操作时间戳,读到lastOpTimeWritten后就进入等待状态
        Client::initThread("rsSyncNotifier");
        while (!inShutdown()) {
            bool clearTarget = false;
            MemberState state = theReplSet->state();
            if (state.primary() || state.fatal() || state.startup()) {
                sleepsecs(5);
                continue;
            }
            try {
                {
                    boost::unique_lock<boost::mutex> lock(_lastOpMutex);
                    while (_consumedOpTime == theReplSet->lastOpTimeWritten) {
                        _lastOpCond.wait(lock);
                    }
                }
                markOplog();
            }
        }
        cc().shutdown();
    }
继续看markOplog

    void BackgroundSync::markOplog() {
        if (!hasCursor()) {//连接服务器,得到游标
            sleepsecs(1);
            return;
        }
        if (!_oplogMarker.moreInCurrentBatch()) {
            _oplogMarker.more();
        }
        if (!_oplogMarker.more()) {
            _oplogMarker.tailCheck();
            sleepsecs(1);
            return;
        }
        // if this member has written the op at optime T, we want to nextSafe up to and including T
        while (_consumedOpTime < theReplSet->lastOpTimeWritten && _oplogMarker.more()) {//更新_consumedOpTime时间直到lastOpTimeWritten
            BSONObj temp = _oplogMarker.nextSafe();
            _consumedOpTime = temp["ts"]._opTime();
        }
        // call more() to signal the sync target that we've synced T
        _oplogMarker.more();
    }
markOplog->hasCursor

    bool BackgroundSync::hasCursor() {
        {
            // prevent writers from blocking readers during fsync
            SimpleMutex::scoped_lock fsynclk(filesLockedFsync); 
            // we don't need the local write lock yet, but it's needed by OplogReader::connect
            // so we take it preemptively to avoid deadlocking.
            Lock::DBWrite lk("local");
            boost::unique_lock<boost::mutex> lock(_mutex);

            if (!_oplogMarkerTarget || _currentSyncTarget != _oplogMarkerTarget) {
                if (!_currentSyncTarget) {
                    return false;
                }
                _oplogMarkerTarget = _currentSyncTarget;//服务端和sync时读取的服务端一样
                _oplogMarker.resetConnection();
                if (!_oplogMarker.connect(_oplogMarkerTarget->fullName())) {
                    _oplogMarkerTarget = NULL;
                    return false;
                }
            }
        }
        if (!_oplogMarker.haveCursor()) {//只查询其操作日志时间戳
            BSONObj fields = BSON("ts" << 1);
            _oplogMarker.tailingQueryGTE(rsoplog, theReplSet->lastOpTimeWritten, &fields);
        }
        return _oplogMarker.haveCursor();
    }
到这里需要注意_oplogMarker的连接过程.

    BackgroundSync::BackgroundSync() : _buffer(256*1024*1024, &getSize),
                                       _lastOpTimeFetched(0, 0),
                                       _lastH(0),
                                       _pause(true),
                                       _currentSyncTarget(NULL),
                                       _oplogMarkerTarget(NULL),
                                       _oplogMarker(true /* doHandshake */),这notifierThread连接sync端是需要握手的
                                       _consumedOpTime(0, 0) {
    }
	void BackgroundSync::produce() {//这里produceThread从sync端读取数据连接时是不需要握手的
        OplogReader r(false /* doHandshake */);
        getOplogReader(r);
    }
    bool OplogReader::connect(string hostName) {
        if ( ! commonConnect(hostName) )
            return false;
        if ( _doHandshake && ! replHandshake(_conn.get() ) )//这里是握手部分.
            return false;
        return true;
    }
    bool replHandshake(DBClientConnection *conn) {
        string myname = getHostName();
        BSONObj me;
        {
            Lock::DBWrite l("local");
            // local.me is an identifier for a server for getLastError w:2+
            if ( ! Helpers::getSingleton( "local.me" , me ) ||//读取数据失败则清空collection,然后自动生成一个OID,将其存入local.me中
                 ! me.hasField("host") ||
                 me["host"].String() != myname ) {
                // clean out local.me
                Helpers::emptyCollection("local.me");
                BSONObjBuilder b;
                b.appendOID( "_id" , 0 , true );
                b.append( "host", myname );
                me = b.obj();
                Helpers::putSingleton( "local.me" , me );
            }
        }
        BSONObjBuilder cmd;//握手将自己的member id,OID和host地址发送给同步端.
        cmd.appendAs( me["_id"] , "handshake" );
        if (theReplSet) {
            cmd.append("member", theReplSet->selfId());
        }
        BSONObj res;
        bool ok = conn->runCommand( "admin" , cmd.obj() , res );
        return true;
    }
这些需要注意的是notifierThread是会向sync端发送OID, member id,host的.这个信息后面会用到,而produceThread在发送信息时是不会有握手过程.继续来看notifierThread中:

                    while (_consumedOpTime == theReplSet->lastOpTimeWritten) {
                        _lastOpCond.wait(lock);
        while (_consumedOpTime < theReplSet->lastOpTimeWritten && _oplogMarker.more()) {
            BSONObj temp = _oplogMarker.nextSafe();
            _consumedOpTime = temp["ts"]._opTime();
        }
再来看看_lastOpCond这个变量:

    void BackgroundSync::notify() {//在notify中将通知上面的等待过程.
        boost::unique_lock<boost::mutex> lock(s_mutex);
        boost::unique_lock<boost::mutex> opLock(s_instance->_lastOpMutex);
        s_instance->_lastOpCond.notify_all();
    }
其调用过程在,删除了和这里讲的不相关的内容.这个函数是同步操作日志后将其记录到本地时调用的,可以认为到这里时之前的操作日志已经正确的同步到本地了.

    void _logOpObjRS(const BSONObj& op) {
        if( theReplSet ) {
            theReplSet->lastOpTimeWritten = ts;
            theReplSet->lastH = h;
            ctx.getClient()->setLastOp( ts );
            replset::BackgroundSync::notify();
        }
    }
这里通知notifier当前同步的时间戳位置,当notifierThread中没有数据时,那么其将通过getMore向sync端请求数据,这时sync端可以认为secondary端当前同步到的操作时间戳至少是notifierThread上一次请求数据时最后一个数据的时间戳.下面我们继续来看服务端的Handshake部分内容.handshake命令的执行如下:

        virtual bool run(const string& , BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
            Client& c = cc();
            c.gotHandshake( cmdObj );//注意的是一个连接表示一个线程,这里的Client是这个线程独有的.
            return 1;
        }
    void Client::gotHandshake( const BSONObj& o ) {
        BSONObjIterator i(o);
        BSONElement id = i.next();
        _remoteId = id.wrap( "_id" );//这个线程独有的OID,保存到这里
        BSONObjBuilder b;
        while ( i.more() )
            b.append( i.next() );
        b.appendElementsUnique( _handshake );//得到和之前handshake独特的数据
        _handshake = b.obj();//保存handshake数据.
        if (theReplSet && o.hasField("member")) {//建立结构
            theReplSet->ghost->associateSlave(_remoteId, o["member"].Int());
        }
    }
继续看associateSlave.

    void GhostSync::associateSlave(const BSONObj& id, const int memberId) {
        const OID rid = id["_id"].OID();//得到独特的OID
        rwlock lk( _lock , true );
        shared_ptr<GhostSlave> &g = _ghostCache[rid];
        if( g.get() == 0 ) {
            g.reset( new GhostSlave() );//新建一个GhostSlave结构.
        }
        GhostSlave &slave = *g;
        slave.slave = (Member*)rs->findById(memberId);//根据memberId将OID结构与member关联
	if (slave.slave != 0)
            slave.init = true;
    }

下面来看GetMore数据的请求,其请求数据流程为receivedGetMore->processGetMore,在processGetMore函数中有这么一条语句:

            if ( pass == 0 )//这就是我们这里关注的地点.pass目前2.2的代码一直为0
                cc->updateSlaveLocation( curop );
    void ClientCursor::updateSlaveLocation( CurOp& curop ) {
        if ( _slaveReadTill.isNull() )
            return;
        mongo::updateSlaveLocation( curop , _ns.c_str() , _slaveReadTill );
    }
下面来看看_slaveReadTill的首次更新点,这里省略了函数的参数与和这部分内容无关的代码,可以明确这是查询流程中的函数,首次slaveReadTill保存的就是这一次请求最后一条数据的时间戳

string queryWithQueryOptimizer() {
	OpTime slaveReadTill;
    // Note slave's position in the oplog.
    if ( pq.hasOption( QueryOption_OplogReplay ) ) {
        BSONObj current = cursor->current();
        BSONElement e = current["ts"];
        if ( e.type() == Date || e.type() == Timestamp ) {
            slaveReadTill = e._opTime();
            }
    }
    // Save slave's position in the oplog.
    if ( pq.hasOption( QueryOption_OplogReplay ) && !slaveReadTill.isNull() ) {
        ccPointer->slaveReadTill( slaveReadTill );
    }
}
我们接着看updateSlaveLocation函数.

    void updateSlaveLocation( CurOp& curop, const char * ns , OpTime lastOp ) {
        if ( lastOp.isNull() )
            return;
        Client * c = curop.getClient();
        BSONObj rid = c->getRemoteID();//将自己看着primary端,则这里读取local.oplog.rs可以当作
        if ( rid.isEmpty() )//是secondary在读取操作日志然后replay
            return;
        //这里的lastOp是上次getMore操作结束后最后一条记录的时间,
        //而该函数的调用发生在这一次getmore操作的开始,表示服务
        //secondary已经将数据正确的写入了自己的数据库中
        slaveTracking.update( rid , curop.getRemoteString( false ) , ns , lastOp );//这个线程保存的OID,来自slave端
        if (theReplSet && !theReplSet->isPrimary()) {
            // we don't know the slave's port, so we make the replica set keep
            // a map of rids to slaves
            theReplSet->ghost->send( boost::bind(&GhostSync::percolate, theReplSet->ghost, rid, lastOp) );
        }
    }
继续这里的slaveTracking.update

    void update( const BSONObj& rid , const string& host , const string& ns , OpTime last ) {
        Ident ident(rid,host,ns);
        scoped_lock mylk(_mutex);
        _slaves[ident] = last;
        _dirty = true;
        if (theReplSet && theReplSet->isPrimary()) {//primary端更新secondary端同步的时间戳
            theReplSet->ghost->updateSlave(ident.obj["_id"].OID(), last);
        }
        if ( ! _started ) {//启动线程,将每一个slave同步到的时间记录到local.slaves中
            // start background thread here since we definitely need it
            _started = true;
            go();
        }
        _threadsWaitingForReplication.notify_all();
    }
继续updateSlave

    void GhostSync::updateSlave(const mongo::OID& rid, const OpTime& last) {
        rwlock lk( _lock , false );
        MAP::iterator i = _ghostCache.find( rid );//握手时保留的OID
        GhostSlave& slave = *(i->second);//通过OID找到GhostSlave,找到member,然后再找到membercfg,然后membercfg与tags关联
        ((ReplSetConfig::MemberCfg)slave.slave->config()).updateGroups(last);
    }
继续这里的updateGroups函数.

void updateGroups(const OpTime& last) {//membercfg中的tagSubgroup是在parseRules中插入的.
    scoped_lock lk(ReplSetConfig::groupMx);//当前secondary更新则更新其所在的所有tagSubGroup的时间戳
    for (set<TagSubgroup*>::const_iterator it = groups().begin(); it != groups().end(); it++)
        (*it)->updateLast(last);
}
    void ReplSetConfig::TagSubgroup::updateLast(const OpTime& op) {
        if (last < op) {//这个group的时间更新到这个时间点上
            last = op;//这个group的时间戳更新则需要更新所有加在这个tagGroup上的TagClause上的时间戳.
            for (vector<TagClause*>::iterator it = clauses.begin(); it < clauses.end(); it++)
                (*it)->updateLast(op);
        }
    }
    void ReplSetConfig::TagClause::updateLast(const OpTime& op) {
        if (last >= op) {
            return;
        }
        // check at least n subgroups greater than clause.last
        int count = 0;
        map<string,TagSubgroup*>::iterator it;//满足同一个时间点的group数大于预设置的group数目,则需要更新rule的时间戳
        for (it = subgroups.begin(); it != subgroups.end(); it++) {
            if ((*it).second->last >= op) {
                count++;
            }
        }
        if (count >= actualTarget) {//满足这条clause要求的target数目了
            last = op;
            rule->updateLast(op);
        }
    }
    void ReplSetConfig::TagRule::updateLast(const OpTime& op) {
        OpTime *earliest = (OpTime*)&op;
        vector<TagClause*>::iterator it;
        for (it = clauses.begin(); it < clauses.end(); it++) {
            if ((*it)->last < *earliest) {//一条规则多条clause需满足,只能选取其中时间最早的
                earliest = &(*it)->last;//作为这条rule的时间
            }
        }
        // rules are simply and-ed clauses, so whatever the most-behind
        // clause is at is what the rule is at
        last = *earliest;
    }
到这里我们清楚了一条rule的时间戳是怎么更新的了,下面来看看getLastError函数.

        bool run(const string& dbname, BSONObj& _cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) {
            BSONElement e = cmdObj["w"];
            if ( e.ok() ) {
                int timeout = cmdObj["wtimeout"].numberInt();
                Timer t;
                long long passes = 0;
                char buf[32];
                while ( 1 ) {
                    OpTime op(c.getLastOp());//得到上一个操作的时间戳,getLastError当然只针对上一个操作了
                    if ( op.isNull() ) {
                        if ( anyReplEnabled() ) {//只有在开启replset模式时才w才算有用
                            result.append( "wnote" , "no write has been done on this connection" );
                        }
                        else if ( e.isNumber() && e.numberInt() <= 1 ) {
                            // don't do anything
                            // w=1 and no repl, so this is fine
                        }
                        else {
                            // w=2 and no repl
                            result.append( "wnote" , "no replication has been enabled, so w=2+ won't work" );
                            result.append( "err", "norepl" );
                            return true; 
                        }
                        break;
                    }
                    // check this first for w=0 or w=1
                    if ( opReplicatedEnough( op, e ) ) {//这个是我们真正关心的函数
                        break;
                    }
                    // if replication isn't enabled (e.g., config servers)
                    if ( ! anyReplEnabled() ) {
                        result.append( "err", "norepl" );
                        return true;
                    }//超时返回
                    if ( timeout > 0 && t.millis() >= timeout ) {
                        result.append( "wtimeout" , true );
                        errmsg = "timed out waiting for slaves";
                        result.append( "waited" , t.millis() );
                        result.append( "err" , "timeout" );
                        return true;
                    }
                    sleepmillis(1);
                }
                result.appendNumber( "wtime" , t.millis() );
            }
            result.appendNull( "err" );
            return true;
        }
继续这里的opReplicateEnough函数,其调用的是slaveTracking的opReplicatedEnough函数,直接进入该函数.

        bool opReplicatedEnough( OpTime op , BSONElement w ) {
            string wStr = w.String();
            if (wStr == "majority") {//默认目标,要求超过半数的服务器同步到这个时间点了
                // use the entire set, including arbiters, to prevent writing
                // to a majority of the set but not a majority of voters
                return replicatedToNum(op, theReplSet->config().getMajority());
            }//根据名字查找对应的rule,比照上一条操作记录的时间戳与这条rule的当前时间戳,小于表示rule满足要求,已经同步到这个时间点了,否则表示还没有同步到这个时间点
            map<string,ReplSetConfig::TagRule*>::const_iterator it = theReplSet->config().rules.find(wStr);
            return op <= (*it).second->last;
        }

最后来看看updateSlaveLocation部分非primary服务器的流程.非primary部分调用percolate更新slave同步时间点.

    void updateSlaveLocation( CurOp& curop, const char * ns , OpTime lastOp ) {
        if (theReplSet && !theReplSet->isPrimary()) {
            // we don't know the slave's port, so we make the replica set keep
            // a map of rids to slaves
            theReplSet->ghost->send( boost::bind(&GhostSync::percolate, theReplSet->ghost, rid, lastOp) );
        }
    }
    void GhostSync::percolate(const BSONObj& id, const OpTime& last) {
        const OID rid = id["_id"].OID();
        GhostSlave* slave;
        {
            rwlock lk( _lock , false );
            MAP::iterator i = _ghostCache.find( rid );
            if ( i == _ghostCache.end() ) {
                return;
            }
            slave = i->second.get();
            if (!slave->init) {
                return;
            }
        }
        const Member *target = replset::BackgroundSync::get()->getSyncTarget();
        if (!target || rs->box.getState().primary()
            // we are currently syncing from someone who's syncing from us
            // the target might end up with a new Member, but s.slave never
            // changes so we'll compare the names
            || target == slave->slave || target->fullName() == slave->slave->fullName()) {
            return;
        }
         {
            if (!slave->reader.haveCursor()) {//连接slave端读取其操作日志时间戳,因为是slave端,可以确定slave的最后一条记录的时间戳就是该slave的时间戳,到这里就更新了其时间戳.
                if (!slave->reader.connect(id, slave->slave->id(), target->fullName())) {
                    // error message logged in OplogReader::connect
                    return;
                }
                slave->reader.ghostQueryGTE(rsoplog, last);
            }
            if (slave->last > last) {
                return;
            }
            while (slave->last <= last) {
                if (!slave->reader.more()) {
                    // we'll be back
                    return;
                }
                BSONObj o = slave->reader.nextSafe();
                slave->last = o["ts"]._opTime();
            }
        }
    }
到这里所有关于tags的流程部分分析完毕,因为tags是个很小的功能,之前没注意,并且其实现确实相当的隐蔽,个人感觉不仔细阅读很可能分析不出这部分的流程,notifierthread也就无从得知其作用.所以这里将其流程记录下来. 到这里关于mongodb的服务端,客户端分析完毕,后面的文章将继续分析mongodb的shard的实现.


原文链接:mongodb源码分析(十八)replication replset tags

作者: yhjj0108,杨浩





























你可能感兴趣的:(mongodb源码分析(十八)replication replset tags)