MongoDB整理筆記のjava MongoDB分頁最佳化

來源:互聯網
上載者:User

標籤:

    最近項目在做網站使用者資料新訪客統計,資料存放區在MongoDB中,統計的資料其實也並不是很大,1000W上下,但是公司只配給我4G記憶體的電腦,讓我程式跑起來氣喘籲籲...很是疲憊不堪。

    最常見的問題莫過於查詢MongoDB記憶體溢出,沒辦法只能分頁查詢。這種思想大家可能都會想到,但是如何分頁,確實多有門道!

    網上用的最多的,也是最常見的分頁採用的是skip+limit這種組合方式,這種方式對付小資料倒也可以,但是對付上幾百上千萬的大資料,卻只能望而興歎...

    經過網上各種尋找資料,尋師問道的,發現了一種速度足以把skip+limit組合分頁甩出幾條街的方法。

    思路: 條件查詢+排序+限制返回記錄。邊查詢,邊排序,排序之後,抽取第一次分頁中的最後一條記錄,作為第二次分頁的條件,進行條件查詢,以此類推....

    先上代碼: 

 /**     * 小於指定日期的所有根據UUID分組的訪問記錄     * @param 指定日期     * @return 所有訪問記錄的MAP     */    public static Multimap<String, Map<String, String>> getOldVisitors(String date){                //每次查詢的記錄數        int pagesize = 100000;                //mongodb中的"_id"        String objectId = "";                //方法的傳回值類型,此處用的google guava        Multimap<String, Map<String, String>> mapless = null;                //查詢的條件        BasicDBObject queryless = new BasicDBObject(),fields = new BasicDBObject(),field = new BasicDBObject();                //初始化返回的mongodb集合操作對象,大家可以寫個資料連線池        dbCol = init();                //查詢指定欄位,欄位越少,查詢越快,當然都是一些不必要欄位        field.put("uuid",1);                fields.put("uuid", 1);                fields.put("initTime", 1);                //小於指定日期的條件        String conditionless = TimeCond.getTimeCondless(date);                queryless.put("$where", conditionless);                DBCursor cursorless = dbCol.find(queryless,field);                //MongoDB在小於指定日期條件下,集合總大小        int countless = cursorless.count();                //查詢遍曆的次數 circleCountless+1        int circleCountless = countless/pagesize;                //模數,這是最後一次迴圈遍曆的次數        int modless = countless%pagesize;                //開始遍曆查詢        for (int i = 1; i <=circleCountless+1; i++) {                        //文檔對象            DBObject obj = null;                        //將遊標中返回的結果記錄到list集合中,為什麼放到list集合中?這是為後面guava 分組做準備            List<Map<String, String>> listOfMaps = new ArrayList();                        //如果條件不為空白,則加上此條件,構成多條件查詢,這一步是分頁的關鍵            if (!"".equals(objectId)) {                                  //我們通過文檔對象obj.get("_id")返回的是不帶ObjectId(),所以要求此步驟                               ObjectId id = new ObjectId(objectId);                                   queryless.append("_id", new BasicDBObject("$gt",id));                            }                        if (i<circleCountless+1) {                            cursorless = dbCol.find(queryless,fields).sort(new BasicDBObject("_id", 1)).limit(pagesize);                            }else if(i==circleCountless+1){//最後一次迴圈                                cursorless = dbCol.find(queryless,fields).limit(modless);            }                                //將遊標中返回的結果記錄到list集合中,為什麼放到list集合中?這是為後面guava 分組做準備                while (cursorless.hasNext()) {                                        obj = cursorless.next();                                        listOfMaps.add((Map<String, String>) obj);                                    }                //擷取一次分頁中最後一條記錄的"_id",然後作為條件傳入到下一個迴圈中                if (null!=obj) {                                         objectId = obj.get("_id").toString();                                         }            //第一次分組,根據uuid分組,分組除今天之外的曆史資料        mapless = Multimaps.index(                      listOfMaps,new Function<Map<String, String>, String>() {                          public String apply(final Map<String, String> from) {                                                               return from.get("uuid");                          }                 });                        }                return mapless;    }
View Code

    這裡為什麼要用"_id"這個欄位作為分頁的條件?其實,我也用過其他欄位,比如時間欄位,時間字串也是可以比大小的,但它的效率遠不如"_id"高。

    關於MongoDB中的"_id",以前一直忽略它的作用,直接結果是讓我耗了很多時間和精力,繞了大半圈,又回到了原點,有一種眾裡尋他千百度,驀然回首,那人卻在燈火闌珊處的感覺...

    MongoDB ObjectId

    “4e7020cb7cac81af7136236b”這個24位的字串,雖然看起來很長,也很難理解,但實際上它是由一組十六進位的字元構成,每個位元組兩位的十六進位數字,總共用了12位元組的儲存空間。相比MYSQLint類型的4個位元組,MongoDB確實多出了很多位元組。不過按照現在的存放裝置,多出來的位元組應該不會成為什麼瓶頸。不過MongoDB的這種設計,體現著空間換時間的思想。官網中對ObjectId的規範,:

     

    1)Time

    時間戳記。將剛才產生的objectid的前4位進行提取“4e7020cb”,然後按照十六進位轉為十進位,變為“1315971275”,這個數字就是一個時間戳記。通過時間戳記的轉換,就成了易看清的時間格式。

    2)Machine

機器。接下來的三個位元組就是“7cac81”,這三個位元組是所在主機的唯一識別碼,一般是機器主機名稱的散列值,這樣就確保了不同主機產生不同的機器hash值,確保在分布式中不造成衝突,這也就是在同一台機器產生的objectId中間的字串都是一模一樣的原因。

    3)PID

    進程ID。上面的Machine是為了確保在不同機器產生的objectId不衝突,而pid就是為了在同一台機器不同的mongodb進程產生了objectId不衝突,接下來的“af71”兩位就是產生objectId的進程標識符。

    4)INC

    自增計數器。前面的九個位元組是保證了一秒內不同機器不同進程產生objectId不衝突,這後面的三個位元組“36236b”是一個自動增加的計數器,用來確保在同一秒內產生的objectId也不會發現衝突,允許256的3次方等於16777216條記錄的唯一性。

    總的來看,objectId的前4個位元組時間戳記,記錄了文檔建立的時間;接下來3個位元組代表了所在主機的唯一識別碼,確定了不同主機間產生不同的objectId;後2個位元組的進程id,決定了在同一台機器下,不同mongodb進程產生不同的objectId;最後通過3個位元組的自增計數器,確保同一秒內產生objectId的唯一性。ObjectId的這個主鍵建置原則,很好地解決了在分布式環境下高並發情況主鍵唯一性問題,值得學習借鑒。

MongoDB整理筆記のjava MongoDB分頁最佳化

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.