「譯」 MapReduce in MongoDB

在這篇文章裏面,咱們會演示如何在 MongoDB 中使用 MapReduce 操做。
咱們會用 dummy-json 這個包來生成一些虛假的數據,而後用 Mongojsjavascript

若是想要快速看到結果,能夠到 這裏 裏看看。java

什麼是 MongoDB ?

MongoDB 是一個 NoSQL 數據庫,不像 MySQL 、MSSQL 和 Oracle DB 那樣,MongoDB 使用集合(collections) 來代替表(tables)。同時,它用集合中的文檔(documents)來代替表中的行(rows)。還有最好的一點是,全部文檔都保存成 JSON 格式!你能夠到這裏學更多關於 MongoDB 的知識。node

你能夠從 這裏 下載安裝 MongoDB。git

若是之前沒用過 MongoDB,那麼你能夠記住下面這些命令:github

Command Result
mongod 啓動 MongoDB 服務
mongo 進入 MongoDB Shell
show dbs 顯示全部數據庫列表
use <db name> 進入指定的數據庫
show collections 進入數據庫以後,顯示該數據庫中全部的集合
db.collectionName.find() 顯示該集合中全部文檔
db.collectionName.findOne() 顯示該集合中第一個文檔
db.collectionName.find().pretty() 顯示漂亮的 JSON 格式
db.collectionName.insert({key: value}) 插入一條新的記錄
db.collectionName.update({ condition: value}, {$set: {key: value}}, {upsert: true}) 會更新指定的文檔,設置指定的值。若是 upserttrue,當沒有找到匹配的文檔時,會建立一條新的記錄
db.collectionName.remove({}) 移除集合中的全部文檔
db.collectionName.remove({key: value}) 移除集合中匹配到的文檔

什麼是 MapReduce ?

弄清楚 MapReduce 是如何運做的是很是重要的,若是對 MapReduce 過程不瞭解的話,你在運行 MapReduce 時極可能得不到你想要的結果。web

從 mongodb.org 上的解析:mongodb

Map-reduce 是一種數據處理範例,用於將大量的數據變成有用的聚合結果。 對於 map-reduce 操做,MongoDB 提供了 mapReduce 的數據庫命令。shell

在這很是簡單的術語裏面,mapReduce 命令接受兩個基本的輸入:mapper 函數和 reducer 函數。數據庫

Mapper 是一個匹配數據的過程,它會在集合中查詢咱們想要處理的字段,而後根據咱們指定的 key 去分組,再把這些 key-value 對交給 reducer 函數,由它來處理這些匹配到的數據。npm

咱們來看看下面這些數據:

[
  { name: foo, price: 9 },
  { name: foo, price: 12 },
  { name: bar, price: 8 },
  { name: baz, price: 3 },
  { name: baz, price: 5 }
]

咱們想要計算出相同名字下的所須要的價錢。咱們將會用這個數據經過 Mapper 和 Reducer 去得到結果。

當咱們讓 Mapper 去處理上面的數據時,會生成以下的結果:

Key Value
foo [9,12]
bar [8]
baz [3,5]

看到了嗎?它用相同的 key 去分組數據。在咱們的例子中,是用 name 分組。這些結果會發送到 Reducer 中。

如今,在 reducer 中,咱們會獲得上面表格中的第一行數據,而後迭代這些數據而後把它們加起來,這就是第一行數據的總和。而後 reducer 會對第二行數據作一樣的事情,直到全部行被處理完。

最終的輸出結果以下:

Name Total
foo 21
bar 8
baz 8

如今你明白爲何 Mapper 會叫 Mapper 了吧 ! (由於它會建立一份數據的映射)
也明白了爲何 Reducer 會叫 Reducer 了吧 ! (由於它會把 Mapper 生成的數據概括成一個簡單的形式)

若是你運行一些例子,你就會知道它是怎麼工做的拉。你也能夠從官方文檔 中瞭解更多細節。

建立一個項目

正如上文所說,咱們能夠在 mongo shell 中直接查詢和看到輸出結果。可是,爲了讓教程更加豐富,咱們會構建一個 Nodejs 項目,在裏面運行咱們以前的任務。

Mongojs

咱們會用 mongojs 去實現咱們的 MapReduce。你能夠用一樣的代碼跑在 mongo shell 裏面,會看到一樣的結果。

