MongoDB MapReduce教程

本文是我们学院课程中名为MongoDB –可扩展NoSQL DB的一部分

在本课程中,您将被介绍到MongoDB。 您将学习如何安装它以及如何通过它的外壳进行操作。 此外,您还将学习如何通过Java以编程方式访问它以及如何将Map Reduce与其一起使用。 最后,将解释更高级的概念,例如分片和复制。 在这里查看

1.简介

最初由Google推广的Map / Reduce范式(对于好奇的读者,这是到原始论文链接 )如今已经受到了广泛的关注,这主要是由于大数据运动。 许多NoSQL解决方案旨在支持与Map / Reduce框架的集成,但是MongoDB远远超出了此范围,并将其自己的Map / Reduce实现集成到MongoDB服务器中,供所有人使用。

2.映射/缩小一览

Map / Reduce是一个框架,它允许跨许多物理或虚拟服务器并行处理大型和超大型数据集。 典型的Map / Reduce程序包括两个阶段:

  • 映射阶段:过滤/转换/转换数据
  • 减少阶段:对数据执行聚合

在某种程度上, mapreduce阶段的灵感来自于mapreduce ,这是函数编程领域中广泛使用并广为人知的高级函数 。 就像名称Map / Reduce所暗示的那样, 映射作业始终在缩小作业之前执行。 请注意,现代的数据处理方法比前面介绍的方法更为复杂,但是原理保持不变。

从实现的前景看,大多数Map / Reduce框架都在元组上运行。 映射实现接受一组数据并将其转换为另一组数据,通常为元组 (键/值对)。 因此, reduce实现接受map实现的输出作为其输入,并将那些组合并(减少)为较小的(聚合的) 元组集,最终成为最终结果。

让我们回顾一下在第3部分中看到的书店示例。MongoDB和Java教程,并尝试对其应用Map / Reduce范例,以获取每个作者发表了多少本书的汇总结果。 map函数只是遍历每个文档的authors属性,并且对于每个作者都发出 (很常见的术语,指代新输出的生成)键/值元组(author,1)

function map(document):
  for each author in document.authors:
    emit( author, 1 )

reduce函数接受键和值的集合(取自元组),对其进行汇总并发出 (再次输出新的)新元组,其中每个作者均拥有其已出版的书的总数。

function reduce(author, values):
  sum = 0
  for each value in values:
    sum += value
  emit( author, sum )
图1.分阶段映射/缩小示例。

图片1 。 分阶段映射/缩小示例。

它看起来很简单,似乎并没有带来很多价值。 但是这里的简单性是一个关键,它可以将大问题分解为较小的部分,并在数百或数千个服务器(节点)之间分布计算:大规模并行数据处理。

3.在MongoDB中映射/减少

MongoDB提供单个命令mapReduce (以及相应的MongoDB Shell包装器db.<collection>.mapReduce() ),以在整个文档集合中运行Map / Reduce聚合。 该命令支持许多不同的参数,在本节中,我们将逐步介绍所有这些参数。

命令 mapReduce
参量
{
    mapReduce: <collection>,
    map: <function>,
    reduce: <function>,
    out: <output>,
    query: <document>,
    sort: <document>,
    limit: <number>,
    finalize: <function>,
    scope: <document>,
    jsMode: <true|false>,
    verbose: <true|false>
}
包装纸
db.<collection>.mapReduce(
    map,
    reduce, {
        out: <collection>,
        query: <document>,
        sort: <document>,
        limit: <number>,
        finalize: <function>,
        scope: <document>,
        jsMode: <true|false>,
        verbose: <true|false>
} )
描述 该命令允许对collection <collection>的文档运行map / reduce聚合操作。
参考 http://docs.mongodb.org/manual/reference/command/mapReduce/ http://docs.mongodb.org/manual/reference/method/db.collection.mapReduce/

表格1

MapReduce的命令在单输入集合(其可被分片 ),并且可以产生单一的输出集合,其也可以被分片操作。 在将数据输入到map函数之前,该命令允许对输入集合进行任意排序和限制。

