MongoDB源码分析--Command体系架构

运维 数据库运维 其他数据库 MongoDB
本文是用一篇专门介绍MongoDB中Command的体系架构的文章,并用例子来介绍mongod是如何将Command引入其中的。

Command在Mongodb中是一类特殊操作,它提供了强大的管理及各项操作(比如建库,索引,删除集合等)。可以说通过Command可以完成几乎所有想做的事情。同时Mongodb开发者在Command上又做了非常清晰体系架构和设计,便于管理和高效执行各种类型的Command。

今天就专门用一篇篇幅来着重介绍一下其Command的体系架构,并用例子来介绍mongod是如何将Command引入其中的。

为了对其中大部分command对一个大致的了解,我们可以用下面指令来显示一个command列表:

  1. mongod --dbpath d:\mongodb\db --port 27017 --rest
  2. 在浏览器上输入链接地址:http://localhost:28017/_commands

这里mongod就会为我们显示command列表,大约有90多个,这是显示截图:

 

上面90多个类中,按其使用场景可以为分如下几类,分别是:

  1. dbcommand.cpp:一般数据库指令,如数据库,索引的创建,重建,打开/关闭等
  2. dbcommands_admin.cpp:管理指令,如CleanCmd,JournalLatencyTestCmd,ValidateCmd,FSyncCommand
  3. dbcommands_generic.cpp:常用指令,ListCommandsCmd,LogRotateCmd,PingCommand,CmdSet,CmdGet等
  4. replset_commands.cpp:复制集指令,CmdReplSetTest,CmdReplSetGetStatus,CmdReplSetReconfig等
  5. security_commands.cpp:安全指令,CmdGetNonce,CmdLogout,CmdAuthenticate

     

     

  1. commands_admin.cpp:shard管理操作,因其位于mongos项目,这里暂不介绍
  2. commands_public.cpp:shard公用操作,因其位于mongos项目,这里暂不介绍

下面是相关类图:

-----------------------------分割线--------------------------------

-----------------------------分割线--------------------------------

 -----------------------------分割线--------------------------------

-----------------------------分割线--------------------------------

#p#

