深入mongoDB(1)–mongod的執行緒模式與網路架構

來源:互聯網
上載者:User

最近工作需要開始研究mongoDB,我準備從其原始碼角度,對於mongod和mongos服務的架構、sharding策略、replicaset策略、資料同步容災、索引等機製做一個本質性的瞭解。其代碼約20萬行(我研究的是 2.0.6版本源碼),本篇先從mongod的啟動流程說起,它本是一個多線程程式,所以本文在於說明mongod有多少個線程,每個線程的意義所在。希望大家閱讀本文時關注在mongod的外圍架構,暫不涉及資料檔案的組織、索引B樹的組織等,僅focus in在網路架構、執行緒模式上。

弄清楚這點的好處很明顯:之後就可以有的放矢的研究mongod某個模組究竟是如何?的,可以快速的跳到相應的類中閱讀源碼,解決我們在產品中的實際問題。我認為這是研究其龐大源碼一個好的開始。

在說明mongod前,須瞭解mongoDB大量代碼是基於boost庫構建的,因此這裡先行對boost庫建立線程做個簡單的瞭解。

1、boost庫如何建立線程

boost::thread是boost中跨平台的多線程庫,mongoDB建立線程時大多數情況下是使用thread庫的(少量情況直接調用pthread_create方法),主要使用了以下兩種方式:

(1)直接運行讓線程運行func

例如durThread線程:

void durThread() {

while( !inShutdown() ) { ... }

}

boost::thread t(durThread);

(2)在類中定義靜態run方法,調用thread建立線程

    class FileAllocator : boost::noncopyable {
        static void run( FileAllocator * fa );

        void FileAllocator::start() {
             boost::thread t( boost::bind( &FileAllocator::run , this ) );
        }
    };

2、mongod的入口

mongod的入口main函數在src/mongo/db/db.cpp檔案中,我畫了個簡單的活動圖表簡要介紹其啟動流程:

如所示,這裡出現了12個固定線程,還沒有包括mongod運行以後處理請求時派生出來的線程,如下所示:

–      interruptThread

–      DataFileSync::run

–      FileAllocator::run

–      durThread

–      SnapshotThread::run

–      ClientCursorMonitor::run

–      PeriodicTask::Runner::run

–      TTLMonitor::run

–      replSlaveThread

–      replMasterThread

–      webServerThread

–      處理資料庫請求的主線程

如果不屬於任何replica set,那麼至少有10個固定線程(去除 replSlaveThread和 replMasterThread)。

下面我們先討論這10個固定的線程,再討論效能非常弱的監聽web事件的線程是怎樣處理請求的,最後討論效能稍好一點的主服務線程是怎樣處理請求的。

3、5個基於BackgroundJob類實現的背景工作執行緒

這5個線程分別是DataFileSync,SnapshotThread, ClientCursorMonitor, TTLMonitor, PeriodicTask,類圖如下所示:

上面這5個類也是用boost::threadfunction方法建立線程啟動並執行,它們繼承了BackgroundJob類,使用go方法啟動線程執行jobBody就是在啟動線程執行run方法,如下所示:

    BackgroundJob& BackgroundJob::go() {        boost::thread t( boost::bind( &BackgroundJob::jobBody , this, _status ) );        return *this;    }    void BackgroundJob::jobBody( boost::shared_ptr<JobStatus> status ) {        ...        run();        ...    }

這些線程的意義如下:

DataFileSync主要在調用MemoryMappedFile::flush方法將記憶體中的資料刷到磁碟上。 我們知道,mongodb是調用mmap把磁碟中的資料對應到記憶體中的,所以必須有一個機制時刻的刷資料到硬碟才能保證可靠性,多久刷一次是與syncdelay參數相關的。

SnapshotThread將產生快照檔案協助快速恢複。

ClientCursorMonitor將系統管理使用者的遊標,每4秒調用一次idleTimeReport()方法,每一分鐘調用sayMemoryStatus()方法。

TTLMonitor管理TTL,通過調用doTTLForDB()方法檢查所有db。

