MongoDB數據緩存刷新機制
最近配合好幾個項目測試了MongoDB的寫入性能。在內存沒有用盡的情況下,雖然MongoDB只有一個更新線程,寫入還是非常快的,基本上能達到25000/s以上(索引數據用uuid_generate_randome和uuid_unparse隨機產生)。當內存用盡開始往磁盤上刷臟頁的時候,性能有非常大的波動,即使調整了syncdelay也沒有太大改善。在測試中還出現了一個莫名其妙的情況:MongoDB會間歇性地釋放文件系統的cache。除了直接刪除表空間之外,很難想到有什么動作可以誘發這個現象。在MongoDB開發者論壇里描述了這個現象,但是 Eliot Horowitz認為MongoDB內部并沒有代碼會釋放文件系統cache。那么,讓我們去源碼里面看一下MongoDB緩存和刷新數據的機制。
首先找到mongod的入口(db/db.cpp),發現MongoDB的初始化步驟非常簡單,概括起來就以下三步:
- int main(int argc, char* argv[], char *envp[] )
- {
- …
- Module::configAll( params );
- dataFileSync.go();
- …
- initAndListen(cmdLine.port, appsrvPath);
- …
- }
顯然,dataFileSync就是我們感興趣的那個類。dataFileSync類派生自BackgroundJob類,而BackgroundJob 主要的功能就是生成一個后臺線程并指派任務。數據的刷新是一個不斷執行的后臺任務,在dataFileSync.run()里面可以找到刷數據的相關代碼:
- void run()
- {
- …
- Date_t start = jsTime();
- int numFiles = MemoryMappedFile::flushAll( true );
- time_flushing = (int) (jsTime() – start);
- globalFlushCounters.flushed(time_flushing);
- …
- }
從這一段代碼看,MongoDB會在syncdelay設定的周期內,采取同步的形式刷新所有的臟數據。再看一下flushAll是怎么刷新所有數據的:
- int MongoFile::flushAll( bool sync )
- {
- …
- set seen;
- while ( true ){
- auto_ptr f;
- {
- rwlock lk( mmmutex , false );
- for ( set::iterator i = mmfiles.begin(); i != mmfiles.end(); i++ ){
- MongoFile * mmf = *i;
- if ( ! mmf )
- continue;
- if ( seen.count( mmf ) )
- continue;
- f.reset( mmf->prepareFlush() );
- seen.insert( mmf );
- break;
- }
- }
- if ( ! f.get() )
- break;
- f->flush();
- }
- return seen.size();
- }
上面這一段代碼實現的功能很簡單,就是把mmfiles中所有MongoFile指針所引用的對象都flush()一次。不過在執行flush()函數之前,需要先執行prepareFlush()確保這個對象是可以執行flush()函數的。下面是***真正執行刷新操作的代碼:
- void MemoryMappedFile::flush(bool sync)
- {
- if ( view == 0 || fd == 0 )
- return;
- if ( msync(view, len, sync ? MS_SYNC : MS_ASYNC) )
- problem() << “msync ” << errnoWithDescription() << endl;
- }
終于刷新到磁盤了,呵呵。不過這篇blog只涉及到了數據刷新的代碼,至于如何緩存,且聽下回分解。
【編輯推薦】
- 設計實例對比:MySQL vs MongoDB
- MongoDB基于Java、PHP的一般操作和用戶安全設置
- 在Windows環境下MongoDB搭建和簡單操作
- 教你如何利用MySQL學習MongoDB
- 如何用Java操作MongoDB