浅析Mongodb源码之游标Cursor

运维 数据库运维 其他数据库 MongoDB
在Mongodb中,其提供了类似关系型数据中cursor对象来遍历数据集合,同时mongodb并要根据不同的场景生成不同的游标对象(cursor),比如顺序遍历游标(basicCursor),反向游标(reverseCursor), B树索引游标(btreeCursor)等。

在Mongodb中,其提供了类似关系型数据中cursor对象来遍历数据集合,同时mongodb并要根据不同的场景生成不同的游标对象(cursor),比如顺序遍历游标(basicCursor)反向游标(reverseCursor) B树索引游标(btreeCursor)等。

下面是其游标体系架构类图(位于cursor.cpp, cursor.h, clientcursor.cpp, clientcursor.h):

从该图中,可以看到除了(ClientCursor)之外,其余游标均继承自Cursor这个类(基类),下面我们看一下其具体实现:

  1. class Cursor : boost::noncopyable//使类和派生类不可复制  
  2. {  
  3.    virtual bool ok() = 0;//游标当前指向的对象是否有效  
  4.    bool eof() { return !ok(); }//是否已到尾部  
  5.    virtual Record* _current() = 0;//游标当前指向的记录(记录是组成数据文件的最基本单位)  
  6.    virtual BSONObj current() = 0;//游标当前指向的BSONObj对象  
  7.    virtual DiskLoc currLoc() = 0;//游标当前指向的DiskLoc  
  8.    virtual bool advance() = 0; /*true=ok,将游标指向到下一条记录所在位置*/ 
  9.    virtual BSONObj currKey() const { return BSONObj(); }  
  10.      
  11.   /* 标识游标是否为Tailable类型,该类型支持获取最后一条记录后,不马上关闭游标,以便持续获取后面新添加的记录*/ 
  12.    virtual bool tailable()   
  13.    {  
  14.       return false;  
  15.    }  
  16.    //设置游标为Tailable类型  
  17.    virtual void setTailable() {}  
  18.    .....  
  19. }   

在mongodb中,提供了两种遍历数据集合的方向,分别是“向前”和“倒转”方式,其声明如下:

  1. class AdvanceStrategy   
  2. {  
  3.    public:  
  4.       virtual ~AdvanceStrategy() { }  
  5.       virtual DiskLoc next( const DiskLoc &prev ) const = 0;  
  6. };  
  7.  
  8. const AdvanceStrategy *forward(); //向前  
  9. const AdvanceStrategy *reverse(); //倒转 

下面是其实现方式如下:

  1. class Forward : public AdvanceStrategy {  
  2.    virtual DiskLoc next( const DiskLoc &prev ) const {  
  3.    return prev.rec()->getNext( prev );  
  4.    }  
  5. } _forward;  
  6.  
  7. class Reverse : public AdvanceStrategy {  
  8.    virtual DiskLoc next( const DiskLoc &prev ) const {  
  9.    return prev.rec()->getPrev( prev );  
  10.    }  
  11. } _reverse;  
  12.  
  13. const AdvanceStrategy *forward() {  
  14.    return &_forward;  
  15. }  
  16. const AdvanceStrategy *reverse() {  
  17.    return &_reverse;  

 

看到这里,我们有必须简要说明一下mongofile文件的结构,见下面说明:

  1. /*  a datafile - i.e. the "dbname.<#>" files :  
  2.  
  3. ----------------------  
  4. DataFileHeader  :数据头文件信息,包括版本,文件长度,使用情况等  
  5. ----------------------  
  6. Extent (for a particular namespace) 特定namespace下的extent,可理解为数据集合  
  7.   Record : 单条数据记录  
  8.   ...  
  9.   Record (some chained for unused space)  
  10. ----------------------  
  11. more Extents... 其它extent  
  12. ----------------------  
  13. */ 

在一个数据库文件中,同一个namespace的extent可以有多个,每一个extent都有一些记录(record)组成,如果访问record,可以使用diskloc加上文件偏移(getOfs:位于diskloc中)获取。
同时每个extent中包括还包括两个重要属性:

  1. DiskLoc xnext, xprev; /* next/prev extent for this namespace */ 

它们分别记录了同一namespace下,在extent链表中,当前extent的前或后一个extent的位置信息,上面AdvanceStrategy中的next方法即实现了在两种遍历方向(上面已提到)上,在extent链接中跳转的方式,比如在forward方向:

  1. inline DiskLoc Record::getNext(const DiskLoc& myLoc) {  
  2.    //如果当前 Record的nextOfs偏移不为空,表示在当前extent中还有后续记录可访问  
  3.    if ( nextOfs != DiskLoc::NullOfs ) {  
  4.       /* defensive */ 
  5.       if ( nextOfs >= 0 && nextOfs < 10 ) {//是否为已删除的记录  
  6.          sayDbContext("Assertion failure - Record::getNext() referencing a deleted record?");  
  7.          return DiskLoc();  
  8.       }  
  9.    return DiskLoc(myLoc.a(), nextOfs);//获取下一条记录  
  10.    }  
  11.    Extent *e = myExtent(myLoc);//获取当前记录所属的Extent  
  12.    while ( 1 ) {  
  13.       if ( e->xnext.isNull() )  
  14.          return DiskLoc(); //已到表尾.  
  15.       e = e->xnext.ext();//跳转到下一个extent(以便进行next遍历)  
  16.       if ( !e->firstRecord.isNull() )  
  17.       break;  
  18.       // entire extent could be empty, keep looking  
  19.    }  
  20.    return e->firstRecord;//获取下一个extent中的第一条记录  
  21. }  
  22.  

在每个extent对象中,其还包括另外两个属性 firstRecord,lastRecord,两者皆为DiskLoc类型,顾名思义,它们分别指向当前extent的第一条和最后一条记录所在位置,这种定义它们是为了后者在extent中进行跳转时使用,当前如果在更加复杂的capped collection情况下,其值在会删除记录等操作时不断更新,比如下面代码:

  1. //namespace.cpp 文件912行,该方法在删除记录时调用  
  2. void DataFileMgr::_deleteRecord(NamespaceDetails *d, const char *ns, Record *todelete, const DiskLoc& dl)   
  3. {  
  4.    ......  
  5.    //extents是一个数据文件区域,该区域有所有记录(records)均属于同一个名空间namespace  
  6.    /* remove ourself from extent pointers */ 
  7.    {  
  8.       Extent *e = getDur().writing( todelete->myExtent(dl) );  
  9.       if ( e->firstRecord == dl )   
  10.       {//如果要删除记录为该extents区域第一条记录时  
  11.          if ( todelete->nextOfs == DiskLoc::NullOfs )//且为唯一记录时  
  12.             e->firstRecord.Null();//则该空间第一元素为空  
  13.          else //将当前空间第一条(有效)记录后移一位  
  14.             e->firstRecord.set(dl.a(), todelete->nextOfs);  
  15.       }  
  16.       if ( e->lastRecord == dl )   
  17.       {//如果要删除记录为该extents区域最后一条记录时  
  18.          if ( todelete->prevOfs == DiskLoc::NullOfs )//如果要删除记录的前一条信息位置为空时  
  19.             e->lastRecord.Null();//该空间最后一条记录清空  
  20.          else //设置该空间最后一条(有效)记录位置前移一位  
  21.             e->lastRecord.set(dl.a(), todelete->prevOfs);  
  22.       }  
  23.    }  
  24.    ......  
  25. }  
  26.  

介绍了cursor基类的定义和遍历方向这两个基本概念后,下面介绍一下在mongodb中,广泛使用的是basicCursor,其定义如下:

  1. class BasicCursor : public Cursor   
  2. {  
  3.    public:  
  4.       BasicCursor(DiskLoc dl, const AdvanceStrategy *_s = forward()) : curr(dl), s( _s ), _nscanned()   
  5.       {  
  6.          incNscanned();  
  7.          init();  
  8.       }  
  9.       BasicCursor(const AdvanceStrategy *_s = forward()) : s( _s ), _nscanned()   
  10.       {  
  11.          init();  
  12.       }  
  13.       bool ok() { return !curr.isNull(); }  
  14.       Record* _current()   
  15.       {  
  16.          assert( ok() );  
  17.          return curr.rec();  
  18.       }  
  19.       BSONObj current()   
  20.       {  
  21.          Record *r = _current();  
  22.          BSONObj j(r);  
  23.          return j;  
  24.       }  
  25.       virtual DiskLoc currLoc() { return curr; }  
  26.       virtual DiskLoc refLoc()  { return curr.isNull() ? last : curr; }  
  27.       bool advance();  
  28.       virtual string toString() { return "BasicCursor"; }  
  29.       virtual void setTailable()   
  30.       {  
  31.          if ( !curr.isNull() || !last.isNull() )  
  32.          tailable_ = true;  
  33.       }  
  34.       virtual bool tailable() { return tailable_; }  
  35.   ......  
  36. };  
  37.  

可认看到在其构造函数时,使用了forward方向的遍历方式, 即然定义了Forward方向的游标,mongodb接下来定义了Reverse方向的游标:  

  1. /* 用于排序 { $natural: -1 } */ 
  2. class ReverseCursor : public BasicCursor   
  3. {  
  4.    public:  
  5.       ReverseCursor(DiskLoc dl) : BasicCursor( dl, reverse() ) { }  
  6.       ReverseCursor() : BasicCursor( reverse() ) { }  
  7.       virtual string toString() { return "ReverseCursor"; }  
  8. };  

 另外为了支持capped collection集合类型(有关capped collection,参见这篇链接),mongodb分别定义了ForwardCappedCursor和ReverseCappedCursor:  

  1. class ForwardCappedCursor : public BasicCursor, public AdvanceStrategy   
  2. {  
  3.    public:  
  4.  
  5.       ForwardCappedCursor( NamespaceDetails *nsd = 0, const DiskLoc &startLoc = DiskLoc() );  
  6.       virtual string toString() {  
  7.          return "ForwardCappedCursor";  
  8.       }  
  9.       virtual DiskLoc next( const DiskLoc &prev ) const;  
  10.       virtual bool capped() const { return true; }  
  11.    private:  
  12.       NamespaceDetails *nsd;  
  13. };  
  14.  
  1. class ReverseCappedCursor : public BasicCursor, public AdvanceStrategy   
  2. {  
  3.    public:  
  4.       ReverseCappedCursor( NamespaceDetails *nsd = 0, const DiskLoc &startLoc = DiskLoc() );  
  5.       virtual string toString() {  
  6.          return "ReverseCappedCursor";  
  7.       }  
  8.       virtual DiskLoc next( const DiskLoc &prev ) const;  
  9.       virtual bool capped() const { return true; }  
  10.    private:  
  11.       NamespaceDetails *nsd;  
  12. };  
  13.  

 只不过在ForwardCappedCursor和ReverseCappedCursor中,实现next方法会更复杂一下,因为其要考虑删除的记录不在遍历结果中的情况。相当内容详见cursor.cpp的实现代码:)

介绍游标和mongofile结构之后,我们大体知道了mongodb如果遍历数据文件,另外mongodb使用了b树索引来加快查询效率,因此mongodb也提供了相应的btreeCursor,其主要用于遍历内存中的b树索引。

除此以外,为了方便client端使用cursor访问数据库,mongodb提供了ClientCursor,其对Cursor进一步封装(详见clientcursor.h)。

下面我们看一下mongodb如果要据查询方式来确定使用那种类型游标的:  

  1.  //pdfile.cpp 文件639行,查询从指定记录位置startLoc开始的记录,这里要据不同的条件使用不同的注季  
  2. shared_ptr<Cursor> DataFileMgr::findAll(const char *ns, const DiskLoc &startLoc)   
  3. {  
  4.    NamespaceDetails * d = nsdetails( ns );  
  5.    if ( ! d )  
  6.       return shared_ptr<Cursor>(new BasicCursor(DiskLoc()));  
  7.  
  8.    DiskLoc loc = d->firstExtent;  
  9.    Extent *e = getExtent(loc);  
  10.  
  11.    ......  
  12.  
  13.    if ( d->capped )  
  14.       return shared_ptr<Cursor>( new ForwardCappedCursor( d , startLoc ) );  
  15.  
  16.    if ( !startLoc.isNull() )  
  17.       return shared_ptr<Cursor>(new BasicCursor( startLoc ));  
  18.  
  19.    ......  
  20.    return shared_ptr<Cursor>(new BasicCursor( e->firstRecord ));  
  21. }  
  22.  

到这里,可以看了,mongodb在cursor的设计和使用方式上是基于“策略模式”(strategy pattern)的,如下图:

 

其中cursor就是各种遍历数据集合的策略,而pdfile.cpp就是持有相应cursor的上下文(context)  ,该模式也是使用比较广泛的一种设置模式,好处这里就不多说了。
  
好了,今天的内容到这里就告一段落了,在接下来的文章中,将会介绍mongodb中mmap的使用场景。

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

【编辑推荐】

  1. 走进MongoDB的世界 展开MongoDB的学习之旅
  2. 野心勃勃的NoSQL新贵 MongoDB应用实战
  3. MongoDB与CouchDB全方位对比
  4. MongoDB1.8发布,分布式文档数据库
责任编辑:艾婧 来源: 博客园
相关推荐

2011-12-02 13:04:06

Java

2011-05-26 10:05:48

MongoDB

2011-05-26 16:18:51

Mongodb

2021-10-26 10:22:27

ArrayList阿里云

2011-04-06 09:30:29

游标脚本性能问题

2017-02-27 09:03:37

Mesos架构源码

2022-01-12 18:35:54

MongoDB数据查询

2009-07-08 14:06:22

ClassLoaderJDK源码

2011-09-14 15:30:00

MongoDB

2010-07-16 09:30:42

C#MongoDB

2009-07-08 12:53:29

JDK源码Java.lang.B

2021-05-26 05:22:09

Virtual DOMSnabbdom虚拟DOM

2011-04-29 13:40:37

MongoDBCommand

2023-12-17 14:43:17

2010-11-16 15:40:21

oracle游标

2012-07-26 08:32:38

MongoDB

2018-11-13 09:49:11

存储云存储云备份

2015-03-31 18:26:43

陌陌社交

2009-10-27 16:26:58

2011-04-25 17:15:39

MongodbMMAP
点赞
收藏

51CTO技术栈公众号