65.9K
CodeProject 正在变化。 阅读更多。
Home

开始使用 MapReduce

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2016 年 7 月 28 日

CPOL

7分钟阅读

viewsIcon

24090

开始使用 MapReduce

引言

Cloudant 引擎支持多种快速查询数据的方式,这些数据基于与核心数据分开维护的索引。一些活动(如创建、更新和删除)会影响索引的生成和重新生成,这些都在后台任务中调度,以实现更快、非阻塞的写入吞吐量。

我们有以下索引类型可供查看

  • MapReduce 视图
  • 搜索索引

这些索引类型通过在 Cloudant 数据库中使用不同的算法来实现

  • MapReduce 基于二叉树索引键值对,从主数据集或其子集中过滤,从而呈现可配置的视图。
  • 搜索索引基于请求参数使用,这就是为什么它们使用 Apache Lucene 进行架构的原因。这使得它们能够更灵活地处理复杂的请求,例如自由文本搜索。

什么是 MapReduce?

有很多很好的定义来描述 MapReduce

MapReduce 是一种编程模型和相关的实现,用于在集群上通过并行分布式算法处理和生成大型数据集。自 1995 年以来,随着消息传递接口标准减少了散射操作,概念上相似的方法已广为人知。MapReduce 程序由一个执行过滤和排序的 Map() 过程(方法)(例如按名字将学生分类到队列中,每个名字一个队列)和一个执行摘要操作的 Reduce() 方法(例如计算每个队列中的学生人数,得出名字频率)组成。“MapReduce 系统”(也称为“基础设施”或“框架”)通过调度分布式服务器、并行运行各种任务、管理系统各个部分之间的所有通信和数据传输,并提供冗余和容错来协调处理。

MapReduce 框架的关键贡献不是实际的 Map 和 Reduce 函数,而是通过一次优化执行引擎为各种应用程序实现的可伸缩性和容错性。