首先我们看一下在Command的基类,其用于定义子类要实现的方法及属性,自身也实现了一些通用方法,比如htmlHelp(用于以html方法显示该command的帮助信息),构造方法,findCommand(查询命令)等,其声明如下:

  1. //commands.h  
  2. class Command {  
  3. public:  
  4.    //执行当前Command时所使用的锁类型  
  5.    enum LockType { READ = -1/*读*/ , NONE = 0 /*无锁*/, WRITE = 1 /*写*/};  
  6.  
  7.    const string name;  
  8.  
  9.    /* 运行指定的命令,需要子类实现  
  10.    fromRepl - command is being invoked as part of replication syncing.  In this situation you  
  11.    normally do not want to log the command to the local oplog.  
  12.  
  13.    如执行成功返回true,否则为false, errmsg记录错误信息  
  14.    */ 
  15.    virtual bool run(const string& db, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) = 0;  
  16.  
  17.    /*  
  18.    note: logTheTop() MUST be false if READ  
  19.    if NONE, can't use Client::Context setup  
  20.    use with caution  
  21.    */ 
  22.    virtual LockType locktype() const = 0;  
  23.  
  24.    /* 是否有管理特权才可运行该命令 has privileges to run this command. */ 
  25.    virtual bool adminOnly() const {  
  26.       return false;  
  27.    }  
  28.    //html格式的帮助信息  
  29.    void htmlHelp(stringstream&) const;  
  30.  
  31.    /* 与adminOnly相似,但更严格: 要么被验证,要么只运行在本地接口(local interface)  
  32.    注:当本属性为true时,adminOnly()也必须为true.  
  33.    */ 
  34.    virtual bool localHostOnlyIfNoAuth(const BSONObj& cmdObj) { return false; }  
  35.  
  36.    /* 如果replication pair 的slaves可以运行命令的,则返回true  
  37.    (the command directly from a client -- if fromRepl, always allowed).  
  38.    */ 
  39.    virtual bool slaveOk() const = 0;  
  40.  
  41.    /* 通过在查询命令中打开 'slaveok'选项,客户端强制在一个slave上运行一个命令时,返回true.  
  42.    */ 
  43.    virtual bool slaveOverrideOk() {  
  44.       return false;  
  45.    }  
  46.  
  47.    /* Override and return true to if true,log the operation (logOp()) to the replication log.  
  48.    (not done if fromRepl of course)  
  49.  
  50.    Note if run() returns false, we do NOT log.  
  51.    */ 
  52.    virtual bool logTheOp() { return false; }  
  53.  
  54.    virtual void help( stringstream& help ) const;  
  55.  
  56.    /* Return true if authentication and security applies to the commands.  Some commands  
  57.    (e.g., getnonce, authenticate) can be done by anyone even unauthorized.  
  58.    */ 
  59.    virtual bool requiresAuth() { return true; }  
  60.  
  61.    /** @param webUI:在web上暴露当前command,形如 localhost:28017/<name>  
  62.    @param oldName: 旧选项,表示当前command的旧(已弃用)名称  
  63.    */ 
  64.    Command(const char *_name, bool webUI = falseconst char *oldName = 0);  
  65.  
  66.    virtual ~Command() {}  
  67.  
  68. protected:  
  69.    BSONObj getQuery( const BSONObj& cmdObj ) {  
  70.      if ( cmdObj["query"].type() == Object )  
  71.         return cmdObj["query"].embeddedObject();  
  72.      if ( cmdObj["q"].type() == Object )  
  73.         return cmdObj["q"].embeddedObject();  
  74.      return BSONObj();  
  75.    }  
  76.  
  77.    static void logIfSlow( const Timer& cmdTimer,  const string& msg);  
  78.    //command map,其包含系统实现的所有command对象,以便findCommand查询时使用  
  79.    //注意也包含该command的旧名称(构造方法中的oldName参数)所对应的对象,  
  80.    static map<string,Command*> * _commands;  
  81.    //与上面形同,但不含旧名称的command map  
  82.    static map<string,Command*> * _commandsByBestName;  
  83.    //将web类型的command放到该map中  
  84.    static map<string,Command*> * _webCommands;  
  85.  
  86. public:  
  87.    static const map<string,Command*>* commandsByBestName() { return _commandsByBestName; }  
  88.    static const map<string,Command*>* webCommands() { return _webCommands; }  
  89.    /** @return 返回是否找到或已执行command */ 
  90.    static bool runAgainstRegistered(const char *ns, BSONObj& jsobj, BSONObjBuilder& anObjBuilder);  
  91.    static LockType locktype( const string& name );  
  92.    //根据命令名称在集合中找到相应Command对象  
  93.    static Command * findCommand( const string& name );  
  94. }; 

Command基类中提供了几个map<string,Command*>类型的集合map,用于将系统实现的Command进行收集,以便后面findCommand进行便历查询时使用。如下:

  1. //commands.cpp  
  2. Command* Command::findCommand( const string& name ) {  
  3.    //从_commands map中找到指定name的Command对象  
  4.    map<string,Command*>::iterator i = _commands->find( name );  
  5.    if ( i == _commands->end() )//如果已到结尾,表示未找到  
  6.       return 0;  
  7.    return i->second;//返回Command对象  
  8. }  

