mongodb2.2源码分析(一)

来源:互联网 发布:ue4是什么软件 编辑:程序博客网 时间:2024/04/30 05:55
 

mongodb2.2源码分析(一)概述

学习了一段时间的mongodb,有必要写些文章记录下自己的研究结果。后面我将陆续对mongodb2.2的查询,插入,删除,修改,日志等等部分实现流程进行分析,分析将覆盖mongo,mongod,mongos三部分。网上已经有一篇对于mongodb的源码分析:http://www.cnblogs.com/daizhj/category/260889.html我在学习mongodb时也参考过,作者写得很详细,但是有些部分没有讲到比如说查询部分的QueryPlanSet,QueryPlan, MultiPlanScanner,QueryOp,query optimizer的机制,另外作者分析源码时版本为1.8,我阅读的时候版本已经升级到了2.2(现在最新的版本是2.2.2),有些东西已经不一样了,最后自己学习了2个月不写点东西出来实在对不起自己呢。源码分析主要是通过source insight和visual studio2012完成的。

      mongodb是一个基于文档的关系型数据库,其最大特色我觉得是本身是非关系型数据库却实现的非常像关系型数据库。我没有阅读过关系型数据库代码,这里不负责任的说当我阅读到mongodb关于索引B树的实现时我觉得应该是和一般关系型数据库差不多的。最后关于mongodb的介绍网上一大堆文章,我也不用多说了,想查看的自己Google吧。

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