MapReduce 库已用多种编程语言编写,具有不同的优化级别。(维基百科 https://en.wikipedia.org/wiki/MapReduce

我不会尝试创建比维基百科更好的 MapReduce 描述。相反,我将详细介绍 Cloudant 如何实现它。

可伸缩性基于增量视图更新;每次创建、更新或删除记录时,视图都会触发重新生成。Cloudant 引擎允许我们构建链式 MapReduce 视图,以获得更好的性能和更快的开发——这完全是为了能够将过滤或聚合的视图数据重新用于新设计的视图中,以便创建多个为不同访问级别、不同区域等设计的相似视图。

MapReduce 基础

MapReduce 视图的设计基于两个 JavaScript 函数

  • 映射函数 - 旨在允许您过滤数据集并将其转换为键值对,以便处理这些值
  • 归约函数 - 旨在处理值,以便将数据聚合到较小的数据集中

归约脚本的使用是可选的。它允许我们仅使用映射函数构建简单的过滤器。

    function (doc) {   
      if (doc.temp) {   
        emit(doc.time,doc.temp);   
      }   
    }

(此映射函数示例基于我们之前帖子的示例。)

它创建了一个键值部分视图,其中时间戳是键,温度是值。

需要注意的是,在这种情况下,键(以及值)可以重复,因此此映射函数不会删除重复项。MapReduce 视图存储在设计文档中,Cloudant 仪表板上提供 RESTful 访问和设计 UI。每个设计文档都有一个唯一 ID,存储此文档的唯一 URL。

    {   
      "_id": "_design/sensorExt",   
      "_rev": "7-5f949af68be60bbc8dca57447aeef309",   
      "views": {       
        "RecordCount": {   
          "reduce": "_count",   
          "map": "function (doc) {\n  emit(doc.dspl, doc.temp);\n}"  
        }   
      },   
      "language": "javascript"  
    }

此处的示例正在使用预定义函数之一 (_count) 计算记录。

归约函数

为了加速和优化视图重新计算,归约函数旨在同时处理整个数据集的部分子集,并将它们之间的结果组合成最终结果。

一个简单的例子是默认的计数代码

    function (keys, values, rereduce) {   
      if (rereduce) {   
        return sum(values);   
      } else {   
        return values.length;   
      }   
    }

在此代码中,第一个“reduce”(参数 rereduce 为 false)在键值对数组(应用映射函数后整个数据集的子集)上执行,这就是为什么返回值是使用“|length|”计数数组中的记录。

每次后续执行都接收带有初始块计数结果的键值对(并且参数 rereduce 为 true),因此返回值计算为给定计数数组中所有值的总和。

下面是一个用于提取最大值和组的 Reduce 函数

假设我们有一个传感器数据库,其中每个传感器在特定时刻传输温度。我们需要实现一个 MapReduce 函数,该函数获取每个传感器的最高温度(这基于之前的帖子示例)。

    function (keys, values, rereduce) {   
      var getMaxOfValues = function(vs) {      
        var max = {};      
        for(var i in vs) {      
          var val = vs[i];      
          if(val instanceof Array) {      
            val = getMaxOfValues(val, max);      
          }      
          for(var sensorType in val) {      
            var temperature = val[sensorType];      
            if(max[sensorType] === undefined ||  max[sensorType] < temperature ) {      
              max[sensorType] = temperature;      
            }      
          }     
        }      
        return max;      
      };      
            
      if(rereduce) {      
        return getMaxOfValues(values);      
      } else {      
        var max = {};      
        for(var j = 0 ; j < keys.length ; j++) {      
          var sensorType = keys[j][0];      
          var temperature = values[j];      
          if(max[sensorType] === undefined || max[sensorType] < temperature){      
            max[sensorType] = temperature;      
          }      
        }      
        return max;      
      }      
    }

下图显示了 Cloudant MapReduce 如何与此函数一起工作

第一列(蓝色)表示函数的线程化“reduce”执行,它处理映射结果。所有其他列表示函数的线程化“rereduce”执行,它们聚合第一个 reduce 的结果。

通过这种方式,您可以看到,当记录更改或添加时,不需要重新计算整个树即可获得正确结果。此外,它很容易进行多线程处理以获得快速结果。

Using the Code

新设计文档

如上所述,您可以通过向数据库 RESTful API 发布格式化的 JSON 来创建视图。

第二个选项是使用 Cloudant 仪表板并创建新的设计文档。

Cloudant 教程

扩展简单的映射函数

在上面的示例中,我们创建了温度和时间戳的键/值对,但是如果我们需要不止一个值怎么办?映射函数允许我们将键映射为 JSON 对。由于这是 JavaScript,您可以使用简单的值操作(考虑到它们会增加服务器的负载)。

下一个示例导出湿度、华氏温度并将其重新计算为摄氏温度

    function (doc) {   
      if (doc.dspl=="sensorB") {   
        emit(doc.time,{"F":doc.temp,"C":((doc.temp-32)*0.5556),   
        "hmdt":doc.hmdt});   
      }   
    }

一对多关系的数据规范化

在某些情况下,存储在数据库中的 JSON 文档可能包含多条记录——内部数组——一对多关系。当您需要处理这些记录时,最好构建一个视图,将它们作为键值对(更像键-JSON 对)导出,并索引到原始记录。

反之亦然——当您尝试重用已经生成一对多关系的视图时,您可以通过发出带有字段“|_id|”的值来访问原始文档。

下面的示例创建了一个索引,其中包含文档 _id 与 children 集合中每个子项之间的关系。

    function(doc) {   
      if (doc.children) {   
        for (child in children) {   
          emit(doc._id, { "_id": child });   
        }   
      }   
    }

复杂键

如上所述,值不限于简单值,但映射函数可以设计为发出键-JSON 对。相同的限制缺失适用于键——您可以使用 JSON 值来定义键。因此,您的映射函数可能会产生 [JSON 键]-[JSON 值] 对(其中键已索引)

计数和最大值示例

让我们使用数据库中已有的传感器数据创建一些视图。在下面的示例中,我们有简单的记录计数视图和上面示例中的最大值视图

请注意,这些映射函数发出的键/值对的键等于分解值,这就是我们得到按这些值区分的结果的方式。

请求

    GET https://$USERNAME:$PASSWORD@$USERNAME.cloudant.com/$DATABASE/_design/sensorExt/_view/RecordCount?reduce=true&group=true HTTP/1.1   
    Accept: application/json   
    Content-Type: application/json

响应

    {   
        "rows": [   
            {   
                "key": "sensorA",   
                "value": 2   
            },   
            {   
                "key": "sensorB",   
                "value": 1   
            },   
            {   
                "key": "sensorC",   
                "value": 2   
            },   
            {   
                "key": "sensorD",   
                "value": 6   
            },   
            {   
                "key": "sensorE",   
                "value": 2   
            }   
        ]   
    }

请注意,如果未将“|reduce|”和“|group|”属性设置为 true,则相同的请求将返回简单的总计数。

请求

GET https://$USERNAME:$PASSWORD@$USERNAME.cloudant.com/$DATABASE/_design/sensorExt/_view/RecordCount HTTP/1.1   
Accept: application/json   
Content-Type: application/json

响应

{ "rows": [ { "key": null, "value": 13 } ] }

最大值请求

    GET https://$USERNAME:$PASSWORD@$USERNAME.cloudant.com/$DATABASE/_design/sensorExt/_view/maxValue HTTP/1.1   
    Accept: application/json   
    Content-Type: application/json

响应

    {   
        "rows": [   
            {   
                "key": null,   
                "value": {   
                    "sensorD": 148,   
                    "sensorA": 148,   
                    "sensorC": 145,   
                    "sensorB": 135,   
                    "sensorE": 148   
                }   
            }   
        ]   
    }

在自定义归约函数中,请求的“|Reduce|”和“|Group|”属性仅在函数实现正确的情况下才有效。

响应带有“?reduce=true&group=true

    {   
        "rows": [   
            {   
                "key": "sensorA",   
                "value": {   
                    "sensorA": 148   
                }   
            },   
            {   
                "key": "sensorB",   
                "value": {   
                    "sensorB": 135   
                }   
            },   
            {   
                "key": "sensorC",   
                "value": {   
                    "sensorC": 145   
                }   
            },   
            {   
                "key": "sensorD",   
                "value": {   
                    "sensorD": 148   
                }   
            },   
            {   
                "key": "sensorE",   
                "value": {   
                    "sensorE": 148   
                }   
            }   
        ]   
    }

如您所见,键现在已与最大值函数分组,并且每个键我们都有一个最大值。

摘要

本教程介绍了如何使用 Cloudant MapReduce 的基础知识。

您还可以学习如何将 REST 接口与 IBM Cloudant MapReduce 模型结合使用。

下一部分将重点介绍 Cloudant 搜索。将有关于如何在简单 IoT 解决方案中使用此功能的示例。

© . All rights reserved.