# 七、使用 C# 流构建映射器

Hadoop 的一个关键组件是用于处理数据的 MapReduce 框架。其概念是处理数据的代码的执行被发送到计算节点，这使得它成为分布式计算的一个例子。这项工作被划分到执行特定任务的多个作业中。

映射器的工作相当于提取 ETL 范例的组件。他们读取核心数据并从中提取关键信息，实际上是在非结构化数据上强加结构。另外，“非结构化”这个术语有点用词不当，因为数据并非完全没有结构——否则几乎不可能解析。相反，数据并不像关系数据库那样具有正式应用于它的结构。从这个意义上说，管道分隔的文本文件可以被认为是非结构化的。例如，我们的源数据可能如下所示:

```cs
      1995|Johns, Barry|The Long Road to Succintness|25879|Technical
      1987|Smith, Bob|I fought the data and the data won|98756|Humour
      1997|Johns, Barry|I said too little last time|105796|Fictions

```

人眼可能能够猜测这些数据可能是一个图书馆目录，以及每个领域是什么。然而，计算机不会有这样的运气，因为它没有被告知数据的结构。在某种程度上，这是映射器的工作。可能会被告知，该文件是管道分隔的，它将提取作者姓名作为关键字，字数作为值作为<key>对。因此，该映射器的输出如下所示:</key>

```cs
      [key] <Johns, Barry> [value] <25879>
      [key] <Smith, Bob> [value] <98756>
      [key] <Johns, Barry> [value] <105796>

```

Reducer 相当于 ETL 范例的转换组件。它的工作是处理提供的数据。这可能是像聚类算法这样复杂的事情，也可能是像聚合这样简单的事情(例如，在我们的示例中，通过键对值求和)，例如:

```cs
      [key] <Johns, Barry> [value] <131675>
      [key] <Smith, Bob> [value] <98756>

```

这个过程还有其他组件，特别是组合器，它在单个映射器的节点上执行缩减器任务的一些功能。还有分区器，指定减压器输出发送到哪里供减压器处理。有关详细信息，请参考 http://wiki.apache.org/hadoop/HadoopMapReduce 官方文件。

写一些作业是可能的。NET 语言，我们稍后将对此进行探讨。

## 流概述