PeriodicTask將從動態數組std::vector<PeriodicTask* > _tasks中擷取週期性任務執行。

4、5個直接提供全域方法執行的線程

FileAllocator用於分配新檔案,它決定分配檔案的大小,例如用翻倍的方式。

interruptThread只處理訊號量。

durThread做批量提交和復原工作。

replSlaveThread是當前結點作為secondary時的同步線程。

replMasterThread是當前結點作為master時的同步線程。

5、web監聽線程

mongod是如何處理web請求的呢?它是通過網路架構中的核心類Listerner實現的,類圖如下所示:

怎麼理解這幅類圖呢?

首先看 Listener類,它負責監聽、建立新串連,其工作步驟如下:

a、建立socket控制代碼,綁定連接埠,監聽

b、調用select檢測新串連事件

c、對偵測到的事件調用accept建立新串連

d、調用void Listener::acceptedMP(MessagingPort*mp)方法處理新串連,誰重新實現acceptedMP方法誰決定處理方式

這個Listener類既用於處理web請求,也用於處理普通的資料庫請求。

OK,現在我們看web請求是如何處理的。MiniWebServer類繼承了Listener類,它重新實現了acceptedMP方法,開始接收TCP流,解析HTTP協議,同時還會負責組裝HTTP響應包並發送TCP流到用戶端。那麼實際完成http請求的類是誰呢?它是繼承了MiniWebServer類的DbWebServer類。這個類重新實現了doRequest方法,它會在完整接收到HTTP請求後被調用,HTTP請求的處理過程不在本篇的討論範圍內,這裡略過。但我們清楚了,這個線程採用同步的阻塞的方式處理請求,它意味著它同一時刻只能處理一個web請求,並發能力超級弱,還好web請求只是mongod的副業,僅用於查詢狀態。

6、主監聽線程和資料請求的處理線程

處理資料庫請求的是中的PortMessageServer 類,它運行在主線程中。

我們先看看PortMessageServer 類是如何?acceptedMP方法的:

virtual voidacceptedMP(MessagingPort * p) {if ( !connTicketHolder.tryAcquire() ) {sleepmillis(2); // otherwisewe'll hard loopreturn;} …int failed =pthread_create(&thread, &attrs, (void*(*)(void*)) &pms::threadRun,p);…}

很清晰,它開啟了一個線程獨立的執行這個請求。雖然這種方式依然效能極差:大量的進程間環境切換在等著我們,但總比web請求處理要好多了,而且mongod的並發能力本來就不是它的長項。

對於每個新串連,都會有類封裝成對象,如下:

接下來pms::threadRun方法是在處理MessagingPort對象。

下面看看pms::threadRun方法中做了些什麼:

void threadRun( MessagingPort *inPort) {TicketHolderReleaserconnTicketReleaser( &connTicketHolder );Message m;try {LastError * le = newLastError();lastError.reset( le ); //lastError now has ownershiphandler->connected( p.get());while ( ! inShutdown() ) {if ( ! p->recv(m) ) {p->shutdown();break;}handler->process( m ,p.get() , le );}}handler->disconnected( p.get());}

可以看到,它會在這個串連上接收完整的請求,之後會調用handler的process方法。這個handler又是什麼呢?如所示:

所以,普通的資料庫請求是由MyMessageHandler的process方法處理的。這個方法裡也只是個封裝,真正處理業務的是全域方法assembleResponse。

assembleResponse方法中會按照8種操作方式分別的調用DataFileMgr中的方法處理實際檔案,例如:

enum Operations {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};

在方法中有類似這樣的代碼在調用實際的業務類處理操作:

                else if ( op == dbInsert ) {                    receivedInsert(m, currentOp);                }                else if ( op == dbUpdate ) {                    receivedUpdate(m, currentOp);                }                else if ( op == dbDelete ) {                    receivedDelete(m, currentOp);                }

當然本篇志不在此,下篇我們再討論索引和資料檔案的操作。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.