標籤:
由於nodejs本身的限制,在程式中使用js進行大批次運算效率不高。而V8引擎自身對記憶體大小的限制(64位系統下1.4G),同樣限制了資料規模。
因此,相對於從mongodb中抽出資料進行計算,在mongodb中利用彙總函式或者其他方法完成計算,避開nodejs自身限制的方案在可靠性和擴充性上都相對較為令人滿意。
mongodb支援類似SQL中的彙總函式,雖然文法不通,不過基本原理類似。
mongodb內建的介面中,aggregate被用來實現彙總查詢:
rec = db.LIBRARY.aggregate([{ $match: { date: { $gte: ‘2220832129000‘, $lte: ‘3330918529000‘ } }}, { $group: { _id: ‘$book_id‘, maxScience: { $max: ‘$shelf.science‘ }, maxMath: { $max: ‘$data.math‘ }, avgAlgebra: { $avg: ‘$data.algebra‘ } }}])
在$group中可以利用各種運算子實現各種彙總運算。
如果目的是產生欄位不多的簡單資料結構,彙總運算幾乎可以一步到位。
需要注意的是在沒有格式轉換的情況下,js對於字串和數位區分很模糊。如果對字串變數使用max函數,出現的結果會是"999" > "1234"。如果mongodb內部資料格式不規範,可能得不到理想的結果。
對於複雜的計算,可以使用mapReduce方法。
mongodb的原生方法中包括了mapReduce方法,遍曆所有符合query條件的資料,對於每一條抽取到的資料,通過map方法提出索引值對,在收到的鍵發生變化時,將目前為止當前鍵的所有值儲存在一個數組中。
對於reduce方法觸發的時機,是在遍曆結果集,鍵發生變化時觸發,還是遍曆結束後,對新的索引值對遍曆執行reduce方法,這個目前還不能確定。
傳遞給reduce方法的參數包括鍵和一個儲存目前為止當前鍵的所有值的數組,reduce處理結束後,返回處理結果的統計結果,mapReduce過程結束。
mapReduce是個功能很強大的方法,目前我只能將接觸到的內容記錄下來,等待以後慢慢完善。
首先,是最核心的map方法和reduce方法。
※ map方法
處理query條件抽取的結果集,對於每一條mongodb的記錄,都會執行一次map方法。
map方法一般不需要傳入參數,在方法體重,this指向當前處理的記錄,可以通過this.columnName方式調用記錄中的欄位。
map方法不需要傳回值,在方法體中,調用內建的emit方法,以emit(key, val)的形式向mapReduce記憶體中傳遞一組索引值對。索引值對的格式很自由,即使在目前記錄中不存在的變數,只要符合文法,同樣可以正常emit傳出。網上的例子裡,大部分emit方法中的value值都是數字,或單變數,但其實emit傳遞的索引值可以是複雜的json甚至某個外部方法,配合對應的reduce方法,可以實現更為複雜也更有效功能。
※ reduce方法
reduce方法的調用時機目前還不確定,基礎原理是接收兩個參數,key和values。其中key是emit傳出的鍵,values是經過整合後,集中在同一個數組中的相同key的值,即使只有一個值,values也是數組形式儲存的。
在此過程之前,相同key的相同value並不會被合并,map reduce概念說明中提到的詞頻統計的執行個體,是在map reduce過程完成後才產生了被合并的結果,而在reduce方法真正執行之前,相同key的相同value應該會保持emit時的狀態。
在reduce方法中,遍曆values數組,根據emit方法中value對應的結構,對有共同特性的結果進行統一處理,最後返回結果,在mapReduce方法的傳回值中,當前key對應的值就是reduce方法返回的結果。可以總結為mapReduce方法的返回結果就是 emit中的key: reduce中的傳回值 形式的json格式索引值對組。
在mongodb自己的js介面中,mapReduce方法的直接傳回值是方法的結果統計:
> rec = db.DATA.mapReduce(m, r, {query:{time:{$gte:‘1450832129000‘}}, sort: {id:1}, out:"result"})
{
"result" : "result",
"timeMillis" : 50948,
"counts" : {
"input" : 490672,
"emit" : 490672,
"reduce" : 4931,
"output" : 26
},
"ok" : 1
}
>
使用這種介面,如果需要查看詳細的計算結果就需要設定mapReduce方法的out參數。
out參數的值是字串時,返回的結果中result屬性就是該字串的值,如果只需要查看統計結果,這樣也就夠了。
如果需要緩衝計算結果,就需要將out參數的值設定為{<option>:"暫存資料表名"}的json結構。mapReduce的計算結果會存在mongodb的暫存資料表中,可以通過find方法查看。
在out參數值的json結構中,<option>定義的是對暫存資料表中相同的key的處理方式,一共有三種:
replace 使用當前reduce結果替換暫存資料表中存在的結果
merge 當前reduce結果的key在暫存資料表中不存在時,將reduce結果存入暫存資料表,如果存在相同key,直接使用當前key的值替換暫存資料表中相同key的值
reduce 如果當前key在暫存資料表中存在,則對當前結果和已存在結果進行map reduce,重新調用map方法與reduce方法,將結果整合。
這裡需要注意的是,quey結果集較大時,可能出現對同一個key多次reduce的情況,這時需要設定out的整合方法為reduce,否則暫存資料表中儲存的將只是其中一次reduce的結果,而非所有記錄的reduce處理結果。在這種情況下,最好保持reduce方法的傳回值結構與map方法中emit的value結構相同,使後續mapReduce可以正常執行相同的map方法與reduce方法。
這次使用的並不是mongodb自己的js介面,而是mongoose,作為第三方mongodb外掛程式,使用起來比原生的要相對方便一些。
mongoose的model.mapReduce方法接收兩個參數:mapReduce結構設定對象、回呼函數(因為是非同步方法呼叫)
在mapReduce對象中,mapReduceObj.map指定map方法,mapReduceObj.reduce指定reduce方法,對於out參數,mongoose的mapReduce方法預設設定為{inline: 1},以js的json對象格式返回計算結果,預設對相同key的計算結果執行reduce方法,如果預期結果規模不大,就不需要像原生方法那樣再設定暫存資料表。
在map方法和reduce方法中如果需要使用外部的js變數,可以設定mapReduceObj.scope({key:外部變數,...}),在map或reduce方法中,直接使用定義好的key,就可以取到外部變數的值。
※ mapReduce的最佳化
對於處理大量資料的方法,在query後將結果集排序,盡量保證在執行map方法時相同的key連續出現,減少reduce的次數,可以顯著提升mapReduce的效率。這裡要注意,由於調用了sort,最好將key值涉及的mongodb欄位加上聯合或獨立索引,避免mongodb引擎使用預設sort方法產生錯誤。
還可以通過外部變數,過濾emit傳出的結果,只保留需要的資料,減少冗餘計算。
如果使用的語言支援,還可以考慮多線程方式執行,mapReduce方法的limit參數與skip參數,可以很方便的將結果集分塊。
另外,最近看蝴蝶書,裡邊對於遞迴的解釋讓我對mapReduce方法的實現有了些感想,mapReduce中,reduce方法使用了類似遞迴的概念,從結果看輸入,就是將輸入的資料集合根據索引值的不同,分成若干塊,對每一塊再分別調用reduce方法,逐層遞迴,直到每一塊的內容都是一條單獨的記錄,無法繼續分割為止。相同的map&reduce方法的遞迴調用。
關於mongodb的mapReduce