使用MongoDB的MapReduce進(jìn)行查詢(xún)和數(shù)據(jù)組織

Date: 2016-03-09
Summary: 在實(shí)際應(yīng)用中,用MongoDB做了數(shù)據(jù)庫(kù)查詢(xún)之后返回的數(shù)據(jù)量很大琳拨,想要做一些比較復(fù)雜的統(tǒng)計(jì)和聚合操作做花費(fèi)的時(shí)間超過(guò)了數(shù)據(jù)庫(kù)操作耗時(shí),嘗試用MongoDB中的MapReduce機(jī)制進(jìn)行了實(shí)現(xiàn)缚陷,速度提升還是很明顯的祥国!

使用MongoDB的MapReduce進(jìn)行查詢(xún)和數(shù)據(jù)組織

MapReduce是MongoDB中最復(fù)雜的查詢(xún)機(jī)制之一。它通過(guò)兩個(gè)JavaScript函數(shù)實(shí)現(xiàn)查詢(xún):map和reduce碳抄。這兩個(gè)函數(shù)完全由用戶(hù)自定義愉老,并且能夠提供及其強(qiáng)大的靈活性!實(shí)際應(yīng)用中遇到的問(wèn)題無(wú)法直接使用MongoDB的聚合框架實(shí)現(xiàn)剖效,才考慮到使用MapReduce嫉入。下面將通過(guò)一個(gè)簡(jiǎn)短的例子演示如何使用MapReduce焰盗。該例子的原型來(lái)自于實(shí)際的問(wèn)題,由于數(shù)據(jù)的保密性咒林,我采用MongoDB官方對(duì)外發(fā)布的Zip Code數(shù)據(jù)集進(jìn)行舉例熬拒。

問(wèn)題描述

目前的流程是這樣的,首先按照一定的條件對(duì)collection進(jìn)行查詢(xún)垫竞,在查詢(xún)結(jié)果中想要統(tǒng)計(jì)每一個(gè)key的loc屬性(地理經(jīng)緯度)澎粟,并把所有的loc加入到一個(gè)數(shù)組,然后返回欢瞪。剛開(kāi)始統(tǒng)計(jì)的部分是在程序中進(jìn)行的活烙,由于數(shù)據(jù)量很大,查詢(xún)的部分并不慢遣鼓,主要是統(tǒng)計(jì)的部分形成了瓶頸啸盏。想到用MongoDB的聚合框架來(lái)解決問(wèn)題,研究了一下骑祟,發(fā)現(xiàn)功能有限回懦。看到了MapReduce非常靈活次企,感覺(jué)肯定可以解決粉怕,所以決定嘗試一下,最后比較一下性能有多大的提升抒巢。

數(shù)據(jù)集導(dǎo)入

首先下載zips.json文件贫贝,放到當(dāng)前目錄下,然后使用mongoimport工具進(jìn)行導(dǎo)入:

$mongoimport -d test -c zipcodes < zips.json

然后看下是否已經(jīng)導(dǎo)入成功了:

use test
db.zipcodes.find()

有數(shù)據(jù)則導(dǎo)入成功蛉谜!

{ "_id" : "01002", "city" : "CUSHMAN", "loc" : [ -72.51565, 42.377017 ], "pop" : 36963, "state" : "MA" }
{ "_id" : "01001", "city" : "AGAWAM", "loc" : [ -72.622739, 42.070206 ], "pop" : 15338, "state" : "MA" }
{ "_id" : "01005", "city" : "BARRE", "loc" : [ -72.108354, 42.409698 ], "pop" : 4546, "state" : "MA" }
{ "_id" : "01007", "city" : "BELCHERTOWN", "loc" : [ -72.410953, 42.275103 ], "pop" : 10579, "state" : "MA" }
{ "_id" : "01008", "city" : "BLANDFORD", "loc" : [ -72.936114, 42.182949 ], "pop" : 1240, "state" : "MA" }

下面我們就以state為key稚晚,來(lái)返回collection中全部loc的數(shù)組。

解決方法1:使用傳遞字符串的方法

