摘要:操作花費的時間,單位是毫秒。處理完成后,會自動將臨時集合的名字更改為你指定的集合名,這個重命名的過程是原子性的。作用域在這些函數內部是不變的。上一篇文章指南聚合下一篇文章指南聚合命令
上一篇文章:MongoDB指南---16、聚合
下一篇文章:MongoDB指南---18、聚合命令
MapReduce是聚合工具中的明星,它非常強大、非常靈活。有些問題過于復雜,無法使用聚合框架的查詢語言來表達,這時可以使用MapReduce。MapReduce使用JavaScript作為“查詢語言”,因此它能夠表達任意復雜的邏輯。然而,這種強大是有代價的:MapReduce非常慢,不應該用在實時的數據分析中。
MapReduce能夠在多臺服務器之間并行執行。它會將一個大問題分割為多個小問題,將各個小問題發送到不同的機器上,每臺機器只負責完成一部分工作。所有機器都完成時,再將這些零碎的解決方案合并為一個完整的解決方案。
MapReduce需要幾個步驟。最開始是映射(map),將操作映射到集合中的每個文檔。這個操作要么“無作為”,要么“產生一些鍵和X個值”。然后就是中間環節,稱作洗牌(shuffle),按照鍵分組,并將產生的鍵值組成列表放到對應的鍵中。化簡(reduce)則把列表中的值化簡成一個單值。這個值被返回,然后接著進行洗牌,直到每個鍵的列表只有一個值為止,這個值也就是最終結果。
下面會多舉幾個MapReduce的例子,這個工具非常強大,但也有點復雜。
用MapReduce來解決這個問題有點大材小用,不過還是一種了解其機制的不錯的方式。要是已經知道MapReduce的原理,則直接跳到本節最后,看看MongoDB中MapReduce的使用注意事項。
MongoDB會假設你的模式是動態的,所以并不跟蹤記錄每個文檔中的鍵。通常找到集合中所有文檔所有鍵的最好方式就是用MapReduce。在本例中,會記錄每個鍵出現了多少次。內嵌文檔中的鍵就不計算了,但給map函數做個簡單修改就能實現這個功能了。
在映射環節,我們希望得到集合中每個文檔的所有鍵。map函數使用特別的emit函數“返回”要處理的值。emit會給MapReduce一個鍵(類似于前面$group所使用的鍵)和一個值。這里用emit將文檔某個鍵的計數(count)返回({count : 1})。我們想為每個鍵多帶帶計數,所以為文檔中的每個鍵調用一次emit。this就是當前映射文檔的引用:
> map = function() { ... for (var key in this) { ... emit(key, {count : 1}); ... }};
這樣就有了許許多多{count : 1}文檔,每一個都與集合中的一個鍵相關。這種由一個或多個{count : 1}文檔組成的數組,會傳遞給reduce函數。reduce函數有兩個參數,一個是key,也就是emit返回的第一個值,還有另外一個數組,由一個或者多個與鍵對應的{count : 1}文檔組成。
> reduce = function(key, emits) { ... total = 0; ... for (var i in emits) { ... total += emits[i].count; ... } ... return {"count" : total}; ... }
reduce一定要能夠在之前的map階段或者前一個reduce階段的結果上反復執行。所以reduce返回的文檔必須能作為reduce的第二個參數的一個元素。例如,x鍵映射到了3個文檔{count : 1,id : 1}、{count : 1,id : 2}和{count : 1,id : 3},其中id鍵只用于區分不同的文檔。MongoDB可能會這樣調用reduce:
> r1 = reduce("x", [{count : 1, id : 1}, {count : 1, id : 2}]) {count : 2} > r2 = reduce("x", [{count : 1, id : 3}]) {count : 1} > reduce("x", [r1, r2]) {count : 3}
不能認為第二個參數總是初始文檔之一(比如{count:1})或者長度固定。reduce應該能處理emit文檔和其他reduce返回結果的各種組合。
總之,MapReduce函數可能會是下面這樣:
> mr = db.runCommand({"mapreduce" : "foo", "map" : map, "reduce" : reduce}) { "result" : "tmp.mr.mapreduce_1266787811_1", "timeMillis" : 12, "counts" : { "input" : 6 "emit" : 14 "output" : 5 }, "ok" : true }
MapReduce返回的文檔包含很多與操作有關的元信息。
"result" : "tmp.mr.mapreduce_1266787811_1"
這是存放MapReduce結果的集合名。這是個臨時集合,MapReduce的連接關閉后它就被自動刪除了。本章稍后會介紹如何指定一個好一點的名字以及將結果集合持久化。
"timeMillis" : 12
操作花費的時間,單位是毫秒。
"counts" : { ... }
這個內嵌文檔主要用作調試,其中包含3個鍵。
"input" : 6
發送到map函數的文檔個數。
"emit" : 14
在map函數中emit被調用的次數。
"output" : 5
結果集合中的文檔數量。
對結果集合進行查詢會發現原有集合的所有鍵及其計數:
···
db[mr.result].find()示例2:網頁分類
{ "_id" : "_id", "value" : { "count" : 6 } }
{ "_id" : "a", "value" : { "count" : 4 } }
{ "_id" : "b", "value" : { "count" : 2 } }
{ "_id" : "x", "value" : { "count" : 1 } }
{ "_id" : "y", "value" : { "count" : 1 } }
···
這個結果集中的每個"_id"對應原集合中的一個鍵,"value"鍵的值就是reduce的最終結果。
假設有個網站,人們可以提交其他網頁的鏈接,比如reddit(http://www.reddit.com)。提交者可以給這個鏈接添加標簽,表明主題,比如politics、geek或者icanhascheezburger。可以用MapReduce找出哪個主題最為熱門,熱門與否由最近的投票決定。
首先,建立一個map函數,發出(emit)標簽和一個基于流行度和新舊程度的值。
map = function() { for (var i in this.tags) { var recency = 1/(new Date() - this.date); var score = recency * this.score; emit(this.tags[i], {"urls" : [this.url], "score" : score}); } };
現在就化簡同一個標簽的所有值,以得到這個標簽的分數:
reduce = function(key, emits) { var total = {urls : [], score : 0} for (var i in emits) { emits[i].urls.forEach(function(url) { total.urls.push(url); } total.score += emits[i].score; } return total; };
最終的集合包含每個標簽的URL列表和表示該標簽流行程度的分數。
MongoDB和MapReduce前面兩個例子只用到了mapreduce、map和reduce鍵。這3個鍵是必需的,但是MapReduce命令還有很多可選的鍵。
"finalize" : function
可以將reduce的結果發送給這個鍵,這是整個處理過程的最后一步。
"keeptemp" : boolean
如果為值為true,那么在連接關閉時會將臨時結果集合保存下來,否則不保存。
"out" : string
輸出集合的名稱。如果設置了這選項,系統會自動設置keeptemp : true。
"query" : document
在發往map函數前,先用指定條件過濾文檔。
"sort" : document
在發往map前先給文檔排序(與limit一同使用非常有用)。
"limit" : integer
發往map函數的文檔數量的上限。
"scope" : document
可以在JavaScript代碼中使用的變量。
"verbose" : boolean
是否記錄詳細的服務器日志。
1. finalize函數和group命令一樣,MapReduce也可以使用finalize函數作為參數。它會在最后一個reduce輸出結果后執行,然后將結果存到臨時集合中。
返回體積比較大的結果集對MapReduce不是什么大不了的事情,因為它不像group那樣有4 MB的限制。然而,信息總是要傳遞出去的,通常來說,finalize是計算平均數、裁剪數組、清除多余信息的好時機。
默認情況下,Mongo會在執行MapReduce時創建一個臨時集合,集合名是系統選的一個不太常用的名字,將"mr"、執行MapReduce的集合名、時間戳以及數據庫作業ID,用“.”連成一個字符串,這就是臨時集合的名字。結果產生形如mr.stuff.18234210220.2這樣的名字。MongoDB會在調用的連接關閉時自動銷毀這個集合(也可以在用完之后手動刪除)。如果希望保存這個集合,就要將keeptemp選項指定為true。
如果要經常使用這個臨時集合,你可能想給它起個好點的名字。利用out選項(該選項接受字符串作為參數)就可以為臨時集合指定一個易讀易懂的名字。如果用了out選項,就不必指定keeptemp : true了,因為指定out選項時系統會將keeptemp設置為true。即便你取了一個非常好的名字,MongoDB也會在MapReduce的中間過程使用自動生成的集合名。處理完成后,會自動將臨時集合的名字更改為你指定的集合名,這個重命名的過程是原子性的。也就是說,如果多次對同一個集合調用MapReduce,也不會在操作中遇到集合不完整的情況。
MapReduce產生的集合就是一個普通的集合,在這個集合上執行MapReduce完全沒有問題,或者在前一個MapReduce的結果上執行MapReduce也沒有問題,如此往復直到無窮都沒問題!
有時需要對集合的一部分執行MapReduce。只需在傳給map函數前使用查詢對文檔進行過濾就好了。
每個傳遞給map函數的文檔都要先反序列化,從BSON對象轉換為JavaScript對象,這個過程非常耗時。如果事先知道只需要對集合的一部分文檔執行MapReduce,那么在map之前先對文檔進行過濾可以極大地提高map速度。可以通過"query"、"limit"和"sort"等鍵對文檔進行過濾。
"query"鍵的值是一個查詢文檔。通常查詢返回的結果會傳遞給map函數。例如,有一個做跟蹤分析的應用程序,現在我們需要上周的總結摘要,只要使用如下命令對上周的文檔執行MapReduce就好了:
> db.runCommand({"mapreduce" : "analytics", "map" : map, "reduce" : reduce, "query" : {"date" : {"$gt" : week_ago}}})
sort選項和limit一起使用時通常能夠發揮非常大的作用。limit也可以多帶帶使用,用來截取一部分文檔發送給map函數。
如果在上個例子中想分析最近10 000個頁面的訪問次數(而不是最近一周的),就可以使用limit和sort:
> db.runCommand({"mapreduce" : "analytics", "map" : map, "reduce" : reduce, "limit" : 10000, "sort" : {"date" : -1}})
query、limit、sort可以隨意組合,但是如果不使用limit的話,sort就不能有效發揮作用。
4. 使用作用域MapReduce可以為map、reduce、finalize函數都采用一種代碼類型。但多數語言里,可以指定傳遞代碼的作用域。然而MapReduce會忽略這個作用域。它有自己的作用域鍵"scope",如果想在MapReduce中使用客戶端的值,則必須使用這個參數。可以用“變量名 : 值”這樣的普通文檔來設置該選項,然后在map、reduce和finalize函數中就能使用了。作用域在這些函數內部是不變的。例如,上一節的例子使用1/(newDate() - this.date)計算頁面的新舊程度。可以將當前日期作為作用域的一部分傳遞進去:
> db.runCommand({"mapreduce" : "webpages", "map" : map, "reduce" : reduce, "scope" : {now : new Date()}})
這樣,在map函數中就能計算1/(now - this.date)了。
5. 獲得更多的輸出還有個用于調試的詳細輸出選項。如果想看看MapReduce的運行過程,可以將"verbose"指定為true。
也可以用print把map、reduce、finalize過程中的信息輸出到服務器日志上。
上一篇文章:MongoDB指南---16、聚合
下一篇文章:MongoDB指南---18、聚合命令
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/44115.html
摘要:操作花費的時間,單位是毫秒。處理完成后,會自動將臨時集合的名字更改為你指定的集合名,這個重命名的過程是原子性的。作用域在這些函數內部是不變的。上一篇文章指南聚合下一篇文章指南聚合命令 上一篇文章:MongoDB指南---16、聚合下一篇文章:MongoDB指南---18、聚合命令 MapReduce是聚合工具中的明星,它非常強大、非常靈活。有些問題過于復雜,無法使用聚合框架的查詢語言...
摘要:將返回結果限制為前個。所以,聚合的結果必須要限制在以內支持的最大響應消息大小。包含字段和排除字段的規則與常規查詢中的語法一致。改變字符大小寫的操作,只保證對羅馬字符有效。只對羅馬字符組成的字符串有效。 上一篇文章:MongoDB指南---15、特殊的索引和集合:地理空間索引、使用GridFS存儲文件下一篇文章:MongoDB指南---17、MapReduce 如果你有數據存儲在Mon...
摘要:將返回結果限制為前個。所以,聚合的結果必須要限制在以內支持的最大響應消息大小。包含字段和排除字段的規則與常規查詢中的語法一致。改變字符大小寫的操作,只保證對羅馬字符有效。只對羅馬字符組成的字符串有效。 上一篇文章:MongoDB指南---15、特殊的索引和集合:地理空間索引、使用GridFS存儲文件下一篇文章:MongoDB指南---17、MapReduce 如果你有數據存儲在Mon...
摘要:上一篇文章指南下一篇文章為在集合上執行基本的聚合任務提供了一些命令。也可以給傳遞一個查詢文檔,會計算查詢結果的數量對分頁顯示來說總數非常必要共個,目前顯示個。使用時必須指定集合和鍵。指定要進行分組的集合。 上一篇文章:MongoDB指南---17、MapReduce下一篇文章: MongoDB為在集合上執行基本的聚合任務提供了一些命令。這些命令在聚合框架出現之前就已經存在了,現在(大多...
摘要:上一篇文章指南下一篇文章為在集合上執行基本的聚合任務提供了一些命令。也可以給傳遞一個查詢文檔,會計算查詢結果的數量對分頁顯示來說總數非常必要共個,目前顯示個。使用時必須指定集合和鍵。指定要進行分組的集合。 上一篇文章:MongoDB指南---17、MapReduce下一篇文章: MongoDB為在集合上執行基本的聚合任務提供了一些命令。這些命令在聚合框架出現之前就已經存在了,現在(大多...
閱讀 1672·2021-09-26 10:00
閱讀 2935·2021-09-06 15:00
閱讀 3538·2021-09-04 16:40
閱讀 2298·2019-08-30 15:44
閱讀 715·2019-08-30 10:59
閱讀 1883·2019-08-29 18:34
閱讀 3616·2019-08-29 15:42
閱讀 2292·2019-08-29 15:36