分类 可选文档,可以指定输入文档的顺序。 将排序键指定为与发射键相同可以减少reduce操作的调用。 排序键必须在相关集合的现有索引中。
限制 可选参数,指定要从集合(或作为查询结果)返回的最大文档数。
询问 可选文档,用于指定匹配条件(使用第2部分中描述的全套查询操作符。MongoDB Shell指南–操作和命令 ),这些条件应作为输入发送到map函数。
冗长的 可选参数,允许在结果中包括命令执行的时间信息。 默认情况下, 详细信息的值设置为true ,并且计时信息包含在结果中(如下面的示例所示)。

表2

MongoDB中的mapreduce函数是JavaScript函数,并在MongoDB服务器进程中运行。 map函数将单个集合的文档作为输入,并应用自定义JavaScript函数以发出新的输出。

范围 一个可选文档,它指定可在mapreducefinalize函数中访问的全局变量。
地图 一个JavaScript函数,它接受文档作为输入并发出新的键/值元组。 它具有以下原型定义:
function() {
   // do something, this references the current document
   emit(key, value);
}

此函数具有以下上下文和约束:

  • 在函数中, 引用当前文档
  • 在函数中,实现不应尝试访问任何数据库
  • 实现应无副作用(纯函数)
  • 实现可以引用scope参数中定义的变量

实现可能会调用emit函数0次或更多次,具体取决于map函数预期产生的输出类型。

降低 一个JavaScript函数,它接受键和值,并返回此特定键的汇总结果(值中的每个值均符合map函数的输出)。 它具有以下原型定义:
function(key, values) {
    // do some aggregations here
    return result;
}

此函数具有以下上下文和约束:

  • 在函数中,实现不应尝试访问任何数据库
  • 实现应无副作用(纯函数)
  • 实现可以引用scope参数中定义的变量
  • 仅具有单个值的键不会调用该函数

还需要记住reduce函数的一个非常重要的方面:对于同一个键,它可能被多次调用。 在这种情况下,该键的先前调用的结果将成为同一键的下一次调用的输入值之一。 由于这种行为,应以满足以下约束的方式设计reduce函数的实现:

  • 结果值的类型必须与map函数发出的值的类型相同
  • values参数中元素的顺序不应影响函数的结果
  • 最后但并非最不重要的一点是,该函数应该是幂等的 (可以多次应用,而不会在初始应用程序之外更改结果): reduce( key, reduce( key, values ) ) == reduce( key, values )
完成 可选JavaScript函数,它接受键和缩减值( reduce函数调用的结果)并返回聚合的最终结果。 它具有以下原型定义:
function(key, value) {
    // do some final aggregations here
    return result;
}

并且,类似于mapreduce ,此函数具有以下上下文和约束:

  • 在功能内,实现不应尝试访问任何数据库
  • 实现应无副作用(纯函数)
  • 实现可以引用scope参数中定义的变量
jsMode 一个可选参数,指定在执行mapreduce函数之间是否应将中间数据转换为BSON格式 。 提供时具有以下含义:
  • 如果设置为false (默认值):

map函数发出JavaScript对象将转换为BSON对象。 调用reduce函数时,这些BSON对象将转换回JavaScript对象。 映射/减少操作将中间BSON对象放置在磁盘上的临时存储中,这允许在任意大的数据集(可能不适合内存)上执行。

  • 如果设置为true

map函数发出JavaScript对象将保留为JavaScript对象,这可以导致执行速度更快。 但是,结果集限制为通过emit(key, value)函数调用传递的500,000个不同的键参数。

表3

MapReduce的命令在一种方式,它是一个使用分片集合(请参阅运行第4部分的MongoDB拆分指南详细介绍)作为输入,mongos进程将自动调度地图/减少命令每个碎片平行和将等待所有分片上的作业完成。 因此,如果MapReduce的命令的方式,它是使用分片集合作为输出,运行MongoDB的使用_id字段作为片键碎片的输出集合。

一个简单的字符串或文档,概述了map / reduce操作的结果的位置。 存在三种可能的输出方案:
  • 输出到集合(仅在副本集的 主要成员或独立实例上执行时)
out: <collection>
  • 输出到具有操作的集合(在仅对副本集的 主要成员或独立实例执行时)