mongod是mongodb的存储服务器,其代码入口在mongo/db/db.cpp中,mongod的大部分代码都在mongo/db这个文件夹中。
[cpp] view plaincopy
  1. int main(int argc, char* argv[]) {    
  2.     int exitCode = mongoDbMain(argc, argv);    
  3.     ::_exit(exitCode);    
  4. }    
  5. static int mongoDbMain(int argc, char* argv[]) {  
  6.     static StaticObserver staticObserver;  
  7.     getcurns = ourgetns;  
  8.   
  9.     //下面几部分就是mongod的主要启动选项类别,具体有哪些选项可以参考mongodb相应的文档。  
  10.     po::options_description general_options("General options");  
  11. #if defined(_WIN32)  
  12.     po::options_description windows_scm_options("Windows Service Control Manager options");  
  13. #endif  
  14.     po::options_description replication_options("Replication options");  
  15.     po::options_description ms_options("Master/slave options");  
  16.     po::options_description rs_options("Replica set options");  
  17.     po::options_description sharding_options("Sharding options");  
  18.     po::options_description visible_options("Allowed options");  
  19.     po::options_description hidden_options("Hidden options");  
  20.     po::options_description ssl_options("SSL options");  

跟随源码来到这里

[cpp] view plaincopy
  1. Module::configAll( params );//这里module的配置,mms是其中部分,但是调试时发现module中没有任何模块,没有设置实例吧。  
  2. dataFileSync.go();//mongodb使用内存文件映射管理数据,所以过一段时间有必要把脏数据写回磁盘,这里开启一个线程干的就是这个事情  
  3.   //默认是60s刷一次,启动时可以用--syncdelay加秒数来设置多少时间刷新一次若设置为0则反而不刷新,而只是执行5s的睡眠,然后继续  
  4. if (params.count("command")) {//睡眠5s,直到通过db.adminCommand({"setParameter":1,syncdelay:20})设置时间后再刷新  
  5.     vector<string> command = params["command"].as< vector<string> >();  
  6.   
  7.     if (command[0].compare("run") == 0) {  
  8.         if (command.size() > 1) {  
  9.             cout << "Too many parameters to 'run' command" << endl;  
  10.             cout << visible_options << endl;  
  11.             return 0;  
  12.         }  
  13.   
  14.         initAndListen(cmdLine.port);  
  15.         return 0;  
  16.     }  
最后到这里:
[cpp] view plaincopy
  1. StartupTest::runTests();//启动测试,有几十项,主要是做些sanity check,测试失败则mongod启动失败,具体可自己分析源码  
  2. initAndListen(cmdLine.port);//cmdLine.port可配置,若不设置默认为27017  
  3. dbexit(EXIT_CLEAN);  
[cpp] view plaincopy
  1. void initAndListen(int listenPort) {  
  2.     try {  
  3.         _initAndListen(listenPort);  
  4.     }  
  5.     catch ( DBException &e ) {  
  6.         log() << "exception in initAndListen: " << e.toString() << ", terminating" << endl;  
  7.         dbexit( EXIT_UNCAUGHT );  
  8.     }  
  9.     catch ( std::exception &e ) {  
  10.         log() << "exception in initAndListen std::exception: " << e.what() << ", terminating" << endl;  
  11.         dbexit( EXIT_UNCAUGHT );  
  12.     }  
  13.     catch ( int& n ) {  
  14.         log() << "exception in initAndListen int: " << n << ", terminating" << endl;  
  15.         dbexit( EXIT_UNCAUGHT );  
  16.     }  
  17.     catch(...) {  
  18.         log() << "exception in initAndListen, terminating" << endl;  
  19.         dbexit( EXIT_UNCAUGHT );  
  20.     }  
  21. }  
[cpp] view plaincopy
  1.     void _initAndListen(int listenPort ) {//这里删除了些无关代码,贴出了主要的代码  
  2.   
  3.         Client::initThread("initandlisten");  
  4.   
  5.         Database::_openAllFiles = false;  
  6.   
  7.         Logstream::get().addGlobalTee( new RamLog("global") );  
  8.   
  9.         bool is32bit = sizeof(int*) == 4;  
  10.   
  11.         acquirePathLock(forceRepair);//文件锁,保证一个目录下启动只能有一个mongod实例,若另一个实例启动起来则因为前一个实例的这个锁失败。  
  12.         boost::filesystem::remove_all( dbpath + "/_tmp/" );  
  13.   
  14.         FileAllocator::get()->start();//专门的文件分配线程,mongodb分配文件时预分配文件时就是通过这里启动的线程预分配文件  
  15.   
  16.         MONGO_ASSERT_ON_EXCEPTION_WITH_MSG( clearTmpFiles(), "clear tmp files" );  
  17.   
  18.         dur::startup();//mongodb的日志,后面专门文章来分析mongo的journal  
  19.   
  20.         if( cmdLine.durOptions & CmdLine::DurRecoverOnly )  
  21.             return;  
  22.   
  23.         // comes after getDur().startup() because this reads from the database  
  24.         clearTmpCollections();  
  25.         Module::initAll();//调试的时候发现没有启动的module,mms应该属于这里  
  26.   
  27.         if ( scriptingEnabled ) {  
  28.             ScriptEngine::setup();  
  29.             globalScriptEngine->setCheckInterruptCallback( jsInterruptCallback );  
  30.             globalScriptEngine->setGetInterruptSpecCallback( jsGetInterruptSpecCallback );  
  31.         }  
  32.   
  33.         repairDatabasesAndCheckVersion();  
  34.   
  35.         /* we didn't want to pre-open all files for the repair check above. for regular 
  36.            operation we do for read/write lock concurrency reasons. 
  37.         */  
  38.         Database::_openAllFiles = true;  
  39.   
  40.         if ( shouldRepairDatabases )  
  41.             return;  
  42.   
  43.         /* this is for security on certain platforms (nonce generation) */  
  44.         srand((unsigned) (curTimeMicros() ^ startupSrandTimer.micros()));  
  45.   
  46.         snapshotThread.go();//似乎是统计没一段时间内的插入查询修改删除等状况的线程,没仔细看有待研究  
  47.         d.clientCursorMonitor.go();//报告内存使用情况和过时过时删除客户端游标的线程,不会分析,感兴趣自己研究吧  
  48.         PeriodicTask::theRunner->go();//执行任务的线程,其它部分向它提交任务  
  49.         if (missingRepl) {  
  50.             log() << "** warning: not starting TTL monitor" << startupWarningsLog;  
  51.             log() << "**          if this member is not part of a replica set and you want to use "  
  52.                   << startupWarningsLog;  
  53.             log() << "**          TTL collections, remove local.system.replset and restart"  
  54.                   << startupWarningsLog;  
  55.         }  
  56.         else {  
  57.             startTTLBackgroundJob();//2.2的特性,创建索引时设置一个超时时间,超过时间后自动删除数据如:db.log.events.ensureIndex({"status:1"},{expireAfterS<span style="white-space:pre">              </span>    //econds:3600})。这里专门启动一个线程来做这件事。  
  58.   
  59.         }  
  60.         listen(listenPort);  
  61.         exitCleanly(EXIT_NET_ERROR);  
  62. }  
[cpp] view plaincopy
  1.     void listen(int port) {  
  2.         //testTheDb();  
  3.         MessageServer::Options options;  
  4.         options.port = port;  
  5.         options.ipList = cmdLine.bind_ip;  
  6.   
  7.         MessageServer * server = createServer( options , new MyMessageHandler() );  
  8.         server->setAsTimeTracker();  
  9.   
  10.         startReplication();//mongodb有两种replcation模式,master/slave,和replset模式,这两种模式都在这里启动后面分析时会分析到  
  11.         if ( !noHttpInterface )//启动一个webService线程,使得我们可以在浏览器中访问mongod的一些信息,端口为server的端口加1000,默认server端口为27017,所以其<span style="white-space:pre">         </span>       //默认端口为28017  
  12.             boost::thread web( boost::bind(&webServerThread, new RestAdminAccess() /* takes ownership */));  
  13.   
  14. #if(TESTEXHAUST)  
  15.         boost::thread thr(testExhaust);  
  16. #endif  
  17.         server->run();//服务器的启动  
  18.     }  
server->run这里执行流程是这样的:PostMessageServer::run->listener::initAndListen。

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

[cpp] view plaincopy
  1. void Listener::accepted(boost::shared_ptr<Socket> psocket, long long connectionId ) {  
  2.     MessagingPort* port = new MessagingPort(psocket);  
  3.     port->setConnectionId( connectionId );  
  4.     acceptedMP( port );//这是一个虚函数,将执行PortMessageServer的acceptedMP  
  5. }  
[cpp] view plaincopy
  1.         virtual void acceptedMP(MessagingPort * p) {  
  2.   
  3.             if ( ! connTicketHolder.tryAcquire() ) {//这里超过了配置的最大连接数,windows默认的连接数为20000,其它系统则查看系统限制,最大还是20000,  
  4. //若系统限制小于20000,则连接数为系统限制的数目  
  5.                 log() << "connection refused because too many open connections: " << connTicketHolder.used() << endl;  
  6.   
  7.                 // TODO: would be nice if we notified them...  
  8.                 p->shutdown();  
  9.                 delete p;  
  10.                 sleepmillis(2); // otherwise we'll hard loop  
  11.                 return;  
  12.             }  
  13.   
  14.             try {  
  15. #ifndef __linux__  // TODO: consider making this ifdef _WIN32  
  16.                 {  
  17.                     boost::thread thr( boost::bind( &pms::threadRun , p ) );  
  18.                 }  
  19. #else  
  20.                 pthread_attr_t attrs;  
  21.                 pthread_attr_init(&attrs);  
  22.                 pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);  
  23.   
  24.                 static const size_t STACK_SIZE = 1024*1024; // if we change this we need to update the warning  
  25.   
  26.                 struct rlimit limits;  
  27.                 verify(getrlimit(RLIMIT_STACK, &limits) == 0);  
  28.                 if (limits.rlim_cur > STACK_SIZE) {//因为连接数多了后每一个连接开一个线程栈空间占用大这里显示的配置占空间大小为1M  
  29.                     pthread_attr_setstacksize(&attrs, (DEBUG_BUILD  
  30.                                                         ? (STACK_SIZE / 2)  
  31.                                                         : STACK_SIZE));  
  32.                 } else if (limits.rlim_cur < 1024*1024) {  
  33.                     warning() << "Stack size set to " << (limits.rlim_cur/1024) << "KB. We suggest 1MB" << endl;  
  34.                 }  
  35.   
  36.   
  37.                 pthread_t thread;  
  38.                 int failed = pthread_create(&thread, &attrs, (void*(*)(void*)) &pms::threadRun, p);//创建服务线程  
  39.   
  40.                 pthread_attr_destroy(&attrs);  
  41.   
  42.                 if (failed) {  
  43.                     log() << "pthread_create failed: " << errnoWithDescription(failed) << endl;  
  44.                     throw boost::thread_resource_error(); // for consistency with boost::thread  
  45.                 }  
  46. #endif  
  47.             }  
  48.         }  
最后描述下threadRun流程,首先其接收来自客户端的连接请求,然后接收数据,然后出来,最后断掉连接类似代码如下:
[cpp] view plaincopy
  1. threadrun(){  
  2. handler->connected()//若是来自本机的连接请求则设置_isLocalHost=true,和_isLocalHostAndLocalHostIsAuthorizedForAll=true,当然再检查admin.system.users是否  
  3. //有数据,有则_isLocalHostAndLocalHostIsAuthorizedForAll=false,要不然本机读取到用户了怎么行呢  
  4. p->recv()   //接收数据  
  5. handler->process()//处理请求  
  6. handler->disconnect()  
  7. }  

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


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



mongodb源码分析(三)mongo的启动

  mongo是mongodb的一个C++写的javascript交互式的可执行客户端。为了分析mongod对于服务请求的响应,这里分析下mongo。先来看看mongo的启动,mongo的启动代码在mongo/shell/dbshell.cpp中。其支持两个javascript 引擎,因为用visual stdio2012调试时默认编译的是spidermonkey,那么就分析spidermonkey那边吧。

          当我们使用mongodb时最简单的一个保存命令是db.coll.save({a:1}),我们知道db是javascript对象,也是数据库名,coll是数据集collection,这里将{a:1}保存到coll的集合中,但是这里有一个疑问,javascript对象对于未定义的属性的访问不是通过db[coll]访问的吗,coll这里本应该是undefined,但是为什么结果成功的保存了呢。当我在阅读源码时就有这么个疑问,还好终于从源码中找到了答案,好了废话不多说了,直接分析。

[cpp] view plaincopy
  1. po::options_description shell_options( "options" );//同样是一大堆启动命令,具体含义不说了,自己可以看相关的文档  
  2. po::options_description hidden_options( "Hidden options" );  
  3. po::options_description cmdline_options( "Command line options" );  
  4. po::positional_options_description positional_options;  
  5.   
  6. shell_options.add_options()  
  7. "shell""run the shell after executing files" )  
  8. "nodb""don't connect to mongod on startup - no 'db address' arg expected" )  
  9. "norc""will not run the \".mongorc.js\" file on start up" )  
  10. "quiet""be less chatty" )  
  11. "port", po::value<string>( &port ), "port to connect to" )  
  12. "host", po::value<string>( &dbhost ), "server to connect to" )  
  13. "eval", po::value<string>( &script ), "evaluate javascript" )  
  14. "username,u", po::value<string>(&username), "username for authentication" )  
  15. "password,p"new mongo::PasswordValue( &password ), "password for authentication" )  
  16. "help,h""show this usage information" )  
  17. "version""show version information" )  
  18. "verbose""increase verbosity" )  
  19. "ipv6""enable IPv6 support (disabled by default)" )  
