cheng029 发表于 2015-7-6 05:17:26

Mongodb源码分析--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多个,这是显示截图:
http://daizhj.iyunv.com/images/cnblogs_com/daizhj/mongod_command_showlist.png
    上面90多个类中,按其使用场景可以为分如下几类,分别是:
    dbcommand.cpp:一般数据库指令,如数据库,索引的创建,重建,打开/关闭等
    dbcommands_admin.cpp:管理指令,如CleanCmd,JournalLatencyTestCmd,ValidateCmd,FSyncCommand
    dbcommands_generic.cpp:常用指令,ListCommandsCmd,LogRotateCmd,PingCommand,CmdSet,CmdGet等
    replset_commands.cpp:复制集指令,CmdReplSetTest,CmdReplSetGetStatus,CmdReplSetReconfig等
    security_commands.cpp:安全指令,CmdGetNonce,CmdLogout,CmdAuthenticate  

    commands_admin.cpp:shard管理操作,因其位于mongos项目,这里暂不介绍
    commands_public.cpp:shard公用操作,因其位于mongos项目,这里暂不介绍  
  
  下面是相关类图:
http://daizhj.iyunv.com/images/cnblogs_com/daizhj/classdraw_dbcommand.png
-----------------------------分割线--------------------------------
http://daizhj.iyunv.com/images/cnblogs_com/daizhj/classdraw_dbcommands_admin.png


-----------------------------分割线--------------------------------http://daizhj.iyunv.com/images/cnblogs_com/daizhj/classdraw_dbcommands_generic.png  
  

-----------------------------分割线--------------------------------http://daizhj.iyunv.com/images/cnblogs_com/daizhj/classdraw_replset_commands.png   
  

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



    //commands.h
    class Command {
    public:
      //执行当前Command时所使用的锁类型
      enum LockType { READ = -1/*读*/ , NONE = 0 /*无锁*/, WRITE = 1 /*写*/};
      const string name;
      /* 运行指定的命令,需要子类实现
         fromRepl - command is being invoked as part of replication syncing.In this situation you
                      normally do not want to log the command to the local oplog.
         如执行成功返回true,否则为false, errmsg记录错误信息
      */
      virtual bool run(const string& db, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl) = 0;
      /*
         note: logTheTop() MUST be false if READ
         if NONE, can't use Client::Context setup
                  use with caution
         */
      virtual LockType locktype() const = 0;
      /* 是否有管理特权才可运行该命令 has privileges to run this command. */
      virtual bool adminOnly() const {
            return false;
      }
      //html格式的帮助信息
      void htmlHelp(stringstream&) const;
      /* 与adminOnly相似,但更严格: 要么被验证,要么只运行在本地接口(local interface)         
         注:当本属性为true时,adminOnly()也必须为true.
      */
      virtual bool localHostOnlyIfNoAuth(const BSONObj& cmdObj) { return false; }
      /* 如果replication pair 的slaves可以运行命令的,则返回true
         (the command directly from a client -- if fromRepl, always allowed).
      */
      virtual bool slaveOk() const = 0;
      /* 通过在查询命令中打开 'slaveok'选项,客户端强制在一个slave上运行一个命令时,返回true.
      */
      virtual bool slaveOverrideOk() {
            return false;
      }
      /* Override and return true to if true,log the operation (logOp()) to the replication log.
         (not done if fromRepl of course)
         Note if run() returns false, we do NOT log.
      */
      virtual bool logTheOp() { return false; }
      virtual void help( stringstream& help ) const;
      /* Return true if authentication and security applies to the commands.Some commands
         (e.g., getnonce, authenticate) can be done by anyone even unauthorized.
      */
      virtual bool requiresAuth() { return true; }
      /** @param webUI:在web上暴露当前command,形如 localhost:28017/
            @param oldName: 旧选项,表示当前command的旧(已弃用)名称
      */
      Command(const char *_name, bool webUI = false, const char *oldName = 0);
      virtual ~Command() {}
    protected:
      BSONObj getQuery( const BSONObj& cmdObj ) {
            if ( cmdObj["query"].type() == Object )
                return cmdObj["query"].embeddedObject();
            if ( cmdObj["q"].type() == Object )
                return cmdObj["q"].embeddedObject();
            return BSONObj();
      }
      static void logIfSlow( const Timer& cmdTimer,const string& msg);
      //command map,其包含系统实现的所有command对象,以便findCommand查询时使用
      //注意也包含该command的旧名称(构造方法中的oldName参数)所对应的对象,
      static map * _commands;
      //与上面形同,但不含旧名称的command map
      static map * _commandsByBestName;
      //将web类型的command放到该map中
      static map * _webCommands;
    public:
      static const map* commandsByBestName() { return _commandsByBestName; }
      static const map* webCommands() { return _webCommands; }
      /** @return 返回是否找到或已执行command */
      static bool runAgainstRegistered(const char *ns, BSONObj& jsobj, BSONObjBuilder& anObjBuilder);
      static LockType locktype( const string& name );
      //根据命令名称在集合中找到相应Command对象
      static Command * findCommand( const string& name );
    };  
    Command基类中提供了几个map类型的集合map,用于将系统实现的Command进行收集,以便后面findCommand进行便历查询时使用。如下:
   



    //commands.cpp
    Command* Command::findCommand( const string& name ) {
      //从_commands map中找到指定name的Command对象
      map::iterator i = _commands->find( name );
      if ( i == _commands->end() )//如果已到结尾,表示未找到
            return 0;
      return i->second;//返回Command对象
    }  
    看到上面代码中的_commands大家可能要问,该map是如何初始化并将系统实现的各个Command注册到其中呢?答案就在Command的构造方法中,如下:




    //command.cpp
    Command::Command(const char *_name, bool web, const char *oldName) : name(_name) {
      // register ourself.
      //如为空(系统刚启动时)则实例化_commands
      if ( _commands == 0 )
            _commands = new map;
      //如为空(系统刚启动时)则实例化_commandsByBestName
      if( _commandsByBestName == 0 )
            _commandsByBestName = new map;
      Command*& c = (*_commands);//获取指定名称的command对象
      if ( c )//如有,表示之前已注册了该command
            log() nReturned = 1;
                result.setData( qr.release(), true );//设置返回结果
            }
            else {
                uasserted(13530, "bad or malformed command request?");
            }
            return 0;
      }
    .....
    }  
    上面代码对传递来的查询消息QueryMessage(有关消息机制参见我的这篇文章)进行分析之后,如果发现其为command时,执行runCommands方法:




   //query.cpp   
   bool runCommands(const char *ns, BSONObj& jsobj, CurOp& curop, BufBuilder &b, BSONObjBuilder& anObjBuilder, bool fromRepl, int queryOptions) {
      try {
            return _runCommands(ns, jsobj, b, anObjBuilder, fromRepl, queryOptions);
      }
      catch ( AssertionException& e ) {
            e.getInfo().append( anObjBuilder , "assertion" , "assertionCode" );
      }
      curop.debug().str ensureStarted();
      string dbname = nsToDatabase( ns );
      if( logLevel >= 1 )
            log()
页: [1]
查看完整版本: Mongodb源码分析--Command体系架构