首先我們來講講Map-Reduce原理
Map-Reduce基本原理請見下圖:
整個資料處理流程可以參見官方上圖,先對要進行處理的資料進行Query,然後針對Query的資料進行map,最後針對map的資料進行reduce.
簡單瞭解之後,我們這裡取一個例子熟悉下整個過程:
資料基本格式為:
/* 0 */
{
"code" : "A",
"uid" : "id_1",
"count" : 1
}
/* 1 */
{
"code" : "A",
"uid" : "id_1",
"count" : 1
}
/* 2 */
{
"code" : "B",
"uid" : "id_1",
"count" : 1
}
/* 3 */
{
"code" : "B",
"uid" : "id_2",
"count" : 2
}
目的:根據uid計算出count的和 且 這個合涉及到哪些code.
很快就可以寫出Map和Reduce函數:
var map = function() {
emit(this.uid, {"code":this.code || "", count:this.count || 1});
};
var reduce = function(key, values) {
var result = {code:{}, count:0};
values.forEach(function(val) {
result.code[val.code] = 1;
result.count += val.count;
});
return result;
}
`
結果為:
/* 0 */
{
"_id" : "id_1",
"value" : {
"code" : {
"A" : 1,
"B" : 1
},
"count" : 3
}
}
/* 1 */
{
"_id" : "id_2",
"value" : {
"code" : "B",
"count" : 2
}
}
這次我省去了query的過程,直接進行Map和Reduce,我們來拆解下過程:
首先,MongoDB會掃描整個資料表(這裡省去Query)遍曆所有documents,對於每個docuemnt都會根據key(uid)進行map儲存.
其次,這個時候MongoDB會對記錄的size進行檢查( mongod checks every 100 records that the size of the map is not over 50KB, if so it runs reduce on ALL current keys. If size of map is still over 100KB, it dumps all current documents to disk in an “incremental” collection.)
最後根據map的資料進行reduce操作.
好,上面三點是大概的過程,對於Mapping過程,上面執行個體中會進行
{"id_1", values:[{"code":"A", "count":1}, {"code":"A", "count":1}, {"code":"B", "count":1}}
這樣的Emit操作.這點是需要注意的.然後以這樣得方式傳入到Reduce進行處理,所以Reduce必須對values進行forEach處理.
通過上面這個過程,還有一點要非常注意:如果有很多文檔,而且這些文檔的分布是非常隨機的,當記憶體比較小時,MongoDB會採取把這些資料存在一個inc自增的文檔中.
比方說:我有A, B, C三個key, 每個key有100個, 但是這些key都是隨機分布的 比如A…B…A…B..C…A..B..C..當我要先對A進行Emit時需要把所有是A的key的document擷取出來,那麼這個過程當記憶體很小時 需要把大部分得document儲存到磁碟上.然後記憶體和磁碟一直交換資料,至到把A全部找出為止(期間每在記憶體中操作的部分A會先Emit出去).
這種操作肯定很耗時, 如果我們對key進行索引且排好序,那麼排好序的A就會大部分在記憶體中,減少了記憶體和磁碟的切換次數.
所以對大資料加排序是必須要有的.這個細節至少可以提高很多倍得處理速度.