[cpp] view plaincopy
  1. if ( !nodb ) { // connect to db  
  2.     //if ( ! mongo::cmdLine.quiet ) cout << "url: " << url << endl;  
  3.     //如果启动时没有指定nodb,那么这里将生成一串javascript代码,用来连接mongod  
  4.     stringstream ss;  
  5.     if ( mongo::cmdLine.quiet )  
  6.         ss << "__quiet = true;";  
  7.     ss << "db = connect( \"" << fixHost( url , dbhost , port ) << "\")";  
  8.   
  9.     mongo::shell_utils::_dbConnect = ss.str();  
  10.   
  11.     if ( params.count( "password" ) && password.empty() )  
  12.         password = mongo::askPassword();  
  13.   
  14.     if ( username.size() && password.size()) {  
  15.         stringstream ss;  
  16.         ss << "if ( ! db.auth( \"" << username << "\" , \"" << password << "\" ) ){ throw 'login failed'; }";  
  17.         mongo::shell_utils::_dbAuth = ss.str();  
  18.     }  
  19. }  
[cpp] view plaincopy
  1. mongo::ScriptEngine::setConnectCallback( mongo::shell_utils::onConnect );//这个回调函数用来做连接成功后的记录工作  
  2. mongo::ScriptEngine::setup();  
  3. mongo::globalScriptEngine->setScopeInitCallback( mongo::shell_utils::initScope );  
  4. auto_ptr< mongo::Scope > scope( mongo::globalScriptEngine->newScope() );  
  5. shellMainScope = scope.get();  