流式传输是 Hadoop 功能的核心部分，允许在 HDFS 内逐行处理文件。[<sup>【13】</sup>](12.html#_ftn13)处理分配给专门为练习编码的映射器(如果需要，还有 Reducer)。

该过程通常使用映射器逐行读取文件块，从每行获取输入数据(STDIN)，对其进行处理，并将其作为键/值对发送到 STDOUT。关键字是第一个制表符之前的任何数据以及其后的任何值。然后，Reducer 将使用来自 STDOUT 的数据，并根据需要对其进行处理和显示。

## 用 C# 流

流的一个关键特性是，它允许使用除 Java 之外的语言作为执行映射和缩减任务的可执行文件。因此，C# 可执行文件可以在流作业中用作映射器和缩减器。

使用控制台。ReadLine()处理输入(来自 STDIN)和控制台。WriteLine()要写输出(到 STDOUT)，很容易实现 C# 程序来处理数据流。[<sup>【14】</sup>](12.html#_ftn14)

在这个例子中，编写了一个 C# 程序来作为映射器处理原始数据的预处理，进一步的处理由更高级的语言如 Pig 和 Hive 来处理。

下面引用的代码可以从[https://bitbucket . org/syncfusiontech/hdinsight-简洁地说/下载](https://bitbucket.org/syncfusiontech/hdinsight-succinctly/downloads)为“Morison _ v2 . zip”。需要一个合适的开发工具(如 Visual Studio)来处理代码。

## 数据来源

例如，数据来源是韦斯特伯里实验室新闻组语料库，该语料库收集了 2005 年 10 月至 2011 年 1 月期间来自 47，000 个团体的 2，800 万个匿名的新闻组帖子。[<sup>【15】</sup>](12.html#_ftn15)这是人类输入的自由格式英文文本，提供了相当大(约 35GB)的数据来源进行分析。

从这些数据中，我们可以提取出新闻组发文者的用户名，发文的大致日期和时间，以及邮件内容的分类。

### 数据挑战

在获取这些数据时会面临一些具体的挑战:

*   一个项目的数据跨多行
*   作者姓名的位置格式不一致
*   这些帖子经常包含大量引用其他帖子的文字
*   有些词构成了数据的重要部分，却没有增加洞察力

对这些问题的处理进行了更深入的分析，因为它们表明了处理非结构化数据时面临的挑战类型。

### 跨多条线的数据

所提供的数据对流式传输有特殊的挑战:所提供的数据中的文本被分成多行。流处理是逐行进行的，因此有必要跨多行保留元数据。在我们的数据示例中，这方面的一个示例如下所示:

| 数据样本 |
| H.布兰德弗利特写道:我打电话给他们，告诉他们发生了什么...问我如果不能怎么办在我收到邮件的那个新号码上装宽带？-结束。文件的 |

这将由 STDIN 读取，如下所示:

| 行# | 文本 |
| --- | --- |
| one | H.布兰德弗利特写道: |
| Two |  |
| three | 我打电话给他们，告诉他们发生了什么...问我如果不能怎么办 |
| four | 在我收到邮件的那个新号码上装宽带？ |
| five |  |
| six | -结束。文件的 |

这意味着映射器必须能够:

*   识别新的数据元素
*   跨行读取维护元数据
*   处理在 HDFS 被分成块的数据

识别新的数据元素很简单，因为每个帖子都由一行固定的文本来分隔。文件-。通过这种方式，映射器可以安全地假设找到该文本意味着当前帖子的结束。

第二个挑战通过将跨行读取的元数据保留在正常变量中，并在确定行尾时重置它们来解决。元数据被附加到每个情感关键词上。

第三个挑战是解决这样一个事实，即数据文件可以通过在 HDFS 进行文件分块来分解，这意味着行将在一个文件中提前结束，而在另一个文件中提前开始，如下所示:

| 文件 | 行# | 文本 |
| --- | --- | --- |
| A | one | H.布兰德弗利特写道: |
| A | Two |  |
| A | three | 我打电话给他们，告诉他们发生了什么...问我如果不能怎么办 |
| 文件分割 |
| B | four | 在我收到邮件的那个新号码上装宽带？ |
| B | five |  |
| B | six | -结束。文件的 |

这是以简单的方式处理的。当文件 A 终止时，它会发出到那时为止收集的所有数据。文件 B 将简单地丢弃那些无效的第一行，因为它不能向它们附加元数据。这是一种妥协，会导致少量数据丢失。

### 格式不一致

在档案中，大多数消息都是以可变数量的空行开始，其后是开始行，有时但不总是表明文章的作者。这通常可以通过模式“[用户名]”来识别。

然而，这是不一致的，因为各种新闻组客户允许改变这一点，没有遵循标准的格式，或者有时提取过程遗漏了某些细节。下面是一些开场白的例子:

| 开场白 | 评论 |
| --- | --- |
| “篮球基地”< <emailaddress>>在留言中写道</emailaddress><newsurl>...</newsurl> | 不出所料，第一行包含“wrote”一词之前的一些文本。 |
| > 2005 年 9 月 28 日星期三 02:13:52 -0400，东海岸巴特德>< <emailaddress>>写道:</emailaddress>> | 文本的第一行不包含“已写”这个词——它被推到了第二行。 |
| 从前...长的...很久以前...我决定得到我家的电话号码改变的...因为我接到了很多愚蠢的电话 | 文本不包含作者详细信息。 |
| 2005 年 9 月 29 日星期四 13:15:30 +0000(世界协调时)，“福巴”写道: | 提交人的名字前面有一个日期/时间戳。 |
| “匿名屋”< <emailaddress>>提议:</emailaddress><newsurl>...</newsurl> | 海报从默认的“写”字变成了“提议”。 |

作为最初的妥协，映射器只是忽略了所有的非标准情况，并将其标记为拥有“未知”作者。

在一个改进中，正则表达式被用来匹配和删除一些更常见的日期戳格式。代码示例中捕获了这方面的详细信息。

### 引用文本

在新闻组邮报内部，许多客户的默认行为是将之前的消息作为引用文本包含进来。为了分析给定消息的情感，需要排除该文本，因为它来自另一个人。

幸运的是，引用的文本很容易被识别为以“>”开头的引用行，因此映射器简单地丢弃了以该字符开头的任何行。这可能会导致少量但可容忍的数据丢失(如果文章作者有意或无意地以“>”字符开头)。这是通过以下方式实现的:

```cs
      // Read line by line from STDIN
      while ((line = Console.ReadLine()) != null)
      {
           // Remove any quoted posts as identified by starting with ">"
           if (line.StartsWith(">") != true)
           {  
                      //There is no “>” so process data

           }
      …   
      }

```

### 没有价值的词

英语中的一些单词非常常见，对于简单的、基于一个单词的情感分析来说，没有任何意义。

在最初的数据传递后，很明显有大量的两个字母的单词(“of”和“at”)和单个字符(“a”和“I”)可以忽略，特别是考虑到我们的 Mority 关键字列表没有长度小于三个字符的单词条目。因此，应用了一个过滤器来防止显示任何长度小于三个字符的字符串:

```cs
      // Only write to STDOUT if there is content, ignoring words of 2 characters or less
      if (descword.Length > 2)
      {
      Console.WriteLine("{0}", MessageId + "|" + AuthorId + "|" + descword);
      }                         

```

此外，还有几个频率非常高的词没有价值，如“和”和“该”。我们使用了字典和查找来防止显示这些内容，将上面的代码修改为:

```cs
      // Set up some words to ignore
      Dictionary<string, int> IgnoreWords = new Dictionary<string, int>();
      IgnoreWords.Add("the", 1);
      IgnoreWords.Add("and", 1);

       // Only write to STDOUT if there is content, ignoring words of 2 characters or less
      if (descword.Length > 2)
      {
           // Check if in list of ignore words, only write if not
           if (!IgnoreWords.TryGetValue(descword, out value))
           {
                 Console.WriteLine(string.Format("{0}|{1}|{2}", MessageId ,AuthorId, descword));
           }                         
      }

```

这大大减少了要显示的行数，从而减少了后续处理的行数。

## 对数据样本执行映射程序

提供的原始数据是 bzip2 格式的。[<sup>【16】</sup>](12.html#_ftn16)Hadoop 作业可以本机处理某些压缩文件格式，如果有指示，还可以以压缩格式显示结果。这意味着，对于执行样本，不需要解压缩来处理数据。确认 HDInsight 支持以下格式:

| 格式 | 多媒体数字信号编解码器 | 延长 | 可拆分的 |
| 紧缩 | org . Apache . Hadoop . io . compress . Defaultcodec | 。紧缩 | 普通 |
| gzip | org . Apache . Hadoop . io . compress . gzipcode | 。地面零点 | 普通 |
| bzip2 | org . Apache . Hadoop . io . compress . bzip2 codec | . bz2 | Y |

使用压缩输入和压缩输出会带来一些性能影响，需要根据存储和网络流量考虑进行平衡。要在 HDInsight 中全面回顾这些注意事项，建议您阅读微软关于题为“Hadoop 中的压缩”的主题的白皮书(上面表格中的信息取自该白皮书)。[<sup>【17】</sup>](12.html#_ftn17)

映射器是作为标准的 C# 控制台应用可执行文件构建的。为了让 Hadoop 作业能够使用它，它需要被加载到作业可以引用该文件的某个地方。Azure Blob 存储显然是处理这个问题的一个方便的地方。

加载数据和映射器后，使用 Hadoop 命令行来指定和启动作业。也可以通过 SDK 或 PowerShell 提交作业。

作业的完整语法如下所示:

```cs
      c:\apps\dist\hadoop-1.1.0-SNAPSHOT\bin\hadoop.cmd
      jar C:\apps\dist\hadoop-1.1.0-SNAPSHOT\lib\hadoop-streaming.jar
      "-D mapred.output.compress=true"
      "-D mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec"
      -files "wasb://user/hadoop/code/Sentiment_v2.exe"
      -numReduceTasks 0
      -mapper "Sentiment_v2.exe"
      -input "wasb://user/hadoop/data"
      -output "wasb://user/hadoop/output/Sentiment/"

```

作业的参数解释如下[<sup>【18】</sup>](12.html#_ftn18):

| 参数 | 详述 |
| --- | --- |
| "-D mapred.output.compress=true " | 压缩输出 |
| "-D mapred . output . compression . codec = org . Apache . Hadoop . io . compression . gzipcode " | 使用 GzipCodec 压缩输出 |
| -文件“was://user/Hadoop/code/Morise _ v2 . exe” | 参考蓝色斑点存储中的映射器代码 |
| -NumReeducasks 0 | 指定没有减速器任务 |
| -映射器“Morise _ v2 . exe” | 指定映射器文件 |
| -输入“was://user/Hadoop/data” | 指定输入目录 |
| -output " was://user/Hadoop/output/Mority/" | 指定输出目录 |

作业结果的示例如下所示:

```cs
      276.0|5|bob|government
      276.0|5|bob|telling   
      276.0|5|bob|oppose
      276.0|5|bob|liberty
      276.0|5|bob|obviously
      276.0|5|bob|fail
      276.0|5|bob|comprehend
      276.0|5|bob|qualifier
      276.0|5|bob|legalized
      276.0|5|bob|curtis

```