Command在Mongodb中是一类特殊操作,它提供了强大的管理及各项操作(比如建库,索引,删除集合等)。可以说通过Command可以完成几乎所有想做的事情。同时Mongodb开发者在Command上又做了非常清晰体系架构和设计,便于管理和高效执行各种类型的Command。
今天就专门用一篇篇幅来着重介绍一下其Command的体系架构,并用例子来介绍mongod是如何将Command引入其中的。
为了对其中大部分command对一个大致的了解,我们可以用下面指令来显示一个command列表:
- mongod –dbpath d:\mongodb\db –port 27017 –rest
- 在浏览器上输入链接地址:http://localhost:28017/_commands
这里mongod就会为我们显示command列表,大约有90多个,这是显示截图:
上面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项目,这里暂不介绍
下面是相关类图:
—————————–分割线——————————–
—————————–分割线——————————–
—————————–分割线——————————–
—————————–分割线——————————–
#p#
首先我们看一下在Command的基类,其用于定义子类要实现的方法及属性,自身也实现了一些通用方法,比如htmlHelp(用于以html方法显示该command的帮助信息),构造方法,findCommand(查询命令)等,其声明如下:
//commands.h classCommand{ public: //执行当前Command时所使用的锁类型 enumLockType{READ=-1/*读*/,NONE=0/*无锁*/,WRITE=1/*写*/}; conststringname; /*运行指定的命令,需要子类实现 fromRepl-commandisbeinginvokedaspartofreplicationsyncing.Inthissituationyou normallydonotwanttologthecommandtothelocaloplog. 如执行成功返回true,否则为false,errmsg记录错误信息 */ virtualboolrun(conststring&db,BSONObj&cmdObj,string&errmsg,BSONObjBuilder&result,boolfromRepl)=0; /* note:logTheTop()MUSTbefalseifREAD ifNONE,can'tuseClient::Contextsetup usewithcaution */ virtualLockTypelocktype()const=0; /*是否有管理特权才可运行该命令hasprivilegestorunthiscommand.*/ virtualbooladminOnly()const{ returnfalse; } //html格式的帮助信息 voidhtmlHelp(stringstream&)const; /*与adminOnly相似,但更严格:要么被验证,要么只运行在本地接口(localinterface) 注:当本属性为true时,adminOnly()也必须为true. */ virtualboollocalHostOnlyIfNoAuth(constBSONObj&cmdObj){returnfalse;} /*如果replicationpair的slaves可以运行命令的,则返回true (thecommanddirectlyfromaclient--iffromRepl,alwaysallowed). */ virtualboolslaveOk()const=0; /*通过在查询命令中打开'slaveok'选项,客户端强制在一个slave上运行一个命令时,返回true. */ virtualboolslaveOverrideOk(){ returnfalse; } /*Overrideandreturntruetoiftrue,logtheoperation(logOp())tothereplicationlog. (notdoneiffromReplofcourse) Noteifrun()returnsfalse,wedoNOTlog. */ virtualboollogTheOp(){returnfalse;} virtualvoidhelp(stringstream&help)const; /*Returntrueifauthenticationandsecurityappliestothecommands.Somecommands (e.g.,getnonce,authenticate)canbedonebyanyoneevenunauthorized. */ virtualboolrequiresAuth(){returntrue;} /**@paramwebUI:在web上暴露当前command,形如localhost:28017/<name> @paramoldName:旧选项,表示当前command的旧(已弃用)名称 */ Command(constchar*_name,boolwebUI=false,constchar*oldName=0); virtual~Command(){} protected: BSONObjgetQuery(constBSONObj&cmdObj){ if(cmdObj["query"].type()==Object) returncmdObj["query"].embeddedObject(); if(cmdObj["q"].type()==Object) returncmdObj["q"].embeddedObject(); returnBSONObj(); } staticvoidlogIfSlow(constTimer&cmdTimer,conststring&msg); //commandmap,其包含系统实现的所有command对象,以便findCommand查询时使用 //注意也包含该command的旧名称(构造方法中的oldName参数)所对应的对象, staticmap<string,Command*>*_commands; //与上面形同,但不含旧名称的commandmap staticmap<string,Command*>*_commandsByBestName; //将web类型的command放到该map中 staticmap<string,Command*>*_webCommands; public: staticconstmap<string,Command*>*commandsByBestName(){return_commandsByBestName;} staticconstmap<string,Command*>*webCommands(){return_webCommands;} /**@return返回是否找到或已执行command*/ staticboolrunAgainstRegistered(constchar*ns,BSONObj&jsobj,BSONObjBuilder&anObjBuilder); staticLockTypelocktype(conststring&name); //根据命令名称在集合中找到相应Command对象 staticCommand*findCommand(conststring&name); };
Command基类中提供了几个map<string,Command*>类型的集合map,用于将系统实现的Command进行收集,以便后面findCommand进行便历查询时使用。如下:
//commands.cpp Command*Command::findCommand(conststring&name){ //从_commandsmap中找到指定name的Command对象 map<string,Command*>::iteratori=_commands->find(name); if(i==_commands->end())//如果已到结尾,表示未找到 return0; returni->second;//返回Command对象 }
看到上面代码中的_commands大家可能要问,该map是如何初始化并将系统实现的各个Command注册到其中呢?答案就在Command的构造方法中,如下:
//command.cpp Command::Command(constchar*_name,boolweb,constchar*oldName):name(_name){ //registerourself. //如为空(系统刚启动时)则实例化_commands if(_commands==0) _commands=newmap<string,Command*>; //如为空(系统刚启动时)则实例化_commandsByBestName if(_commandsByBestName==0) _commandsByBestName=newmap<string,Command*>; Command*&c=(*_commands)[name];//获取指定名称的command对象 if(c)//如有,表示之前已注册了该command log()<<"warning:2commandswithname:"<<_name<<endl; //将当前command(this)赋值到map中相应name的command上 c=this; //绑定到_commandsByBestName中的相应name上 (*_commandsByBestName)[name]=this; //如果命令支持web方式 if(web){ //如为空(系统刚启动时)则实例化_webCommands if(_webCommands==0) _webCommands=newmap<string,Command*>; //绑定到_webCommands中的相应name上 (*_webCommands)[name]=this; } //如有旧名称,则也绑到_commands的oldName所指向的command if(oldName) (*_commands)[oldName]=this; }
有了这些还不够,我们还要从90多个command子类中找出一个来实际分析其实现的方式,这里以最经常使用的count(获取指定条件的记录数)来分析其向map中注册command的流程,参见下面代码段:
//dbcommands.cpp /*selectcount(*)*/ classCmdCount:publicCommand{ public: virtualLockTypelocktype()const{returnREAD;} //调用基类的构造方法 CmdCount():Command("count"){} virtualboollogTheOp(){ returnfalse; } virtualboolslaveOk()const{ //okon--slavesetups,notokfornonmasterofareplpair(unlessoverride) returnreplSettings.slave==SimpleSlave; } virtualboolslaveOverrideOk(){ returntrue; } virtualbooladminOnly()const{ returnfalse; } virtualvoidhelp(stringstream&help)const{help<<"countobjectsincollection";} virtualboolrun(conststring&dbname,BSONObj&cmdObj,string&errmsg,BSONObjBuilder&result,bool){ stringns=dbname+'.'+cmdObj.firstElement().valuestr(); stringerr; longlongn=runCount(ns.c_str(),cmdObj,err);//执行查询 longlongnn=n; boolok=true; if(n==-1){ nn=0; result.appendBool("missing",true); } elseif(n<0){ nn=0; ok=false; if(!err.empty()) errmsg=err; } result.append("n",(double)nn); returnok; } }cmdCount;
上面的CmdCount类即是在命令行模式下使用count指令时对应的代码块,其自身的构造函数就直接调用了基类(Command)的构造方法。但这里只是定义了还不够,还需要一个定义类实例代码(用于启动构造函数),而这个任务就交给了该类定义的代码结尾处的下面代码来实现了:
}cmdCount;
可以看到,这里使用的是在类声明后定义对象的方式来执行构造方法(这时并未使用new实例化方式来创建对象指针),进而注册该command到map。当然继承自Command的子类必须要实现其中的run()方法,因为只有它是具体command要执行的具体逻辑(可参见上面CmdCount的具体实现)。
到这里只能说mongod在系统启动到实始化了相应的Command集合map信息,但mongod是如何将client发来的操作请求进行转换并进而执行相应的command指令的呢?我们接下来继续分析。
之前看过我的这篇文章的朋友可能还有印象,在mongod启动之后,会循环侦听指向端口上的用户(client)请求,这些请求在mongod中被改装成了message在各个功能类中传递。当用户发送一个count指令操作时,其会在query.cpp中执行下面方法(以count查询指令的执行流程为例来进行分析):
//query.cpp constchar*runQuery(Message&m,QueryMessage&q,CurOp&curop,Message&result){ StringBuilder&ss=curop.debug().str; //构造ParsedQuery查询对象,该对象包括查询记录数字,以及记录跳转偏移量等信息, //这些值会在访问磁盘查询时使用,用法参见:query.cpp662行的virtualvoid_init()方法 shared_ptr<ParsedQuery>pq_shared(newParsedQuery(q)); ParsedQuery&pq(*pq_shared); ...... //对查询命令判断,指令形如abc.$cmd.findOne({ismaster:1}) if(pq.couldBeCommand()){//_ns中包括$cmd字符串 BufBuilderbb; bb.skip(sizeof(QueryResult)); BSONObjBuildercmdResBuf; //对查询权限判断,并执行相应查询指令 if(runCommands(ns,jsobj,curop,bb,cmdResBuf,false,queryOptions)){ ss<<"command:"; jsobj.toString(ss); curop.markCommand(); auto_ptr<QueryResult>qr; qr.reset((QueryResult*)bb.buf()); bb.decouple(); qr->setResultFlagsToOk(); qr->len=bb.len(); ss<<"reslen:"<<bb.len(); qr->setOperation(opReply); qr->cursorId=0; qr->startingFrom=0; qr->nReturned=1; result.setData(qr.release(),true);//设置返回结果 } else{ uasserted(13530,"badormalformedcommandrequest?"); } return0; } ..... }
上面代码对传递来的查询消息QueryMessage进行分析之后,如果发现其为command时,执行runCommands方法:
//query.cpp boolrunCommands(constchar*ns,BSONObj&jsobj,CurOp&curop,BufBuilder&b,BSONObjBuilder&anObjBuilder,boolfromRepl,intqueryOptions){ try{ return_runCommands(ns,jsobj,b,anObjBuilder,fromRepl,queryOptions); } catch(AssertionException&e){ e.getInfo().append(anObjBuilder,"assertion","assertionCode"); } curop.debug().str<<"assertion"; anObjBuilder.append("errmsg","dbassertionfailure"); anObjBuilder.append("ok",0.0); BSONObjx=anObjBuilder.done(); b.appendBuf((void*)x.objdata(),x.objsize()); returntrue; }
接着其会执行dbcommands.cpp中的_runCommands()方法
//dbcommands.cpp bool_runCommands(constchar*ns,BSONObj&_cmdobj,BufBuilder&b,BSONObjBuilder&anObjBuilder,boolfromRepl,intqueryOptions){ cc().curop()->ensureStarted(); stringdbname=nsToDatabase(ns); if(logLevel>=1) log()<<"runcommand"<<ns<<''<<_cmdobj<<endl; constchar*p=strchr(ns,'.'); if(!p)returnfalse; //再次进行cmd判断,以确定是command if(strcmp(p,".$cmd")!=0)returnfalse; BSONObjjsobj; { BSONElemente=_cmdobj.firstElement(); if(e.type()==Object&&string("query")==e.fieldName()){ jsobj=e.embeddedObject(); } else{ jsobj=_cmdobj; } } Client&client=cc(); boolok=false; BSONElemente=jsobj.firstElement(); //根据command名称从map中找出相应的command对象 Command*c=e.type()?Command::findCommand(e.fieldName()):0; if(c){ //执行该对象 ok=execCommand(c,client,queryOptions,ns,jsobj,anObjBuilder,fromRepl); } else{ anObjBuilder.append("errmsg",str::stream()<<"nosuchcmd:"<<e.fieldName()); anObjBuilder.append("badcmd",_cmdobj); } //switchtobool,butwaitabitlongerbeforeswitching? //anObjBuilder.append("ok",ok); anObjBuilder.append("ok",ok?1.0:0.0); BSONObjx=anObjBuilder.done(); b.appendBuf((void*)x.objdata(),x.objsize()); returntrue;
上面代码主要是从map中找出相应的command对象,并将该对象及操作命令参数和client(用于获取其中的认证信息,以确定其执行权限)作为参数,来调用 execCommand方法:
//dbcommands.cpp boolexecCommand(Command*c, Client&client,intqueryOptions, constchar*cmdns,BSONObj&cmdObj, BSONObjBuilder&result/*返回command执行结果*/, boolfromRepl){ stringdbname=nsToDatabase(cmdns); AuthenticationInfo*ai=client.getAuthenticationInfo(); if(c->adminOnly()/*如果需要有管理特权开可运行*/ &&c->localHostOnlyIfNoAuth(cmdObj)/*要么被验证,要么只运行在本地接口*/ &&noauth&&!ai->isLocalHost){//未认证且不是在本地运行 result.append("errmsg", "unauthorized:thiscommandmustrunfromlocalhostwhenrunningdbwithoutauth"); log()<<"commanddenied:"<<cmdObj.toString()<<endl; returnfalse; } if(c->adminOnly()&&!fromRepl&&dbname!="admin"){ result.append("errmsg","accessdenied;useadmindb"); log()<<"commanddenied:"<<cmdObj.toString()<<endl; returnfalse; } if(cmdObj["help"].trueValue()){ stringstreamss; ss<<"helpfor:"<<c->name<<""; c->help(ss); result.append("help",ss.str()); result.append("lockType",c->locktype()); returntrue; } boolcanRunHere= isMaster(dbname.c_str())/*如为master库*/|| c->slaveOk()/*如果replicationpair的slaves可以运行命令*/|| (c->slaveOverrideOk()&&(queryOptions&QueryOption_SlaveOk))|| fromRepl; if(!canRunHere){ result.append("errmsg","notmaster"); returnfalse; } if(c->adminOnly()) log(2)<<"command:"<<cmdObj<<endl; //如当前command无须锁时 if(c->locktype()==Command::NONE){ //wealsotrustthatthiswon'tcrash stringerrmsg; //运行当前command intok=c->run(dbname,cmdObj,errmsg,result,fromRepl); if(!ok) result.append("errmsg",errmsg); returnok; } //判断执行当前command是否需要'写锁'(每个command子类都有该属性),枚举定义如下(command.h): //enumLockType{READ=-1/*读*/,NONE=0/*无锁*/,WRITE=1/*写*/}; boolneedWriteLock=c->locktype()==Command::WRITE; if(!needWriteLock){ assert(!c->logTheOp()); } mongolocklk(needWriteLock);//声明锁对象 Client::Contextctx(dbname,dbpath,&lk,c->requiresAuth()); try{ stringerrmsg; //运行当前command(本文中提到的count命令) if(!c->run(dbname,cmdObj,errmsg,result,fromRepl)){ result.append("errmsg",errmsg); returnfalse; } } catch(DBException&e){ stringstreamss; ss<<"exception:"<<e.what(); result.append("errmsg",ss.str()); result.append("code",e.getCode()); returnfalse; } if(c->logTheOp()&&!fromRepl){ logOp("c",cmdns,cmdObj); } returntrue; }
到这里,流程基本就执行完毕了,之后它会将结果传给result(其传参为引用类型,即:"& result"方式).
#p#
***用一张时间序来大体回顾一下这***程:
好了,今天的内容到这里就告一段落了。
转载请注明:IT运维空间 » 运维技术 » MongoDB源码分析–Command体系架构
发表评论