根據(jù)參考3頁(yè)面的問(wèn)題回復(fù)來(lái)看型诚,"Currently, the return value from a reduce function cannot be an array (it's typically an object or a number)." ****目前reduce函數(shù)的返回值還不能是數(shù)組客燕!可以是數(shù)字和對(duì)象!如果直接返回?cái)?shù)組會(huì)報(bào)錯(cuò):"reduce -> multiple not supported yet"狰贯,參考3中介紹了這個(gè)錯(cuò)誤的解決方法也搓。下面的一位大神提出了一個(gè)最簡(jiǎn)單的方法:把返回值轉(zhuǎn)換成一個(gè)字符串返回不就行了。js中的字符串也是對(duì)象啊涵紊。這樣的話(huà)傍妒,在外面再對(duì)字符串進(jìn)行解析,也是一個(gè)不錯(cuò)的方法摸柄。那么就先來(lái)試一下:

//solution 1 使用傳字符串的方法
var map = function() {
    emit(this.state, this.loc);
};

var reduce = function(key, values) {
    //print(values);
    return values.toString();
};

db.zipcodes.mapReduce(
    map,
    reduce,
    {
        query:{state:"NY"},
        out:{inline:1}
    }
)

在map函數(shù)中把state作為key颤练,loc作為value發(fā)送到reduce函數(shù)。reduce函數(shù)中把接收到的每個(gè)key的匯總值直接轉(zhuǎn)成字符串返回驱负。在控制臺(tái)中的運(yùn)行結(jié)果如下:

{
"results" : [
    {
        "_id" : "NY",
        "value" : "-72.017834,41.263934,-73.996705,40.74838,-73.987681,40.715231, 
        .......
        -73.981328,40.737476,-73.99963,40.740225,-78.242958,43.332563"
    }
],
"timeMillis" : 121,
"counts" : {
    "input" : 1595,
    "emit" : 1595,
    "reduce" : 16,
    "output" : 1
},
"ok" : 1
}

數(shù)據(jù)太長(zhǎng)了只能省略了中間的部分嗦玖,從結(jié)果來(lái)看這種方法是可行的患雇!

對(duì)了,代碼中注釋掉的print那一行是用來(lái)調(diào)試mapreduce的輸出信息的宇挫,它會(huì)把結(jié)果輸出到mongodb的日志文件中苛吱,打開(kāi)日志文件就能看到,這樣很方便器瘪,更容易理解這個(gè)過(guò)程翠储。具體可以參考4,5娱局,6進(jìn)行了解和學(xué)習(xí)彰亥。

解決方法2:使用傳遞對(duì)象的方法

下面使用傳遞對(duì)象的方法來(lái)返回我們需要的數(shù)組咧七,簡(jiǎn)單起見(jiàn)衰齐,我們先把每個(gè)state的pop裝到一個(gè)數(shù)組里。具體實(shí)現(xiàn)代碼如下:

//solution 2 : population example 傳對(duì)象的方法傳pop數(shù)組
var map = function() {
    emit(this.state, {p:[this.pop]});
};

var reduce = function(key, values) {
    var ret = {p:[]};
    for(var i = 0; i < values.length; i++){
        ret.p.push(values[i].p[0]);
    }
    return ret;
};

db.zipcodes.mapReduce(
    map,
    reduce,
    {
        query:{state:"NY"},
        out:"NYpop"
    }
)

這里的有幾點(diǎn)是需要注意的继阻,最重要的一點(diǎn)就是:The value from the map function needs to have the same shape as the return from reduce. 所以map函數(shù)中value是什么樣耻涛,reduce函數(shù)中的返回值就要是什么樣!不然會(huì)有很多問(wèn)題瘟檩,返回是null或者undefined都是很常見(jiàn)的抹缕。還有一點(diǎn)就是輸出模式out和上面的例子不一樣,這種是輸出成了一個(gè)collection墨辛,上面是直接輸出在控制臺(tái)了卓研,自己體會(huì)一下就好。輸出結(jié)果如下:

{
    "result" : "NYpop",
    "timeMillis" : 178,
    "counts" : {
        "input" : 1595,
        "emit" : 1595,
        "reduce" : 16,
        "output" : 1
    },
    "ok" : 1
}

好的睹簇,下面實(shí)現(xiàn)最后一步奏赘,就要達(dá)到目標(biāo)了。我們要把每個(gè)state的loc裝到一個(gè)數(shù)組里太惠,loc本身由經(jīng)緯度兩個(gè)字段組成磨淌,也是一個(gè)數(shù)組,也就是說(shuō)凿渊,返回的結(jié)果是數(shù)組的數(shù)組梁只。具體mapreduce函數(shù)如下:

//solution 3 統(tǒng)計(jì)loc
var map = function() {
    emit(this.state, {locs:[this.loc]});
};

var reduce = function(key, values) {
    var ret = {locs:[]}
    for(var i = 0; i < values.length; i++){
        ret.locs.push(values[i].locs[0]);
    }
    return ret;    
};

db.zipcodes.mapReduce(
    map,
    reduce,
    {
        query:{state:"NY"},
        out:{inline:1}
    }
)

輸出結(jié)果如下:

{
    "results" : [
        {
            "_id" : "NEW YORK",
            "value" : {
                "locs" : [
                    [
                        -73.996705,
                        40.74838
                    ],
                    [
                        -73.987681,
                        40.715231
                    ],
                    ......
                    [
                        -74.016323,
                        40.710537
                    ]
                ]
            }
        }
    ],
    "timeMillis" : 77,
    "counts" : {
        "input" : 40,
        "emit" : 40,
        "reduce" : 1,
        "output" : 1
    },
    "ok" : 1
}

整個(gè)流程走通還是不太容易的,遇到了很多問(wèn)題埃脏,其中一個(gè)就是嵌套數(shù)組的問(wèn)題搪锣,參考7中解釋了嵌套數(shù)組的形成原因,并給出了解決方法彩掐,已經(jīng)加到代碼中淤翔。

參考

  1. MongoDB官方文檔https://docs.mongodb.org/manual/core/map-reduce/ 解釋很詳細(xì),圖片到位佩谷,簡(jiǎn)單易懂
  2. http://thejackalofjavascript.com/mapreduce-in-mongodb/ MapReduce使用的例子
  3. http://stackoverflow.com/questions/8175015/mongodb-mapreduce-reduce-multiple-not-supported-yethttp://stackoverflow.com/questions/8175015/mongodb-mapreduce-reduce-multiple-not-supported-yet
  4. http://stackoverflow.com/questions/13963483/how-to-get-print-output-for-debugging-map-reduce-in-mongoid
  5. http://www.cnblogs.com/yuechaotian/archive/2013/02/26/2933455.html
  6. http://stackoverflow.com/questions/7527126/mongodb-how-to-debug-map-reduce-on-mongodb-shell
  7. http://stackoverflow.com/questions/8099991/rejoining-split-mapreduce-arrays-in-mongohttp://stackoverflow.com/questions/8099991/rejoining-split-mapreduce-arrays-in-mongo
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旁壮,一起剝皮案震驚了整個(gè)濱河市监嗜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌抡谐,老刑警劉巖裁奇,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異麦撵,居然都是意外死亡刽肠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)免胃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)音五,“玉大人,你說(shuō)我怎么就攤上這事羔沙√衫裕” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵扼雏,是天一觀的道長(zhǎng)坚嗜。 經(jīng)常有香客問(wèn)我,道長(zhǎng)诗充,這世上最難降的妖魔是什么苍蔬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮蝴蜓,結(jié)果婚禮上碟绑,老公的妹妹穿的比我還像新娘。我一直安慰自己茎匠,他們只是感情好格仲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著汽抚,像睡著了一般抓狭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上造烁,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天否过,我揣著相機(jī)與錄音,去河邊找鬼惭蟋。 笑死苗桂,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的告组。 我是一名探鬼主播煤伟,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了便锨?” 一聲冷哼從身側(cè)響起围辙,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎放案,沒(méi)想到半個(gè)月后姚建,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吱殉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年掸冤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片友雳。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稿湿,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出押赊,到底是詐尸還是另有隱情饺藤,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布考杉,位于F島的核電站策精,受9級(jí)特大地震影響舰始,放射性物質(zhì)發(fā)生泄漏崇棠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一丸卷、第九天 我趴在偏房一處隱蔽的房頂上張望枕稀。 院中可真熱鬧,春花似錦谜嫉、人聲如沸萎坷。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)哆档。三九已至,卻和暖如春住闯,著一層夾襖步出監(jiān)牢的瞬間瓜浸,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工比原, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留插佛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓量窘,卻偏偏與公主長(zhǎng)得像雇寇,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容