mongodb源码分析(二)mongod的启动

本文详细介绍了mongod启动时的initAndListen执行流程,包括如何绑定IP(支持多IP地址)以及如何通过select监听客户端连接请求。启动过程分析完毕,后续将探讨mongo的启动细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    mongod是mongodb的存储服务器,其代码入口在mongo/db/db.cpp中,mongod的大部分代码都在mongo/db这个文件夹中。
int main(int argc, char* argv[]) {  
    int exitCode = mongoDbMain(argc, argv);  
    ::_exit(exitCode);  
}  
static int mongoDbMain(int argc, char* argv[]) {
    static StaticObserver staticObserver;
    getcurns = ourgetns;

    //下面几部分就是mongod的主要启动选项类别,具体有哪些选项可以参考mongodb相应的文档。
    po::options_description general_options("General options");
#if defined(_WIN32)
    po::options_description windows_scm_options("Windows Service Control Manager options");
#endif
    po::options_description replication_options("Replication options");
    po::options_description ms_options("Master/slave options");
    po::options_description rs_options("Replica set options");
    po::options_description sharding_options("Sharding options");
    po::options_description visible_options("Allowed options");
    po::options_description hidden_options("Hidden options");
    po::options_description ssl_options("SSL options");

跟随源码来到这里

        Module::configAll( params );//这里module的配置,mms是其中部分,但是调试时发现module中没有任何模块,没有设置实例吧。
        dataFileSync.go();//mongodb使用内存文件映射管理数据,所以过一段时间有必要把脏数据写回磁盘,这里开启一个线程干的就是这个事情
          //默认是60s刷一次,启动时可以用--syncdelay加秒数来设置多少时间刷新一次若设置为0则反而不刷新,而只是执行5s的睡眠,然后继续
        if (params.count("command")) {//睡眠5s,直到通过db.adminCommand({"setParameter":1,syncdelay:20})设置时间后再刷新
            vector<string> command = params["command"].as< vector<string> >();

            if (command[0].compare("run") == 0) {
                if (command.size() > 1) {
                    cout << "Too many parameters to 'run' command" << endl;
                    cout << visible_options << endl;
                    return 0;
                }

                initAndListen(cmdLine.port);
                return 0;
            }
最后到这里:
    StartupTest::runTests();//启动测试,有几十项,主要是做些sanity check,测试失败则mongod启动失败,具体可自己分析源码
    initAndListen(cmdLine.port);//cmdLine.port可配置,若不设置默认为27017
    dbexit(EXIT_CLEAN);
    void initAndListen(int listenPort) {
        try {
            _initAndListen(listenPort);
        }
        catch ( DBException &e ) {
            log() << "exception in initAndListen: " << e.toString() << ", terminating" << endl;
            dbexit( EXIT_UNCAUGHT );
        }
        catch ( std::exception &e ) {
            log() << "exception in initAndListen std::exception: " << e.what() << ", terminating" << endl;
            dbexit( EXIT_UNCAUGHT );
        }
        catch ( int& n ) {
            log() << "exception in initAndListen int: " << n << ", terminating" << endl;
            dbexit( EXIT_UNCAUGHT );
        }
        catch(...) {
            log() << "exception in initAndListen, terminating" << endl;
            dbexit( EXIT_UNCAUGHT );
        }
    }
    void _initAndListen(int listenPort ) {//这里删除了些无关代码,贴出了主要的代码

        Client::initThread("initandlisten");

        Database::_openAllFiles = false;

        Logstream::get().addGlobalTee( new RamLog("global") );

        bool is32bit = sizeof(int*) == 4;

        acquirePathLock(forceRepair);//文件锁,保证一个目录下启动只能有一个mongod实例,若另一个实例启动起来则因为前一个实例的这个锁失败。
        boost::filesystem::remove_all( dbpath + "/_tmp/" );

        FileAllocator::get()->start();//专门的文件分配线程,mongodb分配文件时预分配文件时就是通过这里启动的线程预分配文件

        MONGO_ASSERT_ON_EXCEPTION_WITH_MSG( clearTmpFiles(), "clear tmp files" );

        dur::startup();//mongodb的日志,后面专门文章来分析mongo的journal

        if( cmdLine.durOptions & CmdLine::DurRecoverOnly )
            return;

        // comes after getDur().startup() because this reads from the database
        clearTmpCollections();
        Module::initAll();//调试的时候发现没有启动的module,mms应该属于这里

        if ( scriptingEnabled ) {
            ScriptEngine::setup();
            globalScriptEngine->setCheckInterruptCallback( jsInterruptCallback );
            globalScriptEngine->setGetInterruptSpecCallback( jsGetInterruptSpecCallback );
        }

        repairDatabasesAndCheckVersion();

        /* we didn't want to pre-open all files for the repair check above. for regular
           operation we do for read/write lock concurrency reasons.
        */
        Database::_openAllFiles = true;

        if ( shouldRepairDatabases )
            return;

        /* this is for security on certain platforms (nonce generation) */
        srand((unsigned) (curTimeMicros() ^ startupSrandTimer.micros()));

        snapshotThread.go();//似乎是统计没一段时间内的插入查询修改删除等状况的线程,没仔细看有待研究
        d.clientCursorMonitor.go();//报告内存使用情况和过时过时删除客户端游标的线程,不会分析,感兴趣自己研究吧
        PeriodicTask::theRunner->go();//执行任务的线程,其它部分向它提交任务
        if (missingRepl) {
            log() << "** warning: not starting TTL monitor" << startupWarningsLog;
            log() << "**          if this member is not part of a replica set and you want to use "
                  << startupWarningsLog;
            log() << "**          TTL collections, remove local.system.replset and restart"
                  << startupWarningsLog;
        }
        else {
            startTTLBackgroundJob();//2.2的特性,创建索引时设置一个超时时间,超过时间后自动删除数据如:db.log.events.ensureIndex({"status:1"},{expireAfterS				    //econds:3600})。这里专门启动一个线程来做这件事。

        }
        listen(listenPort);
        exitCleanly(EXIT_NET_ERROR);
}
    void listen(int port) {
        //testTheDb();
        MessageServer::Options options;
        options.port = port;
        options.ipList = cmdLine.bind_ip;

        MessageServer * server = createServer( options , new MyMessageHandler() );
        server->setAsTimeTracker();

        startReplication();//mongodb有两种replcation模式,master/slave,和replset模式,这两种模式都在这里启动后面分析时会分析到
        if ( !noHttpInterface )//启动一个webService线程,使得我们可以在浏览器中访问mongod的一些信息,端口为server的端口加1000,默认server端口为27017,所以其			       //默认端口为28017
            boost::thread web( boost::bind(&webServerThread, new RestAdminAccess() /* takes ownership */));

#if(TESTEXHAUST)
        boost::thread thr(testExhaust);
#endif
        server->run();//服务器的启动
    }
server->run这里执行流程是这样的:PostMessageServer::run->listener::initAndListen。

initAndListen执行流程:绑定IP(这里IP可以是多IP地址,若是多IP地址则绑定到每一个IP地址上),通过select侦听来自客户端的连接请求,接收请求后来到:

    void Listener::accepted(boost::shared_ptr<Socket> psocket, long long connectionId ) {
        MessagingPort* port = new MessagingPort(psocket);
        port->setConnectionId( connectionId );
        acceptedMP( port );//这是一个虚函数,将执行PortMessageServer的acceptedMP
    }
        virtual void acceptedMP(MessagingPort * p) {

            if ( ! connTicketHolder.tryAcquire() ) {//这里超过了配置的最大连接数,windows默认的连接数为20000,其它系统则查看系统限制,最大还是20000,
//若系统限制小于20000,则连接数为系统限制的数目
                log() << "connection refused because too many open connections: " << connTicketHolder.used() << endl;

                // TODO: would be nice if we notified them...
                p->shutdown();
                delete p;
                sleepmillis(2); // otherwise we'll hard loop
                return;
            }

            try {
#ifndef __linux__  // TODO: consider making this ifdef _WIN32
                {
                    boost::thread thr( boost::bind( &pms::threadRun , p ) );
                }
#else
                pthread_attr_t attrs;
                pthread_attr_init(&attrs);
                pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);

                static const size_t STACK_SIZE = 1024*1024; // if we change this we need to update the warning

                struct rlimit limits;
                verify(getrlimit(RLIMIT_STACK, &limits) == 0);
                if (limits.rlim_cur > STACK_SIZE) {//因为连接数多了后每一个连接开一个线程栈空间占用大这里显示的配置占空间大小为1M
                    pthread_attr_setstacksize(&attrs, (DEBUG_BUILD
                                                        ? (STACK_SIZE / 2)
                                                        : STACK_SIZE));
                } else if (limits.rlim_cur < 1024*1024) {
                    warning() << "Stack size set to " << (limits.rlim_cur/1024) << "KB. We suggest 1MB" << endl;
                }


                pthread_t thread;
                int failed = pthread_create(&thread, &attrs, (void*(*)(void*)) &pms::threadRun, p);//创建服务线程

                pthread_attr_destroy(&attrs);

                if (failed) {
                    log() << "pthread_create failed: " << errnoWithDescription(failed) << endl;
                    throw boost::thread_resource_error(); // for consistency with boost::thread
                }
#endif
            }
        }
最后描述下threadRun流程,首先其接收来自客户端的连接请求,然后接收数据,然后出来,最后断掉连接类似代码如下:
threadrun(){
handler->connected()//若是来自本机的连接请求则设置_isLocalHost=true,和_isLocalHostAndLocalHostIsAuthorizedForAll=true,当然再检查admin.system.users是否
//有数据,有则_isLocalHostAndLocalHostIsAuthorizedForAll=false,要不然本机读取到用户了怎么行呢
p->recv()   //接收数据
handler->process()//处理请求
handler->disconnect()
}

好了,分析到这里mongod的启动部分也就完成了,下一篇文章我将分析mongo的启动部分。


原文链接:http://blog.youkuaiyun.com/yhjj0108/article/details/8252154
作者: yhjj0108,杨浩   










评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值