现在来看看mongo::globalScriptEngine->newScope(),在mongo::ScriptEngine::setup()函数中globalScriptEngine=new SMEngine()。

[cpp] view plaincopy
  1. virtual Scope * newScope() {  
  2.     Scope *s = createScope();  
  3.     if ( s && _scopeInitCallback )//_scopeInitCallback对应上面的mongo::shell_utils::initScope  
  4.         _scopeInitCallback( *s );  
  5.     installGlobalUtils( *s );  
  6.     return s;  
  7. }  
createScope对应spideMonkey则为new SMScope()

[cpp] view plaincopy
  1. SMScope() ://spideMonkey的初始化  
  2.     _this( 0 ),  
  3.     _reportError( true ),  
  4.     _externalSetup( false ),  
  5.     _localConnect( false ) {  
  6.     smlock;  
  7.     _context = JS_NewContext( globalSMEngine->_runtime , 8192 );  
  8.     _convertor = new Convertor( _context );  
  9.     massert( 10431 ,  "JS_NewContext failed" , _context );  
  10.   
  11.     JS_SetOptions( _context , JSOPTION_VAROBJFIX);  
  12.     //JS_SetVersion( _context , JSVERSION_LATEST); TODO  
  13.     JS_SetErrorReporter( _context , errorReporter );  
  14.   
  15.     _global = JS_NewObject( _context , &global_class, NULL, NULL);  
  16.     massert( 10432 ,  "JS_NewObject failed for global" , _global );  
  17.     JS_SetGlobalObject( _context , _global );  
  18.     massert( 10433 ,  "js init failed" , JS_InitStandardClasses( _context , _global ) );  
  19.   
  20.     JS_SetOptions( _context , JS_GetOptions( _context ) | JSOPTION_VAROBJFIX );  
  21.   
  22.     JS_DefineFunctions( _context , _global , globalHelpers );//预先定义的几个函数  
  23.   
  24.     JS_DefineFunctions( _context , _convertor->getGlobalObject( "Object" ), objectHelpers );  
  25.   
  26.     //JS_SetGCCallback( _context , no_gc ); // this is useful for seeing if something is a gc problem  
  27.   
  28.     _postCreateHacks();  
  29. }  
[cpp] view plaincopy
  1. JSFunctionSpec globalHelpers[] = {//这里定义的几个函数映射,javascript中执行相应的函数则会调用这里的本地函数。  
  2.     { "print" , &native_print , 0 , 0 , 0 } ,  
  3.     { "nativeHelper" , &native_helper , 1 , 0 , 0 } ,  
  4.     { "load" , &native_load , 1 , 0 , 0 } ,  
  5.     { "gc" , &native_gc , 1 , 0 , 0 } ,  
  6.     { "UUID", &_UUID, 0, 0, 0 } ,  
  7.     { "MD5", &_MD5, 0, 0, 0 } ,  
  8.     { "HexData", &_HexData, 0, 0, 0 } ,  
  9.     { 0 , 0 , 0 , 0 , 0 }  
  10. };  
继续到newScope函数,接下来调用了_scopeInitCallback()函数,其实就是之前设置的initScope回调函数
[cpp] view plaincopy
  1. void initScope( Scope &scope ) {  
  2.     scope.externalSetup();  
  3.     mongo::shell_utils::installShellUtils( scope );  
  4.     scope.execSetup(JSFiles::servers);//执行相应的javascript文件,相当于初始化javascript环境,以后命令行中执行相应代码时  
  5.     scope.execSetup(JSFiles::shardingtest);//就能正确的找到环境了。  
  6.     scope.execSetup(JSFiles::servers_misc);  
  7.     scope.execSetup(JSFiles::replsettest);  
  8.     scope.execSetup(JSFiles::replsetbridge);  
  9.   
  10.     if ( !_dbConnect.empty() ) {//执行登录代码  
  11.         uassert( 12513, "connect failed", scope.exec( _dbConnect , "(connect)" , false , true , false ) );  
  12.         if ( !_dbAuth.empty() ) {//用户账号登陆认证  
  13.             installGlobalUtils( scope );  
  14.             uassert( 12514, "login failed", scope.exec( _dbAuth , "(auth)" , true , true , false ) );  
  15.         }  
  16.     }  
  17. }  
initScope->externalSetup->initMongoJS
[cpp] view plaincopy
  1.     void initMongoJS( SMScope * scope , JSContext * cx , JSObject * global , bool local ) {  
  2. //JS Class的初始化过程,几乎所有的mongodb使用到的class都在这里创建,所有new XXX对象的构建都是下面这里的函数完成的,比如说db,collection,dbquery  
  3.         verify( JS_InitClass( cx , global , 0 , &mongo_class , local ? mongo_local_constructor : mongo_external_constructor , 0 , 0 , mongo_functions , 0 , 0 ) );  
  4.   
  5.         verify( JS_InitClass( cx , global , 0 , &object_id_class , object_id_constructor , 0 , 0 , 0 , 0 , 0 ) );  
  6.         verify( JS_InitClass( cx , global , 0 , &db_class , db_constructor , 2 , 0 , 0 , 0 , 0 ) );  
  7.         verify( JS_InitClass( cx , global , 0 , &db_collection_class , db_collection_constructor , 4 , 0 , 0 , 0 , 0 ) );  
  8.         verify( JS_InitClass( cx , global , 0 , &internal_cursor_class , internal_cursor_constructor , 0 , 0 , internal_cursor_functions , 0 , 0 ) );  
  9.         verify( JS_InitClass( cx , global , 0 , &dbquery_class , dbquery_constructor , 0 , 0 , 0 , 0 , 0 ) );  
  10.         verify( JS_InitClass( cx , global , 0 , &dbpointer_class , dbpointer_constructor , 0 , 0 , dbpointer_functions , 0 , 0 ) );  
  11.         verify( JS_InitClass( cx , global , 0 , &bindata_class , bindata_constructor , 0 , 0 , bindata_functions , 0 , 0 ) );  
  12. //        verify( JS_InitClass( cx , global , 0 , &uuid_class , uuid_constructor , 0 , 0 , uuid_functions , 0 , 0 ) );  
  13.   
  14.         verify( JS_InitClass( cx , global , 0 , ×tamp_class , timestamp_constructor , 0 , 0 , 0 , 0 , 0 ) );  
  15.         verify( JS_InitClass( cx , global , 0 , &numberlong_class , numberlong_constructor , 0 , 0 , numberlong_functions , 0 , 0 ) );  
  16.         verify( JS_InitClass( cx , global , 0 , &numberint_class , numberint_constructor , 0 , 0 , numberint_functions , 0 , 0 ) );  
  17.         verify( JS_InitClass( cx , global , 0 , &minkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) );  
  18.         verify( JS_InitClass( cx , global , 0 , &maxkey_class , 0 , 0 , 0 , 0 , 0 , 0 ) );  
  19.   
  20.         verify( JS_InitClass( cx , global , 0 , &map_class , map_constructor , 0 , 0 , map_functions , 0 , 0 ) );  
  21.   
  22.         verify( JS_InitClass( cx , global , 0 , &bson_ro_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) );  
  23.         verify( JS_InitClass( cx , global , 0 , &bson_class , bson_cons , 0 , 0 , bson_functions , 0 , 0 ) );  
  24.   
  25.         static const char *dbrefName = "DBRef";  
  26.         dbref_class.name = dbrefName;  
  27.         verify( JS_InitClass( cx , global , 0 , &dbref_class , dbref_constructor , 2 , 0 , bson_functions , 0 , 0 ) );  
  28.   
  29.         scope->execCoreFiles();  
  30.     }  

这里local为false,使用的是mongo_external_constructor函数。先看看mongo_class:

[cpp] view plaincopy
  1. JSClass mongo_class = {//为映射javascript对象而设置的函数,当javascript中new Mongo对象时会调用mongo_external_constructor来构造一个对象  
  2.     "Mongo",                                        // class name  
  3.     JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE,      // flags  
  4.     JS_PropertyStub,                                // addProperty  
  5.     JS_PropertyStub,                                // delProperty  
  6.     JS_PropertyStub,                                // getProperty  
  7.     JS_PropertyStub,                                // setProperty  
  8.     JS_EnumerateStub,                               // enumerate  
  9.     JS_ResolveStub,                                 // resolve  
  10.     JS_ConvertStub,                                 // convert  
  11.     mongo_finalize,                                 // finalize  
  12.     JSCLASS_NO_OPTIONAL_MEMBERS                     // optional members  
  13. };  
再看看  mongo_functions:

[cpp] view plaincopy
  1. JSFunctionSpec mongo_functions[] = {//当在javascript中调用相应的函数时,这里对应的native函数将会被调用到,  
  2.     { "auth" , mongo_auth , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,  
  3.     { "logout", mongo_logout, 0, JSPROP_READONLY | JSPROP_PERMANENT, 0 },  
  4.     { "find" , mongo_find , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,  
  5.     { "update" , mongo_update , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,  
  6.     { "insert" , mongo_insert , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,  
  7.     { "remove" , mongo_remove , 0 , JSPROP_READONLY | JSPROP_PERMANENT, 0 } ,  
  8.     { 0 }  
  9. };  
我们继续看,现在是出来上面的问题了db.coll.save({a:1})为什么没有问题。

[cpp] view plaincopy
  1. JSClass db_collection_class = {  
  2.     "DBCollection",                                 // class name  
  3.     JSCLASS_HAS_PRIVATE | JSCLASS_NEW_RESOLVE,      // flags  
  4.     JS_PropertyStub,                                // addProperty  
  5.     JS_PropertyStub,                                // delProperty  
  6.     JS_PropertyStub,                                // getProperty  
  7.     JS_PropertyStub,                                // setProperty  
  8.     JS_EnumerateStub,                               // enumerate  
  9.     (JSResolveOp)db_collection_resolve,             // 注意这里,这是关键  
  10.     JS_ConvertStub,                                 // convert  
  11.     JS_FinalizeStub,                                // finalize  
  12.     JSCLASS_NO_OPTIONAL_MEMBERS                     // optional members  
  13. };  
[cpp] view plaincopy
  1. JSBool db_collection_resolve( JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp ) {  
  2.     try {  
  3.         if ( flags & JSRESOLVE_ASSIGNING )  
  4.             return JS_TRUE;  
  5.   
  6.         Convertor c( cx );  
  7.         string collname = c.toString( id );  
  8.   
  9.         if ( isSpecialName( collname ) )  
  10.             return JS_TRUE;  
  11.   
  12.         if ( obj == c.getGlobalPrototype( "DBCollection" ) )  
  13.             return JS_TRUE;  
  14.   
  15.         JSObject * proto = JS_GetPrototype( cx , obj );  
  16.         if ( c.hasProperty( obj , collname.c_str() ) || ( proto && c.hasProperty( proto , collname.c_str() )  ) )  
  17.             return JS_TRUE;  
  18.   
  19.         string name = c.toString( c.getProperty( obj , "_shortName" ) );  
  20.         name += ".";  
  21.         name += collname;  
  22.   
  23.         jsval db = c.getProperty( obj , "_db" );  
  24.         if ( ! JSVAL_IS_OBJECT( db ) )  
  25.             return JS_TRUE;  
  26.   
  27.         JSObject * coll = doCreateCollection( cx , JSVAL_TO_OBJECT( db ) , name );//这个地方创建了个集合,当调用db.coll.save()时,javascript调用  
  28.         if ( ! coll )     //resolve函数时在这里将coll集合创建了出来,所以coll不为undefined  
  29.             return JS_FALSE;                                                     //原来如此,spidermonkey的具体流程就不清楚了,有感兴趣的自己去看吧  
  30.         c.setProperty( obj , collname.c_str() , OBJECT_TO_JSVAL( coll ) );  
  31.         *objp = obj;  
  32.     }  
  33.     catch ( const AssertionException& e ) {  
  34.         if ( ! JS_IsExceptionPending( cx ) ) {  
  35.             JS_ReportError( cx, e.what() );  
  36.         }  
  37.         return JS_FALSE;  
  38.     }  
  39.     catch ( const std::exception& e ) {  
  40.         log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;  
  41.         fassertFailed( 16303 );  
  42.     }  
  43.     return JS_TRUE;  
  44. }  
继续回到上面initMongoJS最后一行:        scope->execCoreFiles();

[cpp] view plaincopy
  1. void Scope::execCoreFiles() {//在javascript环境中执行一遍上面文件,然后javascript环境中就会留下那些文件的代码与数据,以后  
  2.     // keeping same order as in SConstruct//在命令行中执行命令时就能正确执行相应代码了。  
  3.     execSetup(JSFiles::utils);  
  4.     execSetup(JSFiles::utils_sh);  
  5.     execSetup(JSFiles::db);  
  6.     execSetup(JSFiles::mongo);  
  7.     execSetup(JSFiles::mr);  
  8.     execSetup(JSFiles::query);  
  9.     execSetup(JSFiles::collection);  
  10. }  
回到initScope:

[cpp] view plaincopy
  1. if ( !_dbConnect.empty() ) {//执行登录代码  
  2.     uassert( 12513, "connect failed", scope.exec( _dbConnect , "(connect)" , false , true , false ) );  
  3.     if ( !_dbAuth.empty() ) {//用户账号登陆认证  
  4.         installGlobalUtils( scope );  
  5.         uassert( 12514, "login failed", scope.exec( _dbAuth , "(auth)" , true , true , false ) );  
  6.     }  
  7. }  

下面来看看登录:
[cpp] view plaincopy
  1. ss << "db = connect( \"" << fixHost( url , dbhost , port ) << "\")";  
这里就是产生的javascript连接代码:
[cpp] view plaincopy
  1. string fixHost( string url , string host , string port ) {//url传进来初始化值为test,这里就可以看出为啥默认连接到了test数据库了。  
  2.     //cout << "fixHost url: " << url << " host: " << host << " port: " << port << endl;//若什么都没设置也知道为什么连接到了  
  3.                                                           //127.0.0.1:27017  
  4.     if ( host.size() == 0 && port.size() == 0 ) {  
  5.         if ( url.find( "/" ) == string::npos ) {  
  6.             // check for ips  
  7.             if ( url.find( "." ) != string::npos )  
  8.                 return url + "/test";  
  9.   
  10.             if ( url.rfind( ":" ) != string::npos &&  
  11.                     isdigit( url[url.rfind(":")+1] ) )  
  12.                 return url + "/test";  
  13.         }  
  14.         return url;  
  15.     }  
  16.   
  17.     if ( url.find( "/" ) != string::npos ) {  
  18.         cerr << "url can't have host or port if you specify them individually" << endl;  
  19.         ::_exit(-1);  
  20.     }  
  21.   
  22.     if ( host.size() == 0 )  
  23.         host = "127.0.0.1";  
  24.   
  25.     string newurl = host;  
  26.     if ( port.size() > 0 )  
  27.         newurl += ":" + port;  
  28.     else if ( host.find(':') == string::npos ) {  
  29.         // need to add port with IPv6 addresses  
  30.         newurl += ":27017";  
  31.     }  
  32.   
  33.     newurl += "/" + url;  
  34.   
  35.     return newurl;  
  36. }  
下面来看看javascript代码connect
[javascript] view plaincopy
  1. connect = function( url , user , pass ){  
  2.     chatty( "connecting to: " + url )  
  3.   
  4.     if ( user && ! pass )  
  5.         throw "you specified a user and not a password.  either you need a password, or you're using the old connect api";  
  6.   
  7.     var idx = url.lastIndexOf( "/" );  
  8.       
  9.     var db;  
  10.       
  11.     if ( idx < 0 )  
  12.         db = new Mongo().getDB( url );//这里创建了Mongo对象,上面代码分析时已经提到New Mongo()会调用本地函数:mongo_external_constructor  
  13.     else   
  14.         db = new Mongo( url.substring( 0 , idx ) ).getDB( url.substring( idx + 1 ) );  
  15.       
  16.     if ( user && pass ){  
  17.         if ( ! db.auth( user , pass ) ){  
  18.             throw "couldn't login";  
  19.         }  
  20.     }  
  21.       
  22.     return db;  
  23. }  
我们回到C++函数:mongo_external_constructor
[cpp] view plaincopy
  1. JSBool mongo_external_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {  
  2.     try {  
  3.         smuassert( cx ,  "0 or 1 args to Mongo" , argc <= 1 );  
  4.   
  5.         string host( "127.0.0.1" );  
  6.         Convertor c( cx );  
  7.         if ( argc > 0 )  
  8.             host = c.toString( argv[0] );  
  9.   
  10.         string errmsg;//注意这里的ConnectionString,其形式可为:server   server:port   foo/server:port,server:port  server,server,server  
  11.         ConnectionString cs = ConnectionString::parse( host , errmsg );//可同时配置多个IP,还记得在QQ群里有个人问,使用replset模式时如  
  12.         if ( ! cs.isValid() ) {//果主服务器挂了,那么php中怎么配置呢,我不知道php怎么配置,  
  13.             JS_ReportError( cx , errmsg.c_str() );                    //但是看到这里大家应该明白了,mongo的连接地址配置其实是可以是  
  14.             return JS_FALSE;                                          //多地址的,其connection有几种模式具体参见:ConnectionString  
  15.   
  16.         }  
  17.   
  18.         shared_ptr< DBClientWithCommands > conn( cs.connect( errmsg ) );//这里通过了ConnectionString屏蔽了具体的连接过程  
  19.         if ( ! conn ) {  
  20.             JS_ReportError( cx , errmsg.c_str() );  
  21.             return JS_FALSE;  
  22.         }  
  23.   
  24.         try {  
  25.             ScriptEngine::runConnectCallback( *conn );//连接成功调用上面说的函数mongo::shell_utils::onConnect记录连接  
  26.         }  
  27.         catch ( const AssertionException& e ){  
  28.             // Can happen if connection goes down while we're starting up here  
  29.             // Catch so that we don't get a hard-to-trace segfault from SM  
  30.             JS_ReportError( cx, ((string)( str::stream() << "Error during mongo startup." << causedBy( e ) )).c_str() );  
  31.             return JS_FALSE;  
  32.         }  
  33.         catch ( const std::exception& e ) {  
  34.             log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;  
  35.             fassertFailed( 16294 );  
  36.         }  
  37.   
  38.         verify( JS_SetPrivate( cx , obj , (void*)( new shared_ptr< DBClientWithCommands >( conn ) ) ) );  
  39.         jsval host_val = c.toval( host.c_str() );  
  40.         verify( JS_SetProperty( cx , obj , "host" , &host_val ) );  
  41.     }  
  42.     catch ( const AssertionException& e ) {  
  43.         if ( ! JS_IsExceptionPending( cx ) ) {  
  44.             JS_ReportError( cx, e.what() );  
  45.         }  
  46.         return JS_FALSE;  
  47.     }  
  48.     catch ( const std::exception& e ) {  
  49.         log() << "unhandled exception: " << e.what() << ", throwing Fatal Assertion" << endl;  
  50.         fassertFailed( 16295 );  
  51.     }  
  52.     return JS_TRUE;  
  53. }  
继续回到initScope,来看看认证过程。
[cpp] view plaincopy
  1. if ( !_dbAuth.empty() ) {//用户账号登陆认证  
  2.     installGlobalUtils( scope );  
  3.     uassert( 12514, "login failed", scope.exec( _dbAuth , "(auth)" , true , true , false ) );  
  4. }  
[cpp] view plaincopy
  1. ss << "if ( ! db.auth( \"" << username << "\" , \"" << password << "\" ) ){ throw 'login failed'; }";  
通过上面的分析可知这里直接调用了函数mongo_auth。这个函数调用DBClientWithCommands::auth函数执行具体的认证动作。
[cpp] view plaincopy
  1. bool DBClientWithCommands::auth(const string &dbname, const string &username, const string &password_text, string& errmsg, bool digestPassword, Auth::Level * level) {  
  2.     string password = password_text;  
  3.     if( digestPassword )  
  4.         password = createPasswordDigest( username , password_text );//将用户名密码按照user:mongo:passwd做md5  
  5.   
  6.     if ( level != NULL )  
  7.             *level = Auth::NONE;  
  8.   
  9.     BSONObj info;  
  10.     string nonce;  
  11.     if( !runCommand(dbname, getnoncecmdobj, info) ) {//执行命令{getnonce:1}从服务端得到一个nonce的值  
  12.         errmsg = "getnonce fails - connection problem?";  
  13.         return false;  
  14.     }  
  15.     {  
  16.         BSONElement e = info.getField("nonce");  
  17.         verify( e.type() == String );  
  18.         nonce = e.valuestr();  
  19.     }  
  20.   
  21.     BSONObj authCmd;  
  22.     BSONObjBuilder b;  
  23.     {  
  24.   
  25.         b << "authenticate" << 1 << "nonce" << nonce << "user" << username;  
  26.         md5digest d;  
  27.         {  
  28.             md5_state_t st;  
  29.             md5_init(&st);  
  30.             md5_append(&st, (const md5_byte_t *) nonce.c_str(), nonce.size() );  
  31.             md5_append(&st, (const md5_byte_t *) username.data(), username.length());  
  32.             md5_append(&st, (const md5_byte_t *) password.c_str(), password.size() );  
  33.             md5_finish(&st, d);  
  34.         }  
  35.         b << "key" << digestToString( d );  
  36.         authCmd = b.done();  
  37.     }  
  38.   
  39.     if( runCommand(dbname, authCmd, info) ) {//执行命令authenticate验证用户,数据发送到服务端后服务端验证用户通过后会将其加入到  
  40.         if ( level != NULL ) {//一个map中,其保存了认证的数据库,用户名,以及权限,权限只有读或者写  
  41.             if ( info.getField("readOnly").trueValue() )  
  42.                 *level = Auth::READ;  
  43.             else  
  44.                 *level = Auth::WRITE;  
  45.         }  
  46.         return true;  
  47.     }  
  48.   
  49.     errmsg = info.toString();  
  50.     return false;  
  51. }  
到这里initscope执行完毕,javascript初始化完毕。进入读取用户输入执行用户输入的javascript代码的流程中,部分代码如下:
[cpp] view plaincopy
  1.         bool wascmd = false;  
  2.         {  
  3.             string cmd = linePtr;  
  4.             if ( cmd.find( " " ) > 0 )  
  5.                 cmd = cmd.substr( 0 , cmd.find( " " ) );  
  6.   
  7.             if ( cmd.find( "\"" ) == string::npos ) {  
  8.                 try {//这里判断用户的输入是否是命令比如说use database,it这种命令  
  9.                     scope->exec( (string)"__iscmd__ = shellHelper[\"" + cmd + "\"];" , "(shellhelp1)" , false , true , true );  
  10.                     if ( scope->getBoolean( "__iscmd__" )  ) {//这里确实是命令,那么就去执行这些命令  
  11.                         scope->exec( (string)"shellHelper( \"" + cmd + "\" , \"" + code.substr( cmd.size() ) + "\");" , "(shellhelp2)" , false , true , false );  
  12.                         wascmd = true;  
  13.                     }  
  14.                 }  
  15.                 catch ( std::exception& e ) {  
  16.                     cout << "error2:" << e.what() << endl;  
  17.                     wascmd = true;  
  18.                 }  
  19.             }  
  20.         }  
  21.   
  22.         if ( ! wascmd ) {  
  23.             try {//不是确切的命令,按照一般javascript的代码规则执行  
  24.                 if ( scope->exec( code.c_str() , "(shell)" , false , true , false ) )  
  25.                     scope->exec( "shellPrintHelper( __lastres__ );" , "(shell2)" , true , true , false );//打印结果,比如说用户查询  
  26.             }                                                                                            //结果就是这里打印出来的  
  27.             catch ( std::exception& e ) {  
  28.                 cout << "error:" << e.what() << endl;  
  29.             }  
  30.         }  
  31.   
  32.         shellHistoryAdd( code.c_str() );//记录这个代码,下次可以使用,用户可以按上下键调出就像linux shell一样。  
  33.         free( line );  
  34.     }  
  35.   
  36.     shellHistoryDone();  
  37. }  

至此mongo的启动流程就分析完了,具体来说可以分为:
1. javascript环境的初始化(包括了一些辅助函数的注入如native_print和mongodb对象的注入如collection,db,dbquery)。
2. 客户端的登录流程(需要注意的是配置服务端地址时可设置多服务端地址,具体可参考ConnectionString这个类的实现)。

原文链接:http://blog.csdn.net/yhjj0108/article/details/8252958
作者:yhjj0108,杨浩

原创粉丝点击