在之前的一篇文章中,介绍了mongodb的主程序入口main()的执行流程,其实main只是实始化一些参数信息并做了些后台线程任务的启动工作(包 括数据准备和恢复),并最终启动一个线程进行循环侦听。今天将会介绍在mongodb中数据查询 (find)的流程,以了解mongodb是如果对message进行拆包分析,以及数据进行表扫描及索引使用的。
好了,开始今天的正文吧!
这里继续昨天的代码浏览过程,从connThread 函 数说起,看了上一篇文章的朋友都清楚了该函数主要工作就是不断循环[while ( 1 )]获取当前客户端发来的信息(上面已封装成了message)并将其信息进行分析,并根据相应操作标志位确定当前操作是CRUD或构建索引等 [assembleResponse()],如果一些正常,则向客户端发送应答信息。而如果客户端连接提交了一个查询操作(也包括CUD及其它操作)的 话,那么它就会调用assembleResponse方法来进行相关操作的处理,该方法声明如下(instance.cpp第224行):
void assembleResponse( Message & m /* 客户端传来的(操作)信息 */ ,
DbResponse & dbresponse, /* 响应结构体,用于绑定要响应的数据及状态 */
const SockAddr & client ) {
// 获取操作符枚举信息
int op = m.operation();
注:枚举定义如下
opReply = 1 , /* reply. responseTo is set. */
dbMsg = 1000 , /* generic msg command followed by a string */
dbUpdate = 2001 , /* update object */
dbInsert = 2002 , // dbGetByOID = 2003,
dbQuery = 2004 ,
dbGetMore = 2005 ,
dbDelete = 2006 ,
dbKillCursors = 2007
};
接着它会判断是否为$cmd命令,即以.$cmd为开头,形如 db.$cmd.findOne({getlasterror: 1}),并对一些特殊指令进行单独处理,包括inprog,killop,unlock。
const char * ns = m.singleData() -> _data + 4 ;
if ( op == dbQuery ) {
if ( strstr(ns, " .$cmd " ) ) {
isCommand = true ;
opwrite(m);
if ( strstr(ns, " .$cmd.sys. " ) ) {
if ( strstr(ns, " $cmd.sys.inprog " ) ) {
inProgCmd(m, dbresponse);
return ;
}
if ( strstr(ns, " $cmd.sys.killop " ) ) {
killOp(m, dbresponse);
return ;
}
if ( strstr(ns, " $cmd.sys.unlock " ) ) {
unlockFsync(ns, m, dbresponse);
return ;
}
}
}
else {
opread(m);
}
}
else if ( op == dbGetMore ) {
opread(m);
}
接着就是获取当前线程连接的客户端对象,如下:
该方法实现代码如下:
inline Client & cc() {
Client * c = currentClient. get ();
assert( c );
return * c;
}
其主要用内联函数方式获取当前客户端操作的线程信息,而该线程默认就是上一篇文章中所创建的那个:
因为mongodb会为每一个客户端DB操作创建一个线程 Client对象,我个人把它理解为服务端持有的对应(每)客户端的操作对象。其主体函数如下:
/* each thread which does db operations has a Client object in TLS.
call this when your thread starts.
*/
Client & Client::initThread( const char * desc, MessagingPort * mp) {
assert( currentClient. get () == 0 );
Client * c = new Client(desc, mp);
currentClient.reset(c);
mongo::lastError.initThread();
return * c;
}
我们再回到assembleResponse函数,接下来的代码就是使用CurOp(一个提供了内部锁机制来保存当前客户端操作状态的对象)来把当前 Client对象及相应操作(CRUD等)封装于其中,这样当以访问该对象进行原子操作时(Atomic)就可以通过其内置支持多线程并发访问和锁保护 了。
......
CurOp & currentOp = * currentOpP;
currentOp.reset(client,op);
OpDebug & debug = currentOp.debug();
StringBuilder & ss = debug.str;
ss << opToString( op ) << " " ;
int logThreshold = cmdLine.slowMS;
bool log = logLevel >= 1 ;
接着就是执行查询语句,也就是我们今天的主角“隆重登场”了,如下:
if ( handlePossibleShardedMessage( m , & dbresponse ) ) /* 查看是不是sharding状态(查询),如果是则返回啊 */
return ;
receivedQuery(c , dbresponse, m ); /* 执行查询 */
}
else if ( op == dbGetMore ) {
......
else {
const char * ns = m.singleData() -> _data + 4 ;
char cl[ 256 ];
nsToDatabase(ns, cl);
// 进行权限认证
if ( ! c.getAuthenticationInfo() -> isAuthorized(cl) ) {
uassert_nothrow( " unauthorized " );
}
else {
try {
if ( op == dbInsert ) { // 添加记录操作
receivedInsert(m, currentOp);
}
else if ( op == dbUpdate ) { // 更新记录
receivedUpdate(m, currentOp);
}
else if ( op == dbDelete ) { // 删除记录
receivedDelete(m, currentOp);
}
else if ( op == dbKillCursors ) { // 删除Cursors(游标)对象
currentOp.ensureStarted();
logThreshold = 10 ;
ss << " killcursors " ;
receivedKillCursors(m);
}
else {
mongo::log() << " operation isn't supported: " << op << endl;
currentOp.done();
log = true ;
}
}
.....
}
}
上面代码中receivedQuery(c , dbresponse, m )就是执行查询功能的,而其它else分支特别是cud操作等我会专门再写文章加以解释,因为今天主要介绍查询功能,所以我们接着会分析该方法的执行逻辑,如下:
bool ok = true ;
MSGID responseTo = m.header() -> id; /* 从message提取id信息用于绑定到dbresponse.responseTo */
DbMessage d(m); /* 对Message进行封装,从而初始化DbMessage实例 */
QueryMessage q(d);
auto_ptr < Message > resp( new Message() );
CurOp & op = * (c.curop()); // 获取当前Client线程对象执行的操作(支持线程安全)
try {
dbresponse.exhaust = runQuery(m, q, op, * resp); /* 执行查询 */
assert( ! resp -> empty() );
}
catch ( AssertionException & e ) {
......
}
......
dbresponse.response = resp.release();
dbresponse.responseTo = responseTo;
return ok;
}
上面代码主要实始化一些查询对象,包括数据库操作消息(用于数据库/服务协议),查询结果消息(QueryMessage,即运行查询的请求所接收到的来自数据库的信息)等。最后运行runQuery(m, q, op, *resp) 开始查询(方法位于query.cpp),该方法代码较长,主要功能如下:
StringBuilder & ss = curop.debug().str;
// 构造ParsedQuery查询对象,该对象包括查询记录数字,以及记录跳转偏移量等信息,这些值会在访问磁盘查询时使用,用法参见:query.cpp 662行的virtual void _init()方法
shared_ptr < ParsedQuery > pq_shared( new ParsedQuery(q) );
ParsedQuery & pq( * pq_shared );
......
// 对查询命令判断,指令形如abc.$cmd.findOne( { ismaster:1 } )
if ( pq.couldBeCommand() ) {
BufBuilder bb;
bb.skip( sizeof (QueryResult));
BSONObjBuilder cmdResBuf;
// 对查询权限判断,并执行相应查询指令
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 , " bad or malformed command request? " );
}
return 0 ;
}
/* 普通查询分支(非指令式操作,也就是我们用c#客户端链接查询方式) */
......
BSONObj order = pq.getOrder();
BSONObj query = pq.getFilter();
/* 对查询对象大小进行判断,过滤错误的查询对象(为0) */
if ( query.objsize() == 0 ) {
out () << " Bad query object?/n jsobj: " ;
out () << jsobj.toString() << " /n query: " ;
out () << query.toString() << endl;
uassert( 10110 , " bad query object " , false );
}
/* 声明读锁 */
mongolock lk( false );
Client::Context ctx( ns , dbpath , & lk );
......
/* 对查询对象及选项进行过滤, 比如:ns,_id值,索引等 */
if ( ! (explain || pq.showDiskLoc()) && isSimpleIdQuery( query ) && ! pq.hasOption( QueryOption_CursorTailable ) ) {
bool nsFound = false ;
bool indexFound = false ;
BSONObj resObject;
Client & c = cc();
bool found = Helpers::findById( c, ns , query , resObject , & nsFound , & indexFound );
if ( nsFound == false || indexFound == true ) {
......
return false ;
}
}
.....
// 定义扫描器(类定义位于queryoptimizer.h),在看代码过程中发现MultiPlanScanner主要用于提供$or查询支持,
// 语法形如:db.foo.find( { name : "bob" , $or : [ { a : 1 } , { b : 2 } ] } )
// 更多内容参见: http://www.mongodb.org/display/DOCS/OR +operations+in+query+expressions
auto_ptr < MultiPlanScanner > mps( new MultiPlanScanner( ns, query, order, & hint, ! explain, pq.getMin(), pq.getMax(), false , true ) );
.....
ExplainBuilder eb;
UserQueryOp original( pq, result, eb, curop );
shared_ptr < UserQueryOp > o = mps -> runOp( original ); /* 执行查询 */
UserQueryOp & dqo = * o;
if ( ! dqo.complete() )
throw MsgAssertionException( dqo.exception() );
if ( explain ) {
dqo.finishExplain( explainSuffix );
}
......
/* 设置查询结果 */
QueryResult * qr = (QueryResult * ) result.header();
qr -> cursorId = cursorid;
qr -> setResultFlagsToOk();
// qr->len is updated automatically by appendData()
ss << " reslen: " << qr -> len;
qr -> setOperation(opReply);
qr -> startingFrom = 0 ;
qr -> nReturned = n;
/* 查询耗时统计 */
int duration = curop.elapsedMillis();
bool dbprofile = curop.shouldDBProfile( duration );
if ( dbprofile || duration >= cmdLine.slowMS ) {
ss << " nscanned: " << nscanned << ' ' ;
if ( ntoskip )
ss << " ntoskip: " << ntoskip;
if ( dbprofile )
ss << " /nquery: " ;
ss << jsobj.toString() << ' ' ;
}
ss << " nreturned: " << n;
return exhaust;
}
读到这里,发现系统除了把查询消息之类的信息一股脑塞给了MultiPlanScanner之后就运行了runOP方法之外,竟然还没看到mongodb 是如果查询数据库文件的,看来mongodb还真挺卖关子,没变法,只有继续往下挖代码,下面是runOP方法代码(位于 queryoptimizer.cpp文件730行):
shared_ptr < QueryOp > ret = runOpOnce( op ); /* 先运行一次查询 */
while ( ! ret -> stopRequested() && mayRunMore() ) { /* 当前查询请求未停止并且有$or查询关键字时 */
ret = runOpOnce( * ret ); // 再次运行查询
}
return ret;
}
看来runOpOnce方法是用于进行单次非or查询的,看一下代码就明白了,如下:
massert( 13271 , " can't run more ops " , mayRunMore() );
if ( ! _or ) { /* 如当前查询不是or,则运行 */
++ _i;
return _currentQps -> runOp( op );
}
++ _i;
auto_ptr < FieldRangeSet > frs( _fros.topFrs() ); /* (表)字段对象集合 */
auto_ptr < FieldRangeSet > originalFrs( _fros.topFrsOriginal() );
BSONElement hintElt = _hint.firstElement();
// 创建查询计划集合
_currentQps.reset( new QueryPlanSet( _ns, frs, originalFrs, _query, BSONObj(), & hintElt, _honorRecordedPlan, BSONObj(), BSONObj(), _bestGuessOnly, _mayYield ) );
// 设置查询计划要调用的查询方法及相关参数
shared_ptr < QueryOp > ret( _currentQps -> runOp( op ) );
if ( ret -> qp().willScanTable() ) { /* 设置表扫描标识 */
_tableScanned = true ;
}
// pop出or谓词/子句
_fros.popOrClause( ret -> qp().indexed() ? ret -> qp().indexKey() : BSONObj() );
return ret;
}
上面方面最终都是调用 _currentQps->runOp( op )来执行查询操作,下面就是方法的代码:
if ( _usingPrerecordedPlan ) { /* 该变量貌似“是否使用预先记录的计划”,也就是索引 */
Runner r( * this , op );
shared_ptr < QueryOp > res = r.run();
......
}
Runner r( * this , op );
return r.run();
}
上面代码主要是定义声明Runner实例并运行它,Runner本身为strcut类型,主要是用于对执行步骤进行封装(形成依次执行的操作流),这里不再多述了。下面是其r.run()方法的定义:
......
for ( vector < shared_ptr < QueryOp > > ::iterator i = ops.begin(); i != ops.end(); ++ i ) {
initOp( ** i ); // 初始化操作,声明如下
if ( ( * i) -> complete() )
return * i;
}
......
}
void QueryPlanSet::Runner::initOp( QueryOp & op ) {
GUARD_OP_EXCEPTION( op, op.init() );
}
上面op.init操作主要最终会执行下面方法(位于query.cpp 662行), 该方法会用查询条件构造一个游标,该游标记录着遍历数据集方式,查询起始位置等信息等
......
_c = qp().newCursor( DiskLoc() , _pq.getNumToReturn() + _pq.getSkip() ); /* 构造 */
_capped = _c -> capped();
// setup check for if we can only use index to extract
if ( _c -> modifiedKeys() == false && _c -> isMultiKey() == false && _pq.getFields() ) {
_keyFieldsOnly.reset( _pq.getFields() -> checkKey( _c -> indexKeyPattern() ) );
}
}
......
}
下面是其函数的代码(queryoptimizer.cpp 168 行):
.....
if ( ! _index ) { // 非索引 扫描
if ( _fbs.nNontrivialRanges() )
checkTableScanAllowed( _fbs.ns() );
return findTableScan( _fbs.ns(), _order, startLoc ); /* 进行表扫描 */
}
.....
}
findTableScan方法(pdfile.cpp 687行)即开始表扫描指定磁盘位置信息,并根据相关条件指定相应类型的游标信息。
BSONElement el = order.getField( " $natural " ); // e.g., { $natural : -1 }
if ( el.number() >= 0 )
return DataFileMgr::findAll(ns, startLoc); /* startLoc开始位置 */
......
}
返回的游标类型为Cursor,但findAll方法里构造的是BasicCursor,相应代码(pdfile.cpp 639行):
NamespaceDetails * d = nsdetails( ns );
if ( ! d )
return shared_ptr < Cursor > ( new BasicCursor(DiskLoc()));
.....
return shared_ptr < Cursor > ( new BasicCursor( e -> firstRecord ));
}
BasicCursor构造函数比较有意思,其引入了AdvanceStrategy对象指针,这个策略指针定义访问物理磁盘文件的方式,其操作单元是 DiskLoc(DiskLoc实例对象实际是一个双向链接),访问方法虽然只有next一种,但mongodb却用它实现了向前和后转两种访问方式(详 情参见cursor.cpp),如下:
incNscanned();
init();
}
/* these will be used outside of mutexes - really functors - thus the const */
class Forward : public AdvanceStrategy {
virtual DiskLoc next( const DiskLoc & prev ) const {
return prev.rec() -> getNext( prev );
}
} _forward;
class Reverse : public AdvanceStrategy {
virtual DiskLoc next( const DiskLoc & prev ) const {
return prev.rec() -> getPrev( prev );
}
} _reverse;
上面的 prev.rec()方法调用最终会执行下面函数调用流程:
inline Record * DiskLoc::rec() const {
return DataFileMgr::getRecord( * this );
}
inline Record * DataFileMgr::getRecord( const DiskLoc & dl) {
assert( dl.a() != - 1 );
return cc().database() -> getFile(dl.a()) -> recordAt(dl);
}
而最后“cc().database()->getFile(dl.a())->recordAt(dl)”方法会最终从数据库文件 mongodfile中获取记录信息(详见database.cpp):
inline Record * MongoDataFile::recordAt(DiskLoc dl) {
int ofs = dl.getOfs();
if ( ofs < DataFileHeader::HeaderSize ) badOfs(ofs); // will uassert - external call to keep out of the normal code path
return (Record * ) (p() + ofs);
}
兜了一大圈,头都快大了,不是吗?呵呵。另外Record,DiskLoc这两个与数据访问/存储相关类以后会抽时间介绍。
好了,今天的内容到这里就告一段落了,在接下来的文章中,将会介绍客户端发起Insert操作时,Mongodb的执行流程和B树的相应部分实现。
原文链接:http://www.cnblogs.com/daizhj/archive/2011/03/18/1988288.html
作者: daizhj, 代震军
微博: http://t.sina.com.cn/daizhj
Tags: mongodb,c++