out: {
    <action>: <collection>,
    db: <db>,
    sharded: <true|false>,
    nonAtomic: <true|false>
}

收集参数指定输出收集名称,而操作参数可以具有以下值之一(它规定了在收集已经存在的情况下如何解决冲突):

  • replace :替换集合的内容
  • merge :将新结果与现有集合合并(如果已经存在具有相同键的文档,它将被覆盖)
  • reduce :将新结果与现有集合合并(如果已经存在具有相同键的文档,则reduce函数将应用于新文档和现有文档,并且现有文档将被函数调用的结果覆盖)

db参数是可选的,它指定结果集合应位于的数据库的名称。 默认情况下,数据库的名称将与输入集合的名称相同。

可选的sharded参数启用(或禁用)输出collection的 分片 。 如果将其设置为true ,则将使用_id属性作为分片对输出集合进行分片。

可选的nonAtomic参数会提示MongoDB服务器是否应该锁定输出集合所在的数据库。 如果将其设置为true ,则数据库将不会被锁定,其他客户端可能会读取输出集合的中间状态。 因此,如果将其设置为false ,则仅在处理完成时才会锁定和解锁数据库。 nonAtomic参数仅对合并归约操作有效,并在后处理步骤中起作用。

  • 内联输出: map / reduce操作在内存中执行并返回完整结果( 副本集辅助成员可用的唯一选项)。
out: { inline: 1 }

表4

数据集

我们将改编第4部分中的书店示例。《 MongoDB分片指南》将说明使用books集合的不同地图/缩小场景。

db.books.insert( {
    "title" : "MongoDB: The Definitive Guide",
    "published" : "2013-05-23",
    "authors": [
        { "firstName" : "Kristina",  "lastName" : "Chodorow" }
    ],
    "categories" : [ "Databases", "NoSQL", "Programming" ],
    "publisher" : { "name" : "O'Reilly" },
    "price" : 32.99
} )
db.books.insert( {
    "title" : "MongoDB Applied Design Patterns",
    "published" : "2013-03-19",
    "authors": [
        { "firstName" : "Rick",  "lastName" : "Copeland" }
    ],
    "categories" : [ "Databases", "NoSQL", "Patterns", "Programming" ],
    "publisher" : { "name" : "O'Reilly" },
    "price" : 32.99
} )
db.books.insert( {
    "title" : "MongoDB in Action",
    "published" : "2011-12-16",
    "authors": [
        { "firstName" : "Kyle",  "lastName" : "Banker" }
    ],
    "categories" : [ "Databases", "NoSQL", "Programming" ],
    "publisher" : { "name" : "Manning" },
    "price" : 30.83
} )
db.books.insert( {
    "title" : "NoSQL Distilled: A Brief Guide to the Emerging World of Polyglot Persistence",
    "published" : "2012-08-18",
    "authors": [
        { "firstName" : "Pramod J.",  "lastName" : "Sadalage" },
        { "firstName" : "Martin",  "lastName" : "Fowler" }
    ],
    "categories" : [ "Databases", "NoSQL" ],
    "publisher" : { "name" : "Addison Wesley" },
    "price" : 26.36
} )
db.books.insert( {
    "title" : "Scaling MongoDB",
    "published" : "2011-03-07",
    "authors": [
        { "firstName" : "Kristina",  "lastName" : "Chodorow" }
    ],
    "categories" : [ "Databases", "NoSQL" ],
    "publisher" : { "name" : "O'Reilly" },
    "price" : 25.30
} )
db.books.insert( {
    "title" : "50 Tips and Tricks for MongoDB Developers",
    "published" : "2011-05-06",
    "authors": [
        { "firstName" : "Kristina",  "lastName" : "Chodorow" }
    ],
    "categories" : [ "Databases", "NoSQL", "Programming" ],
    "publisher" : { "name" : "O'Reilly" },
    "price" : 25.08
} )
db.books.insert( {
    "title" : "MongoDB in Action, 2nd Edition",
    "published" : "2014-12-01",
    "authors": [
        { "firstName" : "Kyle",  "lastName" : "Banker" },
        { "firstName" : "Peter",  "lastName" : "Bakkum" },
        { "firstName" : "Tim",  "lastName" : "Hawkins" }
    ],
    "categories" : [ "Databases", "NoSQL", "Programming" ],
    "publisher" : { "name" : "Manning" },
    "price" : 26.66
} )
db.books.insert( {
    "title" : "Node.js, MongoDB, and AngularJS Web Development",
    "published" : "2014-04-04",
    "authors": [
        { "firstName" : "Brad",  "lastName" : "Dayley" }
    ],
    "categories" : [ "Databases", "NoSQL", "Programming", "Web" ],
    "publisher" : { "name" : "Addison Wesley" },
    "price" : 34.35
} )

