设为首页 收藏本站
查看: 793|回复: 0

[经验分享] mongodb源码分析(四)查询1之mongo的查询请求

[复制链接]

尚未签到

发表于 2015-11-10 13:56:06 | 显示全部楼层 |阅读模式
  在之前的2篇文章中分别分析了mongod和mongo的启动流程,下面开始将分析mongodb的查询,由于查询部分流程比较长,将分成mongo端的请求,mongod端的数据库的加载,mongod query的选取,mongod文档的匹配与数据的响应几部分来分析。
  首先进入mongo的查询请求部分.mongo的查询请求部分归纳起来很简单就是将请求分装成一个Message结构,然后将其发送到服务端,等待服务端的相应数据,取得数据最后显示结果.下面来看具体流程分析吧.
     当我们点击db.coll.find({x:1})时按照上一篇文章的讲解,我们首先来到了mongo/shell/dbshell.cpp
  

            if ( ! wascmd ) {
try {
if ( scope->exec( code.c_str() , "(shell)" , false , true , false ) )//执行相应的javascript代码
scope->exec( "shellPrintHelper( __lastres__ );" , "(shell2)" , true , true , false );
}
catch ( std::exception& e ) {
cout << &quot;error:&quot; << e.what() << endl;
}
}
下面进入javascript代码,其在mongo/shell/collection.js.  

//这里因为我们只设置了query,所以其它选项都是空的,this.getQueryOptions()目前只有一个SlaveOK的option,在replset模式下是不能查询secondary服务器的,需要调用rs.SlaveOK()之后才能对secondary进行查询,其执行SlaveOK后每次查询时都会添加一个QueryOption.
DBCollection.prototype.find = function (query, fields, limit, skip, batchSize, options) {
return new DBQuery( this._mongo , this._db , this ,
this._fullName , this._massageObject( query ) , fields , limit , skip , batchSize , options || this.getQueryOptions() );
}
继续前进看看DBQuery,上一篇文章提到这里的new DBQuery对象的创建发生在:  
  

    JSBool dbquery_constructor( JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval ) {
try {
smuassert( cx ,  &quot;DDQuery needs at least 4 args&quot; , argc >= 4 );
    //整个代码都是创建一个DBQuery对象,并未进行任何的查询请求动作
Convertor c(cx);
c.setProperty( obj , &quot;_mongo&quot; , argv[0] );
c.setProperty( obj , &quot;_db&quot; , argv[1] );
c.setProperty( obj , &quot;_collection&quot; , argv[2] );
c.setProperty( obj , &quot;_ns&quot; , argv[3] );
if ( argc > 4 && JSVAL_IS_OBJECT( argv[4] ) )
c.setProperty( obj , &quot;_query&quot; , argv[4] );
else {
JSObject * temp = JS_NewObject( cx , 0 , 0 , 0 );
CHECKNEWOBJECT( temp, cx, &quot;dbquery_constructor&quot; );
c.setProperty( obj , &quot;_query&quot; , OBJECT_TO_JSVAL( temp ) );
}
if ( argc > 5 && JSVAL_IS_OBJECT( argv[5] ) )
c.setProperty( obj , &quot;_fields&quot; , argv[5] );
else
c.setProperty( obj , &quot;_fields&quot; , JSVAL_NULL );

if ( argc > 6 && JSVAL_IS_NUMBER( argv[6] ) )
c.setProperty( obj , &quot;_limit&quot; , argv[6] );
else
c.setProperty( obj , &quot;_limit&quot; , JSVAL_ZERO );
if ( argc > 7 && JSVAL_IS_NUMBER( argv[7] ) )
c.setProperty( obj , &quot;_skip&quot; , argv[7] );
else
c.setProperty( obj , &quot;_skip&quot; , JSVAL_ZERO );
if ( argc > 8 && JSVAL_IS_NUMBER( argv[8] ) )
c.setProperty( obj , &quot;_batchSize&quot; , argv[8] );
else
c.setProperty( obj , &quot;_batchSize&quot; , JSVAL_ZERO );
if ( argc > 9 && JSVAL_IS_NUMBER( argv[9] ) )
c.setProperty( obj , &quot;_options&quot; , argv[9] );
else
c.setProperty( obj , &quot;_options&quot; , JSVAL_ZERO );
c.setProperty( obj , &quot;_cursor&quot; , JSVAL_NULL );
c.setProperty( obj , &quot;_numReturned&quot; , JSVAL_ZERO );
c.setProperty( obj , &quot;_special&quot; , JSVAL_FALSE );
}
catch ( const AssertionException& e ) {
if ( ! JS_IsExceptionPending( cx ) ) {
JS_ReportError( cx, e.what() );
}
return JS_FALSE;
}
catch ( const std::exception& e ) {
log() << &quot;unhandled exception: &quot; << e.what() << &quot;, throwing Fatal Assertion&quot; << endl;
fassertFailed( 16323 );
}
return JS_TRUE;
}
可以看到上面只有DBQuery对象的构建动作,并没有真正的查询请求,那么查询请求去哪里了呢?回到  
  

                try {
if ( scope->exec( code.c_str() , &quot;(shell)&quot; , false , true , false ) )//执行相应的javascript代码
scope->exec( &quot;shellPrintHelper( __lastres__ );&quot; , &quot;(shell2)&quot; , true , true , false );
}

  

继续分析shellPrintHelper函数,这里__lastres__是什么,搜索整个source insight工程发现mongo/scripting/engine_spidermonkey.cpp中:        bool exec( const StringData& code,const string& name = &quot;(anon)&quot;,bool printResult = false,bool reportError = true, bool assertOnError = true,int timeoutMs = 0 ) {
JSBool worked = JS_EvaluateScript( _context,
_global,
code.data(),
code.size(),
name.c_str(),
1,
&ret );
if ( worked )
_convertor->setProperty( _global , &quot;__lastres__&quot; , ret );
}
原来__lastres__就是上一条执行语句的结果也就是这里的DBQuery对象.继续分析shellPrintHelper函数(mongo/util/util.js)
shellPrintHelper = function (x) {
if (typeof (x) == &quot;undefined&quot;) {
// Make sure that we have a db var before we use it
// TODO: This implicit calling of GLE can cause subtle, hard to track issues - remove?
if (__callLastError && typeof( db ) != &quot;undefined&quot; && db.getMongo ) {
__callLastError = false;
// explicit w:1 so that replset getLastErrorDefaults aren't used here which would be bad.
var err = db.getLastError(1);
if (err != null) {
print(err);
}
}
return;
}
if (x == __magicNoPrint)
return;
if (x == null) {
print(&quot;null&quot;);
return;
}
if (typeof x != &quot;object&quot;)
return print(x);
var p = x.shellPrint;//我们这里是DBQuery对象,所以执行到这里,来到了DBQuery.shellPrint函数
if (typeof p == &quot;function&quot;)
return x.shellPrint();
var p = x.tojson;
if (typeof p == &quot;function&quot;)
print(x.tojson());
else
print(tojson(x));
}
DBQuery.prototype.shellPrint = function(){//(mongo/util/query.js)
try {
var start = new Date().getTime();
var n = 0;//还有查询结果并且输出数目小于shellBatchSize,循环打印结果
while ( this.hasNext() && n < DBQuery.shellBatchSize ){//这里shellBatchSize定义为20
var s = this._prettyShell ? tojson( this.next() ) : tojson( this.next() , &quot;&quot; , true );
print( s );//调用native函数native_print打印结果
n++;
}
if (typeof _verboseShell !== 'undefined' && _verboseShell) {
var time = new Date().getTime() - start;
print(&quot;Fetched &quot; + n + &quot; record(s) in &quot; + time + &quot;ms&quot;);
}
if ( this.hasNext() ){
print( &quot;Type \&quot;it\&quot; for more&quot; );
___it___  = this;
}
else {
___it___  = null;
}
}
catch ( e ){
print( e );
}
}
继续看看hasNext函数和next函数:

DBQuery.prototype.hasNext = function(){
this._exec();
if ( this._limit > 0 && this._cursorSeen >= this._limit )//超过了限制返回false,将不会再输出结果
return false;
var o = this._cursor.hasNext();
return o;
}
DBQuery.prototype.next = function(){
this._exec();
var o = this._cursor.hasNext();
if ( o )
this._cursorSeen++;
else
throw &quot;error hasNext: &quot; + o;
var ret = this._cursor.next();
if ( ret.$err && this._numReturned == 0 && ! this.hasNext() )
throw &quot;error: &quot; + tojson( ret );
this._numReturned++;
return ret;
}
继续前进到_exec函数:
DBQuery.prototype._exec = function(){//到这里终于到了this._mongo.find
if ( ! this._cursor ){
assert.eq( 0 , this._numReturned );
this._cursor = this._mongo.find( this._ns , this._query , this._fields , this._limit , this._skip , this._batchSize , this._options );
this._cursorSeen = 0;
}
return this._cursor;
}
到这里我们来到了this._mongo.find,这里_mongo是一个Mongo对象,在上一篇文章中我们了解到find函数是本地函数mongo_find.继续分析
mongo_find(mongo/scripting/sm_db.cpp).这里删除了部分错误处理代码.
    JSBool mongo_find(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
shared_ptr< DBClientWithCommands > * connHolder = (shared_ptr< DBClientWithCommands >*)JS_GetPrivate( cx , obj );
smuassert( cx ,  &quot;no connection!&quot; , connHolder && connHolder->get() );
DBClientWithCommands *conn = connHolder->get();
Convertor c( cx );
string ns = c.toString( argv[0] );
BSONObj q = c.toObject( argv[1] );
BSONObj f = c.toObject( argv[2] );
int nToReturn = (int) c.toNumber( argv[3] );
int nToSkip = (int) c.toNumber( argv[4] );
int batchSize = (int) c.toNumber( argv[5] );
int options = (int)c.toNumber( argv[6] );//上面一篇文章我们分析到这里的conn其实是由ConnectionString::connect函数返回的,其返回的对象指针可能是:DBClientConnection对应Master,也就是只设置了一个地址,DBClientReplicaSet对应pair或者set模式,SyncClusterConnection对应sync模式,继续分析流程我们选择最简单的Master模式,只有一个地址的服务端
auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0  , options , batchSize );
if ( ! cursor.get() ) {
log() << &quot;query failed : &quot; << ns << &quot; &quot; << q << &quot; to: &quot; << conn->toString() << endl;
JS_ReportError( cx , &quot;error doing query: failed&quot; );
return JS_FALSE;
}
JSObject * mycursor = JS_NewObject( cx , &internal_cursor_class , 0 , 0 );
CHECKNEWOBJECT( mycursor, cx, &quot;internal_cursor_class&quot; );
verify( JS_SetPrivate( cx , mycursor , new CursorHolder( cursor, *connHolder ) ) );
*rval = OBJECT_TO_JSVAL( mycursor );
return JS_TRUE;
}
那么我们继续前进来到DBClientConnection::query函数,该函数只是简单调用了DBClientBase::query函数.
    auto_ptr<DBClientCursor> DBClientBase::query(const string &ns, Query query, int nToReturn,
int nToSkip, const BSONObj *fieldsToReturn, int queryOptions , int batchSize ) {
auto_ptr<DBClientCursor> c( new DBClientCursor( this,//根据传入的参数创建一个DBClientCursor对象
ns, query.obj, nToReturn, nToSkip,
fieldsToReturn, queryOptions , batchSize ) );
if ( c->init() )//创建Message并向服务端发送查询请求
return c;
return auto_ptr< DBClientCursor >( 0 );
}
    bool DBClientCursor::init() {
Message toSend;
_assembleInit( toSend );//构建将要发送的查询请求这是一个Message,具体来说Message负责发送数据,具体的数据是在MsgData中
verify( _client );
if ( !_client->call( toSend, *batch.m, false, &_originalHost ) ) {//实际的发送数据,同时这里发送了数据后会调用recv接收数据
// log msg temp?                                              //接收的数据同样是MsgData,同样由Message来管理
log() << &quot;DBClientCursor::init call() failed&quot; << endl;
return false;
}
if ( batch.m->empty() ) {
// log msg temp?
log() << &quot;DBClientCursor::init message from call() was empty&quot; << endl;
return false;
}
dataReceived();//根据上面的batch.m收到的数据得出查询是否成功成功则设置cursorId,下一次请求时operation就变动为dbGetmore了.
return true;   //查询错误则抛出异常
}
_assembleInit是创建一个message结构,若是第一次请求那么请求操作为dbQuery,若不是则请求操作为dbGetmore.来看看MsgData的具体结构吧.
DSC0000.jpg


回到mongo_find函数.
            auto_ptr<DBClientCursor> cursor = conn->query( ns , q , nToReturn , nToSkip , f.nFields() ? &f : 0  , options , batchSize );
if ( ! cursor.get() ) {//这里得到了cursor
log() << &quot;query failed : &quot; << ns << &quot; &quot; << q << &quot; to: &quot; << conn->toString() << endl;
JS_ReportError( cx , &quot;error doing query: failed&quot; );
return JS_FALSE;
}
JSObject * mycursor = JS_NewObject( cx , &internal_cursor_class , 0 , 0 );//将cursor封装成一个javascript对象,javascript就能
CHECKNEWOBJECT( mycursor, cx, &quot;internal_cursor_class&quot; );//使用游标了
verify( JS_SetPrivate( cx , mycursor , new CursorHolder( cursor, *connHolder ) ) );

我们回到javascript部分的打印请求.
DBQuery.prototype.hasNext = function(){
this._exec();
if ( this._limit > 0 && this._cursorSeen >= this._limit )
return false;
var o = this._cursor.hasNext();//这里的cursor对应于上面的C++对象cursor,其hasNext函数对应的native函数为internal_cursor_hasNext,
return o;                      //这是在mongo启动时初始化javascript环境时绑定的
}
继续看到了internal_cursor_hasNext:
    JSBool internal_cursor_hasNext(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval) {
try {
DBClientCursor *cursor = getCursor( cx, obj );
*rval = cursor->more() ? JSVAL_TRUE : JSVAL_FALSE;//这里返回的就是是否还有数据,如果本地没有查询数据了,那么其会再构建一个
}                                                     //dbGetmore的请求向服务器请求更多数据,还是没有则返回false,表示没有数据了
catch ( const AssertionException& e ) {
if ( ! JS_IsExceptionPending( cx ) ) {
JS_ReportError( cx, e.what() );
}
return JS_FALSE;
}
catch ( const std::exception& e ) {
log() << &quot;unhandled exception: &quot; << e.what() << &quot;, throwing Fatal Assertion&quot; << endl;
fassertFailed( 16290 );
}
return JS_TRUE;
}
最后javascript部分打印数据:
DBQuery.prototype.shellPrint = function(){
try {
var start = new Date().getTime();
var n = 0;
while ( this.hasNext() && n < DBQuery.shellBatchSize ){//前面分析这里hasNext对应C++函数internal_cursor_hasNext,next对应
var s = this._prettyShell ? tojson( this.next() ) : tojson( this.next() , &quot;&quot; , true );//internal_cursor_next,怎么得到的数据不再分析
print( s );//调用native_print打印结果
n++;
}



下面来看看类&#20284;这种形式的命令:db.coll.find().count(), db.coll.find().showDiskLoc().
DBQuery.prototype.sort = function( sortBy ){//可以看到,这里只是在查询时增加了相应的查询请求而已
return this._addSpecial( &quot;orderby&quot; , sortBy );
}
DBQuery.prototype.hint = function( hint ){
return this._addSpecial( &quot;$hint&quot; , hint );
}
DBQuery.prototype.min = function( min ) {
return this._addSpecial( &quot;$min&quot; , min );
}
DBQuery.prototype.max = function( max ) {
return this._addSpecial( &quot;$max&quot; , max );
}
DBQuery.prototype.showDiskLoc = function() {
return this._addSpecial( &quot;$showDiskLoc&quot; , true);
}
DBQuery.prototype.count = function( applySkipLimit ){//而这里count这种是变更了查询的,这里是向服务器发送了一个count命令,执行的并不是
var cmd = { count: this._collection.getName() };//查询请求
if ( this._query ){
if ( this._special )
cmd.query = this._query.query;
else
cmd.query = this._query;
}
cmd.fields = this._fields || {};
if ( applySkipLimit ){
if ( this._limit )
cmd.limit = this._limit;
if ( this._skip )
cmd.skip = this._skip;
}
var res = this._db.runCommand( cmd );
if( res && res.n != null ) return res.n;
throw &quot;count failed: &quot; + tojson( res );
}
DBQuery.prototype.size = function(){
return this.count( true );
}


        总结,本文分析了mongodb自带的mongo客户端发起的查询请求以及结果的打印过程,
总体来说流程很简单,唯一令人感到麻烦的就是代码的执行流程在javascript和C&#43;&#43;中来回的切换,
需要注意的就是对于mongo的查询请求的&#26684;式MsgData.


原文链接:http://blog.iyunv.com/yhjj0108/article/details/8253349
作者:yhjj0108,杨浩




































  

版权声明:本文为博主原创文章,未经博主允许不得转载。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-137589-1-1.html 上篇帖子: mongodb源码分析(三)mongo的启动 下篇帖子: java 操作mongodb插入、读取、修改以及删除基础
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表