看到上面代码中的_commands大家可能要问,该map是如何初始化并将系统实现的各个Command注册到其中呢?答案就在Command的构造方法中,如下:

  1. //command.cpp  
  2. Command::Command(const char *_name, bool web, const char *oldName) : name(_name) {  
  3.    // register ourself.  
  4.    //如为空(系统刚启动时)则实例化_commands  
  5.    if ( _commands == 0 )  
  6.       _commands = new map<string,Command*>;  
  7.    //如为空(系统刚启动时)则实例化_commandsByBestName  
  8.    if( _commandsByBestName == 0 )  
  9.       _commandsByBestName = new map<string,Command*>;  
  10.    Command*& c = (*_commands)[name];//获取指定名称的command对象  
  11.    if ( c )//如有,表示之前已注册了该command  
  12.       log() << "warning: 2 commands with name: " << _name << endl;  
  13.    //将当前command(this)赋值到map中相应name的command上  
  14.    c = this;  
  15.    //绑定到_commandsByBestName中的相应name上  
  16.    (*_commandsByBestName)[name] = this;  
  17.    //如果命令支持web方式  
  18.    if( web ) {  
  19.       //如为空(系统刚启动时)则实例化_webCommands  
  20.       if( _webCommands == 0 )  
  21.          _webCommands = new map<string,Command*>;  
  22.       //绑定到_webCommands中的相应name上  
  23.       (*_webCommands)[name] = this;  
  24.    }  
  25.    //如有旧名称,则也绑到_commands的oldName所指向的command  
  26.    if( oldName )  
  27.       (*_commands)[oldName] = this;  
  28. }  

有了这些还不够,我们还要从90多个command子类中找出一个来实际分析其实现的方式,这里以最经常使用的count(获取指定条件的记录数)来分析其向map中注册command的流程,参见下面代码段:

  1. //dbcommands.cpp  
  2. /* select count(*) */ 
  3. class CmdCount : public Command {  
  4. public:  
  5.    virtual LockType locktype() const { return READ; }  
  6.    //调用基类的构造方法  
  7.    CmdCount() : Command("count") { }  
  8.  
  9.    virtual bool logTheOp() {  
  10.       return false;  
  11.    }  
  12.    virtual bool slaveOk() const {  
  13.       // ok on --slave setups, not ok for nonmaster of a repl pair (unless override)  
  14.       return replSettings.slave == SimpleSlave;  
  15.    }  
  16.    virtual bool slaveOverrideOk() {  
  17.       return true;  
  18.    }  
  19.    virtual bool adminOnly() const {  
  20.       return false;  
  21.    }  
  22.    virtual void help( stringstream& help ) const { help << "count objects in collection"; }  
  23.    virtual bool run(const string& dbname, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool) {  
  24.       string ns = dbname + '.' + cmdObj.firstElement().valuestr();  
  25.       string err;  
  26.       long long n = runCount(ns.c_str(), cmdObj, err);//执行查询  
  27.       long long nn = n;  
  28.       bool ok = true;  
  29.       if ( n == -1 ) {  
  30.          nn = 0;  
  31.          result.appendBool( "missing" , true );  
  32.       }  
  33.       else if ( n < 0 ) {  
  34.          nn = 0;  
  35.          ok = false;  
  36.          if ( !err.empty() )  
  37.             errmsg = err;  
  38.       }  
  39.       result.append("n", (double) nn);  
  40.       return ok;  
  41.    }  
  42. }  cmdCount; 

上面的CmdCount类即是在命令行模式下使用count指令时对应的代码块,其自身的构造函数就直接调用了基类(Command)的构造方法。但这里只是定义了还不够,还需要一个定义类实例代码(用于启动构造函数),而这个任务就交给了该类定义的代码结尾处的下面代码来实现了:

  1. } cmdCount; 

可以看到,这里使用的是在类声明后定义对象的方式来执行构造方法(这时并未使用new实例化方式来创建对象指针),进而注册该command到map。当然继承自Command的子类必须要实现其中的run()方法,因为只有它是具体command要执行的具体逻辑(可参见上面CmdCount的具体实现)。

到这里只能说mongod在系统启动到实始化了相应的Command集合map信息,但mongod是如何将client发来的操作请求进行转换并进而执行相应的command指令的呢?我们接下来继续分析。