一旦书籍收藏中充满了这些文档(最好的方法是使用MongoDB shell),我们就可以开始通过示例开始使用map / reduce了。

示例:按作者计数书籍

让我们从最简单的场景开始,运行MongoDB map / reduce命令来完成我们在Map / Reduce概览部分中所看过的示例:按作者计数书籍。

db.runCommand( {
    mapReduce: "books",
    map: function() {
        for (var index = 0; index < this.authors.length; ++index) {
            var author = this.authors[ index ];
            emit( author.firstName + " " + author.lastName, 1 );
        }
    },
    reduce: function(author, counters) {
        count = 0;

        for (var index = 0; index < counters.length; ++index) {
            count += counters[index];
        }

        return count;
    },
    out: { inline: 1 }
} )

如果我们在MongoDB Shell中运行此命令,则map / reduce命令的结果将返回以下文档:

{
    "results" : [
        {
            "_id" : "Brad Dayley",
            "value" : 1
        },
        {
            "_id" : "Kristina Chodorow",
            "value" : 3
        },
        {
            "_id" : "Kyle Banker",
            "value" : 2
        },
        {
            "_id" : "Martin Fowler",
            "value" : 1
        },
        {
            "_id" : "Peter Bakkum",
            "value" : 1
        },
        {
            "_id" : "Pramod J. Sadalage",
            "value" : 1
        },
        {
            "_id" : "Rick Copeland",
            "value" : 1
        },
        {
            "_id" : "Tim Hawkins",
            "value" : 1
        }
    ],
    "timeMillis" : 1,
    "counts" : {
        "input" : 8,
        "emit" : 11,
        "reduce" : 2,
        "output" : 8
    },
    "ok" : 1
}

相当清晰的输出,并且我们可以看到每个作者都伴随着输入集合中他的书籍总数。

示例:按出版商计算平均图书价格

我们要看的下一个示例稍微复杂一点,并为map / reduce命令引入了三个新元素: finalize范围和具有名称result的 输出到集合 。 我们将使用一种特定的货币(美元)计算每个出版商的平均图书价格。

db.runCommand( {
    mapReduce: "books",
    scope: { currency: "US" },
    map: function() {
        emit( this.publisher, { count: 1, price: this.price } );
    },
    reduce: function(publisher, values) {
        var value = { count: 0, price: 0 };

        for (var index = 0; index < values.length; ++index) {
            value.count += values[index].count;
            value.price += values[index].price;
        }

        return value;
    },
    finalize: function(publisher, value) {
        value.average = currency + ( value.price / value.count ).toFixed(2);
        return value;
    },
    out: {
        replace: "results"
    }
} )

在此示例中, 范围文档将全局可变货币引入到map / reduce操作的上下文( mapreducefinalize函数)。 您可能已经知道,只有在处理完所有文档后才能计算平均价格,这就是引入finalize函数的原因:一旦所有mapreduce步骤都结束,就调用它。 映射/归约操作的最终输出将存储在结果集中( 书店数据库)。 如果我们在MongoDB Shell中运行此命令,则将返回以下文档作为结果:

{
    "result" : "results",
    "timeMillis" : 50,
    "counts" : {
        "input" : 8,
        "emit" : 8,
        "reduce" : 3,
        "output" : 3
    },
    "ok" : 1
}

结果集合包含以下文档(可以通过在MongoDB Shell中运行命令包装器db.results.find().pretty()来检索这些文档):

