1. 程式人生 > >MongoDB查詢時的卡頓與記憶體問題

MongoDB查詢時的卡頓與記憶體問題

這兩天從Remote MongoDB讀資料的時候遇到了一個很奇葩的問題,就是使用DBCursor讀資料的時候每六千多條就卡頓很久,這個六千多是一個固定的數。然後是58w的資料每次到20w putty終端就會卡死,然後MongoDB也會退出。雖然到現在問題還沒解決,但是先把資料做一下彙總,看能不能有所幫助。

阿里雲伺服器只有1G記憶體,但是資料大概也只有320M。

首先從這個連結http://huoding.com/2011/08/19/107看到一些MongoDB記憶體的基本介紹,可知MongoDB使用的是記憶體對映儲存引擎,具體就是MMap,MongoDB採用作業系統底層提供的記憶體檔案對映(MMap)的方式來實現對資料庫記錄檔案的訪問,MMAP可以把磁碟檔案的全部內容直接對映到程序的記憶體空間,這樣檔案中的每條資料記錄就會在記憶體中有對應的地址,這時對檔案的讀寫可以直接通過操作記憶體來完成。同時操作的是虛擬記憶體,所以最好人為控制一下資料量,最好小於實體記憶體,否則可能會有效能問題。

MongoDB的工作基本上都是在記憶體完成的,這也是它高效能的原因之一,特別是插入的時候,在java中例項化Mongo客戶端插入重複 _id 資料會迅速的被重寫就是因為操作是在記憶體中完成,MongoClient則修改了這一機制,插入重複_id會丟擲異常。我在查詢的時候使用了skip,但據說skip也是要一條一條數過去。
自帶的mongostat工具可以監控記憶體佔用情況,下面是擷取的一部分
查詢資料監控
visze:佔用的虛擬記憶體大小
res:佔用的實體記憶體大小
這裡的visze大概是res的兩倍,這是因為journal的緣故,journal相當於給了一個redo操作,如果不想開可以–nojournal。關於journal:

http://blog.mongodb.org/post/33700094220/how-mongodbs-journaling-works

今天做了改進,改成每1w條開啟一次DBCursor,並且每次迴圈後都關閉了DBCrusor,雖然不知道此舉有什麼用,但至少減少一點記憶體洩露的可能。我發現這種做法雖然不能解決卡頓的原因,但MongoDB不會退出了,算是解決了一個問題。但是佔用的記憶體還是往上一直升,證明在連線開啟時讀到的資料是一直存在記憶體裡的,下一步可能需要在迴圈裡開啟關閉MongoClient了。

現在回到我的問題,每到固定的6k條就卡頓,經過監控得知記憶體是足夠的,畢竟第一次只有6k條大概也就不到10MB的資料。連線數也正常。
列一下步驟:
MongoClient建立連線–>想不出有什麼問題
建立DBCursor得到查詢結果–>通過debug看到這個過程有些緩慢,但確實正常結束了
遍歷DBCursor返回結果–>想了一下可能是遍歷的時候出了問題
MongoDB伺服器建立連線–>通過mongostat看到沒有問題
MongoDB伺服器返回查詢結果–>既然DBCursor查到了結果應該也是對的。
這麼一來貼一下程式碼
MongoClient mongo;
try {
mongo = new MongoClient(AppConfig.IP, AppConfig.Port);
DB db = mongo.getDB(AppConfig.dbName);
DBCollection dbcol = db.getCollection(AppConfig.collectionName);
long total = dbcol.count();
long times = (total-total % limitedRecordCount)/limitedRecordCount + 1;
System.out.println(times);
for (int i = 1; i <= times; i++) {
DBCursor dbcursor = dbcol.find().skip((i - 1) * limitedRecordCount).limit(limitedRecordCount);
int count = 0;

            while (dbcursor.hasNext()) {
                DBObject oneRecord = dbcursor.next();
                ...                 
            }
            System.out.println("第"+i+"次迴圈,共" + count + "條");
            dbcursor.close();
        }

    } catch (UnknownHostException e) {
        e.printStackTrace();
    }

問題出在while中,省略號部分只是一些很簡單的封裝操作,那麼很大程度是DBCursor出了問題。待我發現了日後再更新。

好訊息好訊息,剛發現索引建完了。
575030
init done!
read 575030 lines of data and build index in 4665847 millsecond
但是bug還是存在的…