Dummy-json

咱們會用 dummy-json 去建立一些虛假的數據。你能夠在 這裏 找到更多的信息。而後咱們會在這些虛假數據上面運行 MapReduce 命令,生成一些有意義的結果。

咱們開始吧!

首先,你要安裝 Nodejs,你能夠看看 這裏。而後你要建立一個叫 mongoDBMapReduce 的目錄。咱們將會建立 package.json 文件來保存項目的詳細信息。

運行 npm init 而後填入你喜歡的東西,建立完 package.json 後,咱們要添加項目的依賴。
運行 npm i mongojs dummy-json --save-dev ,而後等幾分鐘以後,咱們項目的依賴就安裝好了。

生成虛假數據

下一步,咱們要用 dummy-json 模塊來生成虛假數據。
在項目的根目錄建立一個名叫 dataGen.js 的文件,咱們會把數據生成的邏輯保存到一個獨立的文件裏面。若是之後須要添加更多的數據,你能夠運行這個文件。

把下面的內容複製到 dataGen.js 裏面:

var mongojs = require('mongojs');
var db = mongojs('mapReduceDB', ['sourceData']);
var fs = require('fs');
var dummyjson = require('dummy-json');
 
var helpers = {
  gender: function() {
    return ""+ Math.random() > 0.5 ? 'male' : 'female';
  },
  dob : function() {
    var start = new Date(1900, 0, 1),
        end = new Date();
        return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
    },
  hobbies : function () {
    var hobbysList = []; 
    hobbysList[0] = [];
    hobbysList[0][0] = ["Acrobatics", "Meditation", "Music"];
    hobbysList[0][1] = ["Acrobatics", "Photography", "Papier-Mache"];
    hobbysList[0][2] = [ "Papier-Mache"];
    return hobbysList[0][Math.floor(Math.random() * hobbysList[0].length)];
  }
};
 
console.log("Begin Parsing >>");
 
var template = fs.readFileSync('schema.hbs', {encoding: 'utf8'});
var result = dummyjson.parse(template, {helpers: helpers});
 
console.log("Begin Database Insert >>");
 
db.sourceData.remove(function (argument) {
    console.log("DB Cleanup Completd");
});
 
db.sourceData.insert(JSON.parse(result), function (err, docs) {
    console.log("DB Insert Completed");
});

第1-4行,咱們引入了全部依賴。
第2行,咱們建立了一個叫 mapReduceDB 的數據庫。在數據庫裏面,建立了一個叫 sourceData 的集合。

第6-23行,是 Handlebar 的 helper。你能夠到 dummy-json 中瞭解更多信息。

第27-28行,咱們讀取了 schema.hbs 文件 (咱們接着會建立這個文件),而後把它解析成 JSON。

第32行,在插入新數據以前,咱們要先把舊數據清除掉。若是你想保留舊數據,把這部分註釋掉就行了。

第36行,把生成的數據插入數據庫。

接着,咱們要在項目根目錄建立一個叫 schema.hbs 的文件。這裏面會包括 JSON 文檔的結構。把下面的內容複製到文件裏面:

[
    {{#repeat 9999}}
    {
      "id": {{index}},
      "name": "{{firstName}} {{lastName}}",
      "email": "{{email}}",
      "work": "{{company}}",
      "dob" : "{{dob}}",
      "age": {{number 1 99}},
      "gender" : "{{gender}}",
      "salary" : {{number 999 99999}},
      "hobbies" : "{{hobbies}}"
    }
    {{/repeat}}
]

注意 第2行,咱們會生成 9999 個文檔。

打開一個新的終端,運行 mongod,啓動 MongoDB 服務。而後回到原來的終端,運行 node dataGen.js

若是一切正常,會顯示以下結果:

$ node dataGen.js
Begin Parsing >>
Begin Database Insert >>
DB Cleanup Completed
DB Insert Completed

而後按 ctrl + c 殺掉 Node 程序。要驗證是否插入成功,咱們能夠打開一個新的終端,運行 mongo 命令進入 mongo shell。

> use mapReduceDB
> db.sourceData.findOne()
{
    "id": 0,
    "name": "Leanne Flinn",
    "email": "leanne.flinn@unilogic.com",
    "work": "Unilogic",
    "dob": "Sun Mar 14 1909 12:45:53 GTM+0530 (LST)",
    "age": 27,
    "gender": "male",
    "salary": 16660,
    "hobbies": "Acrobatics,Photography,Papier-Mache",
    "_id": Object("57579f702fa6c7651e504fe2")
}
> db.sourceData.count()
9999

有意義的數據

如今咱們有 9999 個虛假用戶的數據,讓咱們試着把數據變得有意義

例子1:計算男女數量

首先,在項目根目錄建立一個 example1.js 的文件,咱們要進行 MapReduce 操做,去計算男女的數量。

Mapper 的邏輯

咱們只須要讓 Mapper 以性別做爲 key,把值做爲 1。由於一個用戶不是男就是女。因此,Mapper 的輸出會是下面這樣:

Key Value
Male [1,1,1...]
Female [1,1,1,1,1...]

Reducer 的邏輯

在 Reducer 中,咱們會得到上面兩行數據,咱們要作的是把每一行中的值求和,表示該性別的總數。最終的輸出結果以下:

Key Value
Male 5031
Female 4968

代碼

好了,如今咱們能夠寫代碼去實現了。在 example1.js 中,咱們要先引入所須要的依賴。

var mongojs = require('mongojs');
var db = mongojs('mapReduceDB', ['sourceData', 'example1_results']);

注意 第2行,第一個參數是數據庫的名字,第二個參數表示集合的數組。example1_results 集合用來保存結果。

接下來,咱們加上 mapper 和 reducer 函數:

var mapper = function () {
    emit(this.gender, 1);
};
 
var reducer = function(gender, count){
    return Array.sum(count);
};

第2行中, this 表示當前的文檔,所以 this.gender 會做爲 mapper 的 key,它的值要麼是 male,要麼是 female。而 emit() 將會把數據發送到一個臨時保存數據的地方,做爲 mapper 的結果。

第5行中,咱們簡單地把每一個性別的全部值加起來。

最後,加上執行邏輯:

db.sourceData.mapReduce(
    mapper,
    reducer,
    {
        out : "example1_results"
    }
 );
 
 db.example1_results.find(function (err, docs) {
    if(err) console.log(err);
    console.log(docs);
 });

第5行中,咱們設置了輸出的集合名。
第9行中,咱們會從 example1_results 集合取得結果並顯示它。

咱們能夠在終端運行試試:

$ node example1.js
[ { _id: 'female', value: 4968 }, { _id: 'male': value: 5031 } ]

個人數量可能和你的不同,但男女總數應該是 9999 !

Mongo Shell 代碼

若是你想在 mongo shell 中運行上面的例子,你能夠粘貼下面這些代碼到終端裏面:

mapper = function () {
    emit(this.gender, 1);
};
 
reducer = function(gender, count){
    return Array.sum(count);
};
 
db.sourceData.mapReduce(
    mapper,
    reducer,
    {
        out : "example1_results"
    }
 );
 
 db.example1_results.find()

而後你就會看到同樣的結果,很簡單吧!

例子2:獲取每一個性別中最老和最年輕的人

在項目根目錄建立一個 example2.js 的文件。在這裏,咱們要把全部用戶根據性別分組,而後分別找每一個性別中最老和最年輕的用戶。這個例子比前面的稍微複雜一點。

Mapper 的邏輯

在 mapper 中,咱們要以性別做爲 key,而後以 object 做爲 value。這個 object 要包含用戶的年齡和名字。年齡是用來作計算用的,而名字只是用來顯示給人看的。

Key Value
Male [{age: 9, name: 'John'}, ...]
Female [{age: 19, name: 'Rita'}, ...]

Reducer 的邏輯

咱們的 reducer 會比前一個例子要複雜一點。咱們要檢查全部和性別相關的年齡,找到年齡最大和最小的用戶。最終的輸出結果是這樣的:

Key Value
Male {min: {name: 'harry', age: 1}, max: {name: 'Alex', age: 99} }
Female {min: {name: 'Loli', age: 10}, max: {name: 'Mary', age: 98} }

代碼

如今打開 example2.js,粘貼下面的內容進去:

var mongojs = require('mongojs');
var db = mongojs('mapReduceDB', ['sourceData', 'example2_results']);
 
 
var mapper = function () {
    var x = {age : this.age, name : this.name};
    emit(this.gender, {min : x , max : x});
};
 
 
var reducer = function(key, values){
    var res = values[0];
    for (var i = 1; i < values.length; i++) {
        if(values[i].min.age < res.min.age)
            res.min = {name : values[i].min.name, age : values[i].min.age};
        if (values[i].max.age > res.max.age) 
           res.max = {name : values[i].max.name, age : values[i].max.age};
    };
    return res;
};
 
 
db.sourceData.mapReduce(
    mapper,
    reducer,
    {
        out : "example2_results"
    }
 );
 
 db.example2_results.find(function (err, docs) {
    if(err) console.log(err);
    console.log(JSON.stringify(docs));
 });

第6行,咱們構建了一個 object,把它做爲 value 發送。
第13-18行,咱們迭代了全部 object,檢查當前的 object 的年齡是否大於或小於前一個 object 的年齡,若是是,就會更新 res.max 或者 res.min
在第第27行,咱們把結果輸出到 example2_results 中。

咱們能夠運行一下這個例子:

$ node example2.js
[ { _id: 'female', value: { min: [Object], max: [Object] } },
  { _id: 'male', value: { min: [Object], max: [Object] } } ]

例子3:計算每種興趣愛好的人數

在咱們最後的例子中,咱們會看看有多少用戶有相同的興趣愛好。咱們在項目根目錄建立一個叫 example3.js 的文件。用戶數據長這樣子:

{
    "id": 0,
    "name": "Leanne Flinn",
    "email": "leanne.flinn@unilogic.com",
    "work": "Unilogic",
    "dob": "Sun Mar 14 1909 12:45:53 GTM+0530 (LST)",
    "age": 27,
    "gender": "male",
    "salary": 16660,
    "hobbies": "Acrobatics,Photography,Papier-Mache",
    "_id": Object("57579f702fa6c7651e504fe2")
}

如你所見,每一個用戶的興趣愛好列表都用逗號分隔。咱們會找出有多少用戶有表演雜技的愛好等等。

Mapper 的邏輯

在這個場景下,咱們的 mapper 會複雜一點。咱們要爲每一個用戶的興趣愛好發送一個新的 key-value 對。這樣,每一個用戶的每一個興趣愛好都會觸發一次計算。最終咱們會獲得以下的結果:

Key Value
Acrobatics [1,1,1,1,1,1,….]
Meditation [1,1,1,1,1,1,….]
Music [1,1,1,1,1,1,….]
Photography [1,1,1,1,1,1,….]
Papier-Mache [1,1,1,1,1,1,….]

Reducer 的邏輯

在這裏,咱們只要簡單地爲每種興趣愛好求和就行了。最終咱們會獲得下面的結果:

Key Value
Acrobatics 6641
Meditation 3338
Music 3338
Photography 3303
Papier-Mache 6661

代碼

var mongojs = require('mongojs');
var db = mongojs('mapReduceDB', ['sourceData', 'example3_results']);
 
 
var mapper = function () {
     var hobbys = this.hobbies.split(',');
      for (i in hobbys) {
        emit(hobbys[i], 1);
    }
};
 
var reducer = function (key, values) {
    var count = 0;
    for (index in values) {
        count += values[index];
    }
 
    return count;
};
 
 
db.sourceData.mapReduce(
    mapper,
    reducer,
    {
        out : "example3_results"
    }
 );
 
 db.example3_results.find(function (err, docs) {
    if(err) console.log(err);
    console.log(docs);
 });

注意第7-9行,咱們迭代了每一個興趣愛好,而後發送了一次記數。
第13-18行能夠用 Array.sum(values) 來代替,這樣是另一種作相同事情的方式。最終咱們獲得的結果:

$ node example3.js
[ { _id: 'Acrobatics', value: 6641 },
  { _id: 'Meditation', value: 3338 },
  { _id: 'Music', value: 3338 },
  { _id: 'Photography', value: 6661 },
  { _id: 'Papier-Mache', value: 3303 } ]

這就是 MongoDB 中運行 MapReduce 的方法了。但要記住,有時候一個簡單的查詢就能完成你想要的事情的。

出處

http://scarletsky.github.io/2016/06/12/mapreduce-in-mongodb/

參考資料

MapReduce in MongoDB

相關文章
相關標籤/搜索