{
    "_id" : {
        "name" : "Addison Wesley"
    },
    "value" : {
        "count" : 2,
        "price" : 60.71,
        "average" : "US30.36"
    }
}
{
    "_id" : {
        "name" : "Manning"
    },
    "value" : {
        "count" : 2,
        "price" : 57.489999999999995,
        "average" : "US28.74"
    }
}
{
    "_id" : {
        "name" : "O'Reilly"
    },
    "value" : {
        "count" : 4,
        "price" : 116.36,
        "average" : "US29.09"
    }
}

示例:按出版商递增计算平均书价

最后一个示例将演示MongoDB中 map / reduce实现的增量性质。 让我们假设,由于我们已经按出版商计算了平均图书价格,因此有两本新书添加到收藏中。

db.books.insert( {
    "title" : "MongoDB and Python: Patterns and processes for the popular document-oriented database",
    "published" : "2011-09-30",
    "authors": [
        { "firstName" : " Niall",  "lastName" : "O'Higgins" }
    ],
    "categories" : [ "Databases", "NoSQL", "Programming" ],
    "publisher" : { "name" : "O'Reilly" },
    "price" : 18.06
} )
db.books.insert( {
    "title" : " Node.js in Action",
    "published" : "2013-11-28",
    "authors": [
        { "firstName" : " Mike",  "lastName" : "Cantelon" }
    ],
    "categories" : [ "Databases", "NoSQL", "Programming", "Web" ],
    "publisher" : { "name" : "Manning" },
    "price" : 26.09
} )

现在,我们在这里有两个选择,以便获得出版商的更新平均书价:我们可以再次对集合中的所有文档重新运行map / reduce命令,或者仅使用新文档对现有结果进行增量更新。 后者将是本示例的目标。 mapreducefinalize函数与前面的示例相同。 我们将添加的两个新参数是limitquery ,仅用于过滤出这两本新书,另外,将使用reduce操作( out参数)合并输出集合。

db.runCommand( {
    mapReduce: "books",
    scope: { currency: "US" },
    map: function() {
        emit( this.publisher, { count: 1, price: this.price } );
    },
    reduce: function(publisher, values) {
        var value = { count: 0, price: 0 };

        for (var index = 0; index < values.length; ++index) {
            value.count += values[index].count;
            value.price += values[index].price;
        }

        return value;
    },
    finalize: function(publisher, value) {
        value.average = currency + ( value.price / value.count ).toFixed(2);
        return value;
    },
    query: { "authors.lastName": { $in: [ "Cantelon", "O'Higgins" ] } },
    limit: 2,
    out: {
        reduce: "results"
    }
} )

查询过滤器仅包括两名新作者出版的书籍。 出于演示的目的,该限制也设置为2,但是它并不是很有用,因为我们知道只能添加两本新书。 如果我们在MongoDB Shell中运行此命令,则将返回以下文档作为结果:

{
    "result" : "results",
    "timeMillis" : 4,
    "counts" : {
        "input" : 2,
        "emit" : 2,
        "reduce" : 0,
        "output" : 3
    },
    "ok" : 1
}

请注意,根本没有调用reduce函数,因为每个键(发布者)仅包含一个值(有关这些微妙的细节,请参阅MongoDB中的Map / Reduce部分)。

以下是结果集合中的文档(请将它们与“ 示例:按出版商计算平均图书价格”部分中的文档进行比较):

{
    "_id" : {
        "name" : "Addison Wesley"
    },
    "value" : {
        "count" : 2,
        "price" : 60.71,
        "average" : "US30.36"
    }
}
{
    "_id" : {
        "name" : "Manning"
    },
    "value" : {
        "count" : 3,
        "price" : 83.58,
        "average" : "US27.86"
    }
}
{
    "_id" : {
        "name" : "O'Reilly"
    },
    "value" : {
        "count" : 5,
        "price" : 134.42,
        "average" : "US26.88"
    }
}

如我们所见,现有结果集已使用适当的合并算法进行了增量更新。

4.接下来

本教程的这一部分总结了MongoDB文档数据库功能的基本但仍然足够详细的概述。 在本课程最后部分,我们将深入探讨本章,并希望揭示有关MongoDB内部和高级概念的一些有趣细节。

翻译自: https://www.javacodegeeks.com/2015/09/mongodb-mapreduce-tutorial.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值