之前看过我的这篇文章的朋友可能还有印象,在mongod启动之后,会循环侦听指向端口上的用户(client)请求,这些请求在mongod中被改装成了message在各个功能类中传递。当用户发送一个count指令操作时,其会在query.cpp中执行下面方法(以count查询指令的执行流程为例来进行分析):

  1. //query.cpp  
  2. const char *runQuery(Message& m, QueryMessage& q, CurOp& curop, Message &result) {  
  3.    StringBuilder& ss = curop.debug().str;  
  4.    //构造ParsedQuery查询对象,该对象包括查询记录数字,以及记录跳转偏移量等信息,  
  5.  
  6.    //这些值会在访问磁盘查询时使用,用法参见:query.cpp 662行的virtual void _init()方法  
  7.    shared_ptr<ParsedQuery> pq_shared( new ParsedQuery(q) );  
  8.    ParsedQuery& pq( *pq_shared );  
  9.    ......  
  10.    //对查询命令判断,指令形如abc.$cmd.findOne( { ismaster:1 } )  
  11.    if ( pq.couldBeCommand() ) {//_ns中包括$cmd字符串  
  12.   BufBuilder bb;  
  13.   bb.skip(sizeof(QueryResult));  
  14.   BSONObjBuilder cmdResBuf;  
  15.   //对查询权限判断,并执行相应查询指令  
  16.   if ( runCommands(ns, jsobj, curop, bb, cmdResBuf, false, queryOptions) ) {  
  17.      ss << " command: ";  
  18.      jsobj.toString( ss );  
  19.      curop.markCommand();  
  20.      auto_ptr< QueryResult > qr;  
  21.      qr.reset( (QueryResult *) bb.buf() );  
  22.      bb.decouple();  
  23.      qr->setResultFlagsToOk();  
  24.      qr->len = bb.len();  
  25.      ss << " reslen:" << bb.len();  
  26.      qr->setOperation(opReply);  
  27.      qr->cursorId = 0;  
  28.      qr->startingFrom = 0;  
  29.      qr->nReturned = 1;  
  30.      result.setData( qr.release(), true );//设置返回结果  
  31.   }  
  32.   else {  
  33.      uasserted(13530, "bad or malformed command request?");  
  34.   }  
  35.   return 0;  
  36.    }  
  37. .....  

上面代码对传递来的查询消息QueryMessage进行分析之后,如果发现其为command时,执行runCommands方法:

  1. //query.cpp     
  2. bool runCommands(const char *ns, BSONObj& jsobj, CurOp& curop, BufBuilder &b, BSONObjBuilder& anObjBuilder, bool fromRepl, int queryOptions) {  
  3.    try {  
  4.       return _runCommands(ns, jsobj, b, anObjBuilder, fromRepl, queryOptions);  
  5.    }  
  6.    catch ( AssertionException& e ) {  
  7.       e.getInfo().append( anObjBuilder , "assertion" , "assertionCode" );  
  8.    }  
  9.    curop.debug().str << " assertion ";  
  10.    anObjBuilder.append("errmsg""db assertion failure");  
  11.    anObjBuilder.append("ok", 0.0);  
  12.    BSONObj x = anObjBuilder.done();  
  13.    b.appendBuf((void*) x.objdata(), x.objsize());  
  14.    return true;  
  15. }  

接着其会执行dbcommands.cpp中的_runCommands()方法

  1. //dbcommands.cpp  
  2.   bool _runCommands(const char *ns, BSONObj& _cmdobj, BufBuilder &b, BSONObjBuilder& anObjBuilder, bool fromRepl, int queryOptions) {  
  3.   cc().curop()->ensureStarted();  
  4.   string dbname = nsToDatabase( ns );  
  5.  
  6.   if( logLevel >= 1 )  
  7.      log() << "run command " << ns << ' ' << _cmdobj << endl;  
  8.  
  9.   const char *p = strchr(ns, '.');  
  10.   if ( !p ) return false;  
  11.   //再次进行cmd判断,以确定是command  
  12.   if ( strcmp(p, ".$cmd") != 0 ) return false;  
  13.  
  14.   BSONObj jsobj;  
  15.   {  
  16.      BSONElement e = _cmdobj.firstElement();  
  17.      if ( e.type() == Object && string("query") == e.fieldName() ) {  
  18.         jsobj = e.embeddedObject();  
  19.      }  
  20.      else {  
  21.         jsobj = _cmdobj;  
  22.      }  
  23.   }  
  24.  
  25.   Client& client = cc();  
  26.   bool ok = false;  
  27.  
  28.   BSONElement e = jsobj.firstElement();  
  29.   //根据command名称从map中找出相应的command对象  
  30.   Command * c = e.type() ? Command::findCommand( e.fieldName() ) : 0;  
  31.  
  32.   if ( c ) {  
  33.      //执行该对象  
  34.      ok = execCommand( c , client , queryOptions , ns , jsobj , anObjBuilder , fromRepl );  
  35.   }  
  36.   else {  
  37.      anObjBuilder.append("errmsg", str::stream() << "no such cmd: " << e.fieldName() );  
  38.      anObjBuilder.append("bad cmd" , _cmdobj );  
  39.   }  
  40.  
  41.   // switch to bool, but wait a bit longer before switching?  
  42.   // anObjBuilder.append("ok", ok);  
  43.   anObjBuilder.append("ok", ok?1.0:0.0);  
  44.   BSONObj x = anObjBuilder.done();  
  45.   b.appendBuf((void*) x.objdata(), x.objsize());  
  46.  
  47.   return true;  

上面代码主要是从map中找出相应的command对象,并将该对象及操作命令参数和client(用于获取其中的认证信息,以确定其执行权限)作为参数,来调用 execCommand方法:

  1. //dbcommands.cpp  
  2. bool execCommand( Command * c ,  
  3.   Client& client , int queryOptions ,  
  4.   const char *cmdns, BSONObj& cmdObj ,  
  5.   BSONObjBuilder& result /*返回command执行结果*/,  
  6.   bool fromRepl ) {  
  7.  
  8.      string dbname = nsToDatabase( cmdns );  
  9.  
  10.      AuthenticationInfo *ai = client.getAuthenticationInfo();  
  11.      
  12.      if( c->adminOnly() /*如果需要有管理特权开可运行*/ 
  13.      && c->localHostOnlyIfNoAuth( cmdObj ) /*要么被验证,要么只运行在本地接口*/ 
  14.      && noauth && !ai->isLocalHost ) {//未认证 且 不是在本地运行  
  15.         result.append( "errmsg" ,  
  16.         "unauthorized: this command must run from localhost when running db without auth" );  
  17.         log() << "command denied: " << cmdObj.toString() << endl;  
  18.         return false;  
  19.      }  
  20.  
  21.     if ( c->adminOnly() && ! fromRepl && dbname != "admin" ) {  
  22.        result.append( "errmsg" ,  "access denied; use admin db" );  
  23.        log() << "command denied: " << cmdObj.toString() << endl;  
  24.        return false;  
  25.     }  
  26.  
  27.    if ( cmdObj["help"].trueValue() ) {  
  28.       stringstream ss;  
  29.       ss << "help for: " << c->name << " ";  
  30.       c->help( ss );  
  31.       result.append( "help" , ss.str() );  
  32.       result.append( "lockType" , c->locktype() );  
  33.       return true;  
  34.    }  
  35.  
  36.    bool canRunHere =  
  37.    isMaster( dbname.c_str() ) /*如为master库*/||  
  38.    c->slaveOk() /*如果replication pair 的slaves可以运行命令*/||  
  39.    ( c->slaveOverrideOk() && ( queryOptions & QueryOption_SlaveOk ) ) ||  
  40.    fromRepl;  
  41.  
  42.    if ( ! canRunHere ) {  
  43.       result.append( "errmsg" , "not master" );  
  44.       return false;  
  45.    }  
  46.  
  47.    if ( c->adminOnly() )  
  48.       log( 2 ) << "command: " << cmdObj << endl;  
  49.  
  50.    //如当前command无须锁时  
  51.    if ( c->locktype() == Command::NONE ) {  
  52.       // we also trust that this won't crash  
  53.       string errmsg;  
  54.       //运行当前command  
  55.       int ok = c->run( dbname , cmdObj , errmsg , result , fromRepl );  
  56.       if ( ! ok )  
  57.          result.append( "errmsg" , errmsg );  
  58.       return ok;  
  59.    }  
  60.    //判断执行当前command是否需要'写锁'(每个command子类都有该属性),枚举定义如下(command.h):  
  61.    //enum LockType { READ = -1/*读*/ , NONE = 0 /*无锁*/, WRITE = 1 /*写*/};  
  62.    bool needWriteLock = c->locktype() == Command::WRITE;  
  63.  
  64.    if ( ! needWriteLock ) {  
  65.       assert( ! c->logTheOp() );  
  66.    }  
  67.  
  68.    mongolock lk( needWriteLock );//声明锁对象  
  69.    Client::Context ctx( dbname , dbpath , &lk , c->requiresAuth() );  
  70.  
  71.    try {  
  72.       string errmsg;  
  73.       //运行当前command(本文中提到的count命令)  
  74.       if ( ! c->run(dbname, cmdObj, errmsg, result, fromRepl ) ) {  
  75.          result.append( "errmsg" , errmsg );  
  76.          return false;  
  77.       }  
  78.    }  
  79.    catch ( DBException& e ) {  
  80.       stringstream ss;  
  81.       ss << "exception: " << e.what();  
  82.       result.append( "errmsg" , ss.str() );  
  83.       result.append( "code" , e.getCode() );  
  84.       return false;  
  85.    }  
  86.  
  87.    if ( c->logTheOp() && ! fromRepl ) {  
  88.       logOp("c", cmdns, cmdObj);  
  89.    }  
  90.  
  91.    return true;  

到这里,流程基本就执行完毕了,之后它会将结果传给result(其传参为引用类型,即:"& result"方式).

#p#

***用一张时间序来大体回顾一下这***程:

好了,今天的内容到这里就告一段落了。

参考链接:
http://www.mongodb.org/display/DOCS/Commands
http://www.10gen.com/reference

原文链接:http://www.cnblogs.com/daizhj/archive/2011/04/29/mongos_command_source_code.html

【编辑推荐】

  1. Mongodb源码分析--内存文件映射(MMAP)
  2. 走进MongoDB的世界 展开MongoDB的学习之旅
  3. 浅析Mongodb源码之游标Cursor
  4. 野心勃勃的NoSQL新贵 MongoDB应用实战
  5. MongoDB与CouchDB全方位对比

 

责任编辑:艾婧 来源: 博客园
相关推荐

2011-05-26 10:05:48

MongoDB

2011-05-26 16:18:51

Mongodb

2019-07-01 12:55:05

安全体系架构网络安全企业安全

2011-04-25 17:15:39

MongodbMMAP

2016-11-25 13:14:50

Flume架构源码

2016-11-29 09:38:06

Flume架构核心组件

2016-11-25 13:26:50

Flume架构源码

2009-12-25 16:24:14

防火墙三大体系架构前景分析

2022-03-18 15:55:15

鸿蒙操作系统架构

2019-10-16 16:33:41

Docker架构语言

2017-07-26 09:41:28

MyCATSQLMongoDB

2011-09-16 14:43:52

MongoDB

2023-06-02 08:16:14

MySQL体系架构

2016-09-04 14:00:31

Spark

2021-02-19 06:56:33

架构协程应用

2016-11-29 16:59:46

Flume架构源码

2017-06-27 14:05:19

2021-01-06 10:09:38

MySQL

2009-12-23 10:13:20

WPF体系架构

2016-11-03 13:19:38

vue.jsjavascript前端
点赞
收藏

51CTO技术栈公众号