淺析Mongodb源碼之游標Cursor
在Mongodb中,其提供了類似關系型數(shù)據(jù)中cursor對象來遍歷數(shù)據(jù)集合,同時mongodb并要根據(jù)不同的場景生成不同的游標對象(cursor),比如順序遍歷游標(basicCursor),反向游標(reverseCursor), B樹索引游標(btreeCursor)等。
下面是其游標體系架構類圖(位于cursor.cpp, cursor.h, clientcursor.cpp, clientcursor.h):
從該圖中,可以看到除了(ClientCursor)之外,其余游標均繼承自Cursor這個類(基類),下面我們看一下其具體實現(xiàn):
- class Cursor : boost::noncopyable//使類和派生類不可復制
- {
- virtual bool ok() = 0;//游標當前指向的對象是否有效
- bool eof() { return !ok(); }//是否已到尾部
- virtual Record* _current() = 0;//游標當前指向的記錄(記錄是組成數(shù)據(jù)文件的最基本單位)
- virtual BSONObj current() = 0;//游標當前指向的BSONObj對象
- virtual DiskLoc currLoc() = 0;//游標當前指向的DiskLoc
- virtual bool advance() = 0; /*true=ok,將游標指向到下一條記錄所在位置*/
- virtual BSONObj currKey() const { return BSONObj(); }
- /* 標識游標是否為Tailable類型,該類型支持獲取最后一條記錄后,不馬上關閉游標,以便持續(xù)獲取后面新添加的記錄*/
- virtual bool tailable()
- {
- return false;
- }
- //設置游標為Tailable類型
- virtual void setTailable() {}
- .....
- }
在mongodb中,提供了兩種遍歷數(shù)據(jù)集合的方向,分別是“向前”和“倒轉”方式,其聲明如下:
- class AdvanceStrategy
- {
- public:
- virtual ~AdvanceStrategy() { }
- virtual DiskLoc next( const DiskLoc &prev ) const = 0;
- };
- const AdvanceStrategy *forward(); //向前
- const AdvanceStrategy *reverse(); //倒轉
下面是其實現(xiàn)方式如下:
- 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;
- const AdvanceStrategy *forward() {
- return &_forward;
- }
- const AdvanceStrategy *reverse() {
- return &_reverse;
- }
看到這里,我們有必須簡要說明一下mongofile文件的結構,見下面說明:
- /* a datafile - i.e. the "dbname.<#>" files :
- ----------------------
- DataFileHeader :數(shù)據(jù)頭文件信息,包括版本,文件長度,使用情況等
- ----------------------
- Extent (for a particular namespace) 特定namespace下的extent,可理解為數(shù)據(jù)集合
- Record : 單條數(shù)據(jù)記錄
- ...
- Record (some chained for unused space)
- ----------------------
- more Extents... 其它extent
- ----------------------
- */
在一個數(shù)據(jù)庫文件中,同一個namespace的extent可以有多個,每一個extent都有一些記錄(record)組成,如果訪問record,可以使用diskloc加上文件偏移(getOfs:位于diskloc中)獲取。
同時每個extent中包括還包括兩個重要屬性:
- DiskLoc xnext, xprev; /* next/prev extent for this namespace */
它們分別記錄了同一namespace下,在extent鏈表中,當前extent的前或后一個extent的位置信息,上面AdvanceStrategy中的next方法即實現(xiàn)了在兩種遍歷方向(上面已提到)上,在extent鏈接中跳轉的方式,比如在forward方向:
- inline DiskLoc Record::getNext(const DiskLoc& myLoc) {
- //如果當前 Record的nextOfs偏移不為空,表示在當前extent中還有后續(xù)記錄可訪問
- if ( nextOfs != DiskLoc::NullOfs ) {
- /* defensive */
- if ( nextOfs >= 0 && nextOfs < 10 ) {//是否為已刪除的記錄
- sayDbContext("Assertion failure - Record::getNext() referencing a deleted record?");
- return DiskLoc();
- }
- return DiskLoc(myLoc.a(), nextOfs);//獲取下一條記錄
- }
- Extent *e = myExtent(myLoc);//獲取當前記錄所屬的Extent
- while ( 1 ) {
- if ( e->xnext.isNull() )
- return DiskLoc(); //已到表尾.
- e = e->xnext.ext();//跳轉到下一個extent(以便進行next遍歷)
- if ( !e->firstRecord.isNull() )
- break;
- // entire extent could be empty, keep looking
- }
- return e->firstRecord;//獲取下一個extent中的第一條記錄
- }
在每個extent對象中,其還包括另外兩個屬性 firstRecord,lastRecord,兩者皆為DiskLoc類型,顧名思義,它們分別指向當前extent的第一條和最后一條記錄所在位置,這種定義它們是為了后者在extent中進行跳轉時使用,當前如果在更加復雜的capped collection情況下,其值在會刪除記錄等操作時不斷更新,比如下面代碼:
- //namespace.cpp 文件912行,該方法在刪除記錄時調用
- void DataFileMgr::_deleteRecord(NamespaceDetails *d, const char *ns, Record *todelete, const DiskLoc& dl)
- {
- ......
- //extents是一個數(shù)據(jù)文件區(qū)域,該區(qū)域有所有記錄(records)均屬于同一個名空間namespace
- /* remove ourself from extent pointers */
- {
- Extent *e = getDur().writing( todelete->myExtent(dl) );
- if ( e->firstRecord == dl )
- {//如果要刪除記錄為該extents區(qū)域第一條記錄時
- if ( todelete->nextOfs == DiskLoc::NullOfs )//且為唯一記錄時
- e->firstRecord.Null();//則該空間第一元素為空
- else //將當前空間第一條(有效)記錄后移一位
- e->firstRecord.set(dl.a(), todelete->nextOfs);
- }
- if ( e->lastRecord == dl )
- {//如果要刪除記錄為該extents區(qū)域最后一條記錄時
- if ( todelete->prevOfs == DiskLoc::NullOfs )//如果要刪除記錄的前一條信息位置為空時
- e->lastRecord.Null();//該空間最后一條記錄清空
- else //設置該空間最后一條(有效)記錄位置前移一位
- e->lastRecord.set(dl.a(), todelete->prevOfs);
- }
- }
- ......
- }
介紹了cursor基類的定義和遍歷方向這兩個基本概念后,下面介紹一下在mongodb中,廣泛使用的是basicCursor,其定義如下:
- class BasicCursor : public Cursor
- {
- public:
- BasicCursor(DiskLoc dl, const AdvanceStrategy *_s = forward()) : curr(dl), s( _s ), _nscanned()
- {
- incNscanned();
- init();
- }
- BasicCursor(const AdvanceStrategy *_s = forward()) : s( _s ), _nscanned()
- {
- init();
- }
- bool ok() { return !curr.isNull(); }
- Record* _current()
- {
- assert( ok() );
- return curr.rec();
- }
- BSONObj current()
- {
- Record *r = _current();
- BSONObj j(r);
- return j;
- }
- virtual DiskLoc currLoc() { return curr; }
- virtual DiskLoc refLoc() { return curr.isNull() ? last : curr; }
- bool advance();
- virtual string toString() { return "BasicCursor"; }
- virtual void setTailable()
- {
- if ( !curr.isNull() || !last.isNull() )
- tailable_ = true;
- }
- virtual bool tailable() { return tailable_; }
- ......
- };
可認看到在其構造函數(shù)時,使用了forward方向的遍歷方式, 即然定義了Forward方向的游標,mongodb接下來定義了Reverse方向的游標:
- /* 用于排序 { $natural: -1 } */
- class ReverseCursor : public BasicCursor
- {
- public:
- ReverseCursor(DiskLoc dl) : BasicCursor( dl, reverse() ) { }
- ReverseCursor() : BasicCursor( reverse() ) { }
- virtual string toString() { return "ReverseCursor"; }
- };
另外為了支持capped collection集合類型(有關capped collection,參見這篇鏈接),mongodb分別定義了ForwardCappedCursor和ReverseCappedCursor:
- class ForwardCappedCursor : public BasicCursor, public AdvanceStrategy
- {
- public:
- ForwardCappedCursor( NamespaceDetails *nsd = 0, const DiskLoc &startLoc = DiskLoc() );
- virtual string toString() {
- return "ForwardCappedCursor";
- }
- virtual DiskLoc next( const DiskLoc &prev ) const;
- virtual bool capped() const { return true; }
- private:
- NamespaceDetails *nsd;
- };
- class ReverseCappedCursor : public BasicCursor, public AdvanceStrategy
- {
- public:
- ReverseCappedCursor( NamespaceDetails *nsd = 0, const DiskLoc &startLoc = DiskLoc() );
- virtual string toString() {
- return "ReverseCappedCursor";
- }
- virtual DiskLoc next( const DiskLoc &prev ) const;
- virtual bool capped() const { return true; }
- private:
- NamespaceDetails *nsd;
- };
只不過在ForwardCappedCursor和ReverseCappedCursor中,實現(xiàn)next方法會更復雜一下,因為其要考慮刪除的記錄不在遍歷結果中的情況。相當內容詳見cursor.cpp的實現(xiàn)代碼:)
介紹游標和mongofile結構之后,我們大體知道了mongodb如果遍歷數(shù)據(jù)文件,另外mongodb使用了b樹索引來加快查詢效率,因此mongodb也提供了相應的btreeCursor,其主要用于遍歷內存中的b樹索引。
除此以外,為了方便client端使用cursor訪問數(shù)據(jù)庫,mongodb提供了ClientCursor,其對Cursor進一步封裝(詳見clientcursor.h)。
下面我們看一下mongodb如果要據(jù)查詢方式來確定使用那種類型游標的:
- //pdfile.cpp 文件639行,查詢從指定記錄位置startLoc開始的記錄,這里要據(jù)不同的條件使用不同的注季
- shared_ptr<Cursor> DataFileMgr::findAll(const char *ns, const DiskLoc &startLoc)
- {
- NamespaceDetails * d = nsdetails( ns );
- if ( ! d )
- return shared_ptr<Cursor>(new BasicCursor(DiskLoc()));
- DiskLoc loc = d->firstExtent;
- Extent *e = getExtent(loc);
- ......
- if ( d->capped )
- return shared_ptr<Cursor>( new ForwardCappedCursor( d , startLoc ) );
- if ( !startLoc.isNull() )
- return shared_ptr<Cursor>(new BasicCursor( startLoc ));
- ......
- return shared_ptr<Cursor>(new BasicCursor( e->firstRecord ));
- }
到這里,可以看了,mongodb在cursor的設計和使用方式上是基于“策略模式”(strategy pattern)的,如下圖:
其中cursor就是各種遍歷數(shù)據(jù)集合的策略,而pdfile.cpp就是持有相應cursor的上下文(context) ,該模式也是使用比較廣泛的一種設置模式,好處這里就不多說了。
好了,今天的內容到這里就告一段落了,在接下來的文章中,將會介紹mongodb中mmap的使用場景。
原文鏈接:http://www.cnblogs.com/daizhj/archive/2011/04/15/mongodb_cursor_source_code.html
【編輯推薦】