C#码农的大数据之路 - 使用C#编写MR作业

系列目录

写在前面

从Hadoop出现至今,大数据几乎就是Java平台专属一般。虽然Hadoop或Spark也提供了接口可以与其他语言一起使用,但作为基于JVM运行的框架,Java系语言有着天生优势。而且能找到的与大数据框架如Hadoop等使用介绍的文章也都以Java语言作为示例居多。许多C#er为了转投大数据怀抱也开始学习Java。微软为了拥抱大数据在这方面也做了许多,提供了一些工具及库使C#可以更好的与Hadoop等协同工作。本系列中我们一同学习如何以我们熟悉语言来使用Hadoop等大数据平台,毕竟大数据的思想是一致的,算法是固定的,语言只是一个工具。做好准备,出发。

本文先来介绍下微软这些年来在大数据平台上的工作。从中可以了解我们有哪些工具可用,方向是什么。

微软的大数据策略

本部分内容参考了各种官方文档,新闻报道总结而成,虽然已经努力确保正确,但难免出现疏漏,如果有错误请各位指出。

微软的大数据策略大概分了两个阶段。

早期
微软在Hadoop出现不久就已经开始关注,在Azure云服务出现后,微软实现一个Windows平台的Hadoop发行版名为HDInsight,并且在Azure中提供了名为HDInsight的大数据服务。在应用开发中,微软提供了名为Microsoft HDInsight Emulator for Windows Azure的工具来支持开发过程中的调试。这个工具已经过时,不过现在仍然可以使用Web Platform Installer 5.0来安装。实际上这个时期的微软版Hadoop - HDInsight是基于Hortonworks Data Platform for Windows开发的,HDInsight在这个时期也是Windows和Azure平台专属。
这个时期微软还提供了一系列的Framework用于简化使用C#开发Hadoop(准确的说是微软版Hadoop - HDInsight),最初放在Codeplex上,称为Microsoft .NET SDK For Hadoop,现在里面有部分API已经过时了,像是Microsoft.Hadoop.Client等。这些API提供使用C#编写MapReduce的功能,但仅能用于基于Windows的HDInsight。

当下
后来微软拥抱开源,拥抱Linux,微软的产品也不再单一的限于Windows平台或着以.NET作为开发框架。对于HDInsight的开发微软依然继续与Hortonworks合作,但这时的HDInsight(3.4版以后)只有Linux版本。(微软拥抱Linux,Hortonworks的HDP发行版都没有Windows版了)运行在Azure上的HDInsight服务也都是跑在Linux服务器上。对于本地开发可以使用Hortonworks提供的HDP(Hortonworks Data Platform) SandBox作为虚拟环境。HDP SandBox提供了3种不同运行环境的镜像,VirtualBox、VMware及Docker。楼主比较喜欢Docker,有关Docker版HDP SandBox的配置及使用VS连接这个虚拟环境的方法详见此文

关于HDP
HDP是一种大数据栈的发行版,包含了如Hadoop,Spark,Storm等组件。同类发行版中,可能更为人所知的是Cloudera所出的发行版CDH。下面放一张Hortonworks官网的图,2.5版本的HDP包含的组件一目了然:

MapReduce

微软最早开始支持使用自己的技术栈如C#和.NET开发大数据程序就是从MapReduce开始的。时至今日,微软一共提供了两种不同的方式让C#编写的MapReduce任务可以在Hadoop集群上执行,当然这些API也都是基于Hadoop Streaming,因为不管是基于Windows的大数据集群,还是基于Linux的大数据集群,它们都是运行于JVM之上。至少在很长一段时间内.NET CLR都不能和JVM共同工作。(当下,微软文档中也明确提到使用Hadoop Streaming由于数据在JVM和其他运行环境如.NET CLR之间传输会导致性能损失,微软也建议使用Java来编写MR程序,文档中的也有Java编写MR的示例)

随着微软拥抱Linux,基于Windows的大数据集群不再被支持(HDP for Windows也不再有后续版本了),微软也全面转向基于Linux的大数据集群(包括部署在Azure中的HDInsights也都是运行在Linux系统之上),这些C#写的API都不再被支持(主要原因还是这些基于.NET Framework的程序无法运行在Linux上,只能等未来.NET Core普及了)。

虽然这些API都已过时,但为了让大家了解C#技术栈这么多年来挣扎在大数据边缘的过程。下面对它们都进行了简单的介绍。

Hadoop API for .NET

以下代码示例主要来自CodePlex上那篇多年没有更新的文章

Hadoop API for .NET是微软推出的第一套用于Hadoop的.NET库。Hadoop组件中Hadoop Streaming用来支持与其它语言协同工作完成MapReduce(按国际惯例下文缩写为MR)任务的编写。Hadoop API for .NET包装了Hadoop Streaming方便使用.NET平台语言来编写MR任务。
使用Hadoop API for .NET:

  • 可以更方便的提交MR任务,而不用通过Hadoop Streaming命令行
  • 提供了MapperReducerCombiner更好的包装类作为基类,方便MR的编写
  • 自动包含依赖的.NET程序集一起作为streaming任务提交
  • 提供StreamingUnit进行对MapperReducerCombiner的本地单元测试
  • 通过JSON序列化及反序列化,支持输入输出的强类型

Hadoop API for .NET支持的存储包括HDFS和Azure Blob Storage。输入内容的格式就是惯常的\n\r分割行(记录),\r分割列。
如果既有Mapper又有Reducer则所需要的Key Value中的Key为纯文本,以便通过StringComparison.Ordinal进行排序并存储。
其它情况下记录可以是任意结构化数据的文本表示,如字符串表示的JSON数据,如果是二进制数据(如docx文件),则记录将是文件存储的路径。

时至今日,这其中大部分API都已被废弃。

MR程序组成

一个.NET编写的MR程序包含如下几部分:

  • 任务定义,包括MapperTypeReducerTypeCombinerType 和其它设置
  • MapperReducerCombiner
  • 存储于HDFS或Azure Blob Storage的输入数据
  • 任务执行器。可以通过MRRunner.exe运行.NET MR任务,也可以在Main函数中调用HadoopJobExecutor

编写的.NET MR任务(本示例仅有Mapper)

  1. 创建一个名为HadoopNET的C#控制台应用程序,查找名为Microsoft.Hadoop.MapReduce的Nuget包并安装。这将向项目中添加Microsoft.Hadoop.MapReduce.dllMRRunner.exe等工具。
  2. 开始编写Mapper。创建一个名为FirstMapper的类并实现MapperBase这个基类。

    public class FirstMapper : MapperBase
    {
        public override void Map(string inputLine, MapperContext context)
        {
            // 输入
            int inputValue = int.Parse(inputLine);
            // 任务
            var sqrt = Math.Sqrt(inputValue);
            // 写入输出
            context.EmitKeyValue(inputValue.ToString(), sqrt.ToString());
        }
    }

VS或Resharper的重构功能会自动添加using Microsoft.Hadoop.MapReduce;这个引用

  1. 创建任务类FirstJob,该类实现HadoopJob<FirstMapper>

    public class FirstJob : HadoopJob<FirstMapper>
    {
        public override HadoopJobConfiguration Configure(ExecutorContext context)
        {
            HadoopJobConfiguration config = new HadoopJobConfiguration();
            config.InputPath = "input/SqrtJob";
            config.OutputFolder = "output/SqrtJob";
            return config;
        }
    }

配置测试运行环境

要运行这个示例,需要在Windows上安装前面提到的Microsoft HDInsight Emulator for Windows Azure。这个工具需要通过Microsoft Web Platform Installer来安装,搜索hdinsight,结果第一项就是我们要安装的工具:

点击添加 - 安装,耐心等候个半小时(需要下载大约1.2G的文件)

这其中最重要的部分就是用于Windows平台的HDP(Hortonworks Data Platform for Windows),其所使用的是OpenJDK1.7的一个分支 - AZUL公司的Zulu。

安装完成后,桌面会多出3个快捷方式:

  • Hadoop Command Line
  • Hadoop Name Node Status
  • Hadoop YARN Status

Hadoop Command Line是一个命令行的快捷方式,在这个控制台中我们可以直接使用如hadoop fs这样的命令。默认安装下控制台进入后目录为C:\hdp\hadoop-2.4.0.2.1.3.0-1981hadoop等应用程序就在这个目录下的bin目录中。
这个目录的上级目录中(C:\hdp)包含了一些批处理及PowerShell脚本,我们就使用其中的start_local_hdp_services.cmdstop_local_hdp_services.cmd来起停这个Windows上的大数据集群。
运行start_local_hdp_services.cmd输入下面的信息表示已经成功启动。

starting zkServer
starting namenode
starting secondarynamenode
starting datanode
starting resourcemanager
starting timelineserver
starting nodemanager
starting jobhistoryserver
starting hiveserver2
starting metastore
starting derbyserver
starting templeton
starting oozieservice
Sent all start commands.
total services
13
running services
13

运行另外两个快捷方式分别可以看到HDFS和YARN的状态。
为了运行后面的程序,还需要给HDFS创建一个主目录

hadoop fs -mkdir -p /user/username #username是当前登陆的Windows用户名

创建测试数据并运行MR任务

为了运行程序,首先向HDFS中添加测试输入数据。创建一个文本文件input.txt,每行一个数字,类似:

9
16
25
36
81

将该文件上传到FisrtJob中配置的那个目录:

hadoop fs -mkdir input
hadoop fs -mkdir input/SqrtJob #如果目录不存在,先创建
hadoop fs -put input.txt input/SqrtJob/input.txt

剩下的就是编译这个程序并通过MRRunner执行,我们测试项目的名字为HadoopNet,编译后得到HadoopNet.exe。
通过代码可以看到,我们没有在Main函数中实现任何代码,我们要介绍的第一种提交作业的方式是使用MRRunner来提交,将输出的exe作为dll使用,运行下面的命令:

MRRunner.exe -dll HadoopNET.exe

不出意外的话,MR会开始执行。输出中的前半部分部分内容是MR执行过程中的日志。最后会打印实际提交的Hadoop平台的命令,MRRunner的作用也就在于生成这样的一条命令并将其提交到Hadoop Streaming来处理:

>>CMD: C:\hdp\hadoop-2.4.0.2.1.3.0-1981\bin\hadoop.cmd jar C:\hdp\hadoop-2.4.0.2.1.3.0-1981\share\hadoop\tools\lib\hadoop-streaming-2.4.0.2.1.3.0-1981.jar -D "mapred.reduce.tasks=0" -D "mapred.map.max.attempts=1" -D "mapred.reduce.max.attempts=1" -files "hdfs:///user/username/dotnetcli/bbc29713-7dfb-4e33-944e-68a42230383c/app/HadoopNET.exe,hdfs:///user/username/dotnetcli/bbc29713-7dfb-4e33-944e-68a42230383c/app/Microsoft.Hadoop.MapReduce.dll,hdfs:///username/hystar/dotnetcli/bbc29713-7dfb-4e33-944e-68a42230383c/app/Microsoft.Hadoop.WebClient.dll,hdfs:///user/username/dotnetcli/bbc29713-7dfb-4e33-944e-68a42230383c/app/Newtonsoft.Json.dll,hdfs:///user/username/dotnetcli/bbc29713-7dfb-4e33-944e-68a42230383c/app/Microsoft.Hadoop.MapDriver.exe,hdfs:///user/username/dotnetcli/bbc29713-7dfb-4e33-944e-68a42230383c/app/Microsoft.Hadoop.ReduceDriver.exe,hdfs:///user/username/dotnetcli/bbc29713-7dfb-4e33-944e-68a42230383c/app/Microsoft.Hadoop.CombineDriver.exe,hdfs:///user/username/dotnetcli/bbc29713-7dfb-4e33-944e-68a42230383c/app/Microsoft.WindowsAzure.Management.Framework.Threading.dll" -input hdfs:///user/hystar/input/SqrtJob -output hdfs:///user/hystar/output/SqrtJob -mapper Microsoft.Hadoop.MapDriver.exe -reducer NONE -cmdenv "MSFT_HADOOP_MAPPER_DLL=HadoopNET.exe" -cmdenv "MSFT_HADOOP_MAPPER_TYPE=HadoopNET.FirstMapper"

使用代码来提交MR作业

另一个运行MR程序的方式就是使用代码,我们在Main函数中添加如下代码:

var hadoop = Hadoop.Connect();
hadoop.MapReduceJob.ExecuteJob<FirstJob>();

编译生成HadoopNET.exe。这次我们不需要MRRunner可以直接运行Hadoop.exe。
执行过程和执行结果都与使用MRRunner一致。

注意这个测试用途的大数据平台,在MR任务输出目录已存在时会自动将其删除,而不是像原生Hadoop平台那样遇到输出目录存在时报错处理。

Microsoft.Azure.Management.HDInsight.Job

后来,微软技术路线改变,废弃了Azure Service Manager (ASM)-based tools中一列些工具,包括命令行CLI,PowerShell 和HDInsight .NET SDK(上一小节介绍的Microsoft.WindowsAzure.Management.HDInsight和Microsoft.Hadoop.Client都被废弃了)。全面转向Azure Resource Manager所包含的一些列库与工具。
虽然微软总是变来变去,但这也是为了迎合开源社区变化,以及让自身产品更有体系,更有可扩展性的无奈之举。
新的工具中与MapReduce相关的库为Microsoft.Azure.Management.HDInsight.Job下到一系列类。不同于之前的库,新的库只是提供一种通过.NET平台技术(包括C#与PowerShell)来提交MR任务的方法。而没有再提供一些辅助的类用于编写MR任务,新的MR任务编写应该是借鉴了Python等利用Hadoop Streaming进行大数据处理的方法,将Map和Reduce分别编写成可以独立执行的程序,然后提交到Hadoop Streaming去执行。下文会给出一个示例。
使用.NET C#编写的MR任务,需要基于Windows的Azure HDInsight环境来运行。这个目前没有模拟器,所以我们使用真实环境来测试。

这里没有使用世纪互联运营的Azure,而是使用了Azure全球服务,地区选择香港,可以使用大陆的手机号和大陆发行的Visa卡进行验证即可顺利激活1600HKD的试用额度。

登陆Azure控制台后,在左侧导航栏选择大象图标的HDInsight集群菜单(默认情况下,需要点击更多,在弹出的菜单中才能看到此项,可以点击后面的星按钮添加收藏,以使此菜单出现在第一屏中)。
新建集群,类型选择Windows,可以自定义集群,在测试目的中选择最低配置的集群结点(节省银子,虽然是免费额度),如下图。

按下图所示选择结点的配置

第一次使用Azure一般都需要创建新的存储账户,这里的容器就相当于我们这个集群存储的根目录。在Azure中,使用Azure Storage Blob作为类似HDFS的存在。

最后的摘要页面也有明确提示,从集群创建到被删除这个过程中将会一直按照右下角显示价格进行计费,无论是否运行任务。所以对于Azure HDInsight的新用户记得用后要删除是很重要的。另外Azure PowerShell和Azure .NET SDK都提供了使用代码创建与删除HDInsight集群的方法,方便将集群的创建,任务部署与集群删除作为一系列自动化任务来完成。

点击创建按钮,集群创建工作随之开始进行。

创建前,可以点击下载模板链接,将集群创建参数作为模板保存,由于创建过程还是稍显复杂,而有了这个模板就可以在本地通过Azure CLI,PowerShell或C#代码来完成集群的创建。详见此博文

这个过程大约持续20分钟,直到仪表盘中的正在创建变成正在运行。
集群创建好后,可以进入集群的控制台看看:

除了首页为Windows平台Azure HDInsights专有,其他页面都是到YARN,HDFS(Azure Blob Storage)及Job History原有管理页面的链接。通过各自的管理页面,可以了解到MR任务的工作情况,数据存储等。
有了运行环境,我们来实现两个简单的基于.NET的MR任务,分别创建名为NetMapper和NetReducer的两个控制台应用程序。然后添加如下代码:

这些代码来自微软官方示例

// Mapper
class NetMapper
{
    static void Main(string[] args)
    {
        if (args.Length > 0)
        {
            Console.SetIn(new StreamReader(args[0]));
        }

        string line;
        while ((line = Console.ReadLine()) != null)
        {
            Console.WriteLine(line);
        }
    }
}

// Reducer
class NetReducer
{
    static void Main(string[] args)
    {
        string line;
        var count = 0;

        if (args.Length > 0)
        {
            Console.SetIn(new StreamReader(args[0]));
        }

        while ((line = Console.ReadLine()) != null)
        {
            count += line.Count(cr => (cr == ' ' || cr == '\n'));
        }
        Console.WriteLine(count);
    }
}

将这两个项目分别生成,得到NetMapper.exe与NetReducer.exe。
接着在解决方案中新建一个项目SubmitNet用于提交MR任务到Azure中,提交代码如下:

class Program
{
    private static HDInsightJobManagementClient _hdiJobManagementClient;

    private const string ExistingClusterUri = "test-netcore.azurehdinsight.net";
    private const string ExistingClusterUsername = "admin"; //HDInsight集群默认用户名就是admin
    private const string ExistingClusterPassword = "创建集群时设置的密码";

    private const string DefaultStorageAccountName = "hdinsighthystar"; //存储账户名
    private const string DefaultStorageAccountKey = "存储账户Key";
    private const string DefaultStorageContainerName = "与HDInsight集群关联的容器的名称";

    static void Main(string[] args)
    {
        Console.WriteLine("The application is running ...");

        var clusterCredentials = new BasicAuthenticationCloudCredentials { Username = ExistingClusterUsername, Password = ExistingClusterPassword };
        _hdiJobManagementClient = new HDInsightJobManagementClient(ExistingClusterUri, clusterCredentials);

        SubmitMRJob();

        Console.WriteLine("Press ENTER to continue ...");
        Console.ReadLine();
    }

    private static void SubmitMRJob()
    {
        var paras = new MapReduceStreamingJobSubmissionParameters
        {
            Files = new List<string>() { "/example/app/NetMapper.exe", "/example/app/NetReducer.exe" },
            Mapper = "NetMapper.exe",
            Reducer = "NetReducer.exe",
            Input= "/example/data/gutenberg/davinci.txt",
            Output = "/example/data/StreamingOutput/wc.txt"
        };

        Console.WriteLine("Submitting the MR job to the cluster...");
        var jobResponse = _hdiJobManagementClient.JobManagement.SubmitMapReduceStreamingJob(paras);
        var jobId = jobResponse.JobSubmissionJsonResponse.Id;
        Console.WriteLine("Response status code is " + jobResponse.StatusCode);
        Console.WriteLine("JobId is " + jobId);

        Console.WriteLine("Waiting for the job completion ...");

        // Wait for job completion
        var jobDetail = _hdiJobManagementClient.JobManagement.GetJob(jobId).JobDetail;
        while (!jobDetail.Status.JobComplete)
        {
            Thread.Sleep(1000);
            jobDetail = _hdiJobManagementClient.JobManagement.GetJob(jobId).JobDetail;
        }

        // Get job output
        var storageAccess = new AzureStorageAccess(DefaultStorageAccountName, DefaultStorageAccountKey,
            DefaultStorageContainerName);
        var output = (jobDetail.ExitValue == 0)
            ? _hdiJobManagementClient.JobManagement.GetJobOutput(jobId, storageAccess) // fetch stdout output in case of success
            : _hdiJobManagementClient.JobManagement.GetJobErrorLogs(jobId, storageAccess); // fetch stderr output in case of failure

        Console.WriteLine("Job output is: ");

        using (var reader = new StreamReader(output, Encoding.UTF8))
        {
            string value = reader.ReadToEnd();
            Console.WriteLine(value);
        }
    }
}

代码改自文档中提交Job的代码,原文档中使用MapReduceJobSubmissionParameters提交JVM平台语言编写的jar文件。上面的例子使用MapReduceStreamingJobSubmissionParameters作为替换来提交Hadoop Streaming任务。
代码中的存储账户关联的容器可以使用Azure PowerShell快速查看。使用Azure PowerShell需要首先安装。在管理员提升的PowerShell窗口中执行:

Install-Module AzureRM

第一次使用Install-Module可能会提示需要安装NuGet提供程序,按Y继续就可以了。(PowerShell基于.NET,所以其扩展组件也被托管在NuGet上)
如果提示不受信任的存储库,也按Y继续即可。
成功安装基于PowerShell的Azure Resource Management工具后,在使用与账户相关的命令前都要先登录:

Login-AzureRmAccount

按提示输入相关信息,登陆成功后就可以执行查询命令了。必须获取HDInsight集群关联的存储账户。

Get-AzureRmHDInsightCluster -ClusterName "testmr"

返回信息中会包含如下内容,这就是我们想要的。

DefaultStorageAccount     : hdinsighthystar.blob.core.windows.net
DefaultStorageContainer   : testmr-2017-03-26t03-57-40-342z

程序准备完后,在提交任务前,我们还需要将两个exe文件与输入上传到Azure Blob存储中。
向Azure Blob存储中拷贝文件有n多种方式,详见此文档。最直观的方式是使用宇宙第一IDE - VS中的Azure插件。Azure插件更新较频繁,请确保使用最新版的软件。本文编写时最新版本为Azure SDK for .NET 2.9.6。
安装插件后,在Cloud Explorer中可以看到容器(在服务器资源管理器中Azure结点也能看到存储账户的容器),右键快捷菜单中选择查看容器。

打开容器浏览界面,点击上传按钮打开上传对话框,选择要上传的文件,并输入路径。
注意输入路径会自动创建文件夹,这也是使用此插件来创建文件夹的唯一方式。

点击确定上传即可。
其他向Blob上传文件的方式还包括使用Azure CLI,AzCopy应用程序及Azure PowerShell。
另外由于Azure Blob存储与HDFS兼容,也可以直接使用hadoop fs命令来操作Azure Blob存储。
比如:

hadoop fs -put /home/testfile.txt wasbs://CONTAINER@ACCOUNT.blob.core.windows.net/testpath/

其中的容器名与账户名可以通过前文介绍的Azure PowerShell命令来获取。
如果本地Windows中没有安装hadoop,可以通过远程桌面连接到HDInsight集群,将文件复制到远程机器,并在远程桌面中使用hadoop导入文件到Azure Blob存储。(远程桌面环境下导入可以省略wasbs://CONTAINER@ACCOUNT.blob.core.windows.net,直接使用相对路径)

一切准备妥当后,运行SubmitNet项目,就可以提交任务:

测试结束后要记得删除集群:

删除HDInsight集群,不会同时删除存储账户,存储账户只要存在也会按照使用量计费,区域香港的话,每个容器每天要0.01港币。
所以,存储账户几乎不占成本,可以保留,以后创建新集群时直接选择现有存储账户的容器,可以直接执行其中的任务,查看其中的文件。

基于Windows的Azure HDInsight将于未来几个月内停止服务。但我们上面介绍的内容只要做一定就该就可以迁移到基于Linux的HDInsight中。首先,提交任务部分的代码是几乎不需要变(需要变的是指定Map和Reduce任务的参数,后文有说明),只要我们还在Windows系统中提交任务即可。使用.NET编写的Map和Reduce程序需要进行一定修改以便可以在.NET Core下运行。这个在下一小节有详细介绍。

.NET Core

随着.NET Core的日益完善,基于.NET Core开发可以让C# MapReduce程序运行在基于Linux的大数据集群中的程序已经成为可能。在Azure HDInsight(基于Linux)中(Ubuntu16.04)甚至都已经预装了.NET Core。参照上一小节的方法,我们可以让.NET Core编写的MR任务运行在基于Linux的HDInsight中。
首先我们在Azure中创建一个集群基于Linux的HDInsight集群。Linux版的HDInsight使用Ambari作为门户页面(关于Ambari的介绍,官方文档在此),而没有了像Windows版中那样一个页面,同时为了安全考虑YARN,JobHistory及存储的Web管理界面也没有直接暴露出来。访问这些页面需要使用SSH与Linux集群建立隧道,这个道理和我们平时用SSH搭梯子FQ是一样的(官方文档在此)。
首先做一点准备工作,使用https://[cluster-name].azurehdinsight.net这个地址(或直接在集群主页点击仪表盘或Ambari视图)打开Ambari,登陆后,查找并记录下headnode0主机的地址。

如图,Hosts标签页中名称hn0开头的就是我们要找的结点。点开查看详情:

记录下这个ip,后面可以直接使用ip来登陆这个结点。

楼主一般比较喜欢用SecureCRT作为终端,使用BitviseSSH来搭梯子。(虽然它们各自都可以完成另一方的工作,但个人还是喜欢这样搭配使用)
使用BitviseSSH建立与HDInsight Linux集群的通道设置方式见下图:

几个注意的地方,SSH Host的地址不同于集群门户页面地址,其中多了个-ssh,默认ssh用户用户名为sshuser(在创建集群时可以更改)。第二图可以不设置,这样设置可以让我们的BitviseSSH功能保持单一,就是进行转发。第三图最终要的就是端口号。链接成功后,我们可以在Firefox中使用FoxProxy等类似代理软件配置转发。
配置好浏览器转发后,我们打开之前记录的ip的8080端口。由于我们通过SSH建立了隧道,所以这个ip会被正确的路由。

注意:某些情况下,使用headnode0无法访问,可以尝试使用headnode1。

如果一切正常,会再次弹出Ambari的登陆框,登陆后,在这个新的Ambari登陆中的HDFS或YARN界面中的Quick Links菜单中就可以找到我们需要的那些管理网站入口。如图:

HDP2.7版本之后的HDInsight集群,ResourceMananger等管理页面貌似可以直接访问而不再需要上面的限制。

下面大致来介绍如果在基于Linux的集群上运行基于.NET Core的App。我们还是使用上一部分的Map和Reduce程序的代码。为了使用.NET Core的Framework,需要进行一些小小的改造。
新建两个.NET Core控制台应用程序,分别命名为NetCoreMapperNetCoreReducer。其各自的Main方法如下:

namespace NetCoreMapper
{
    public class Program
    {
        public static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                Stream stream = new FileStream(args[0], FileMode.Open, FileAccess.Read, FileShare.Read);
                Console.SetIn(new StreamReader(stream));
            }

            string line;
            while ((line = Console.ReadLine()) != null)
            {
                Console.WriteLine(line);
            }

        }
    }
}
namespace NetCoreReducer
{
    public class Program
    {
        static void Main(string[] args)
        {
            ILoggerFactory loggerFactory = new LoggerFactory()
                .AddConsole()
                .AddDebug();
            ILogger logger = loggerFactory.CreateLogger<Program>();
            logger.LogInformation(
                "This is a test of the emergency broadcast system.");

            string line;
            var count = 0;

            if (args.Length > 0)
            {
                Console.SetIn(new StreamReader(new FileStream(args[0], FileMode.Open, FileAccess.Read)));
            }

            while ((line = Console.ReadLine()) != null)
            {
                count += line.Count(cr => (cr == ' ' || cr == '\n'));
            }
            Console.WriteLine(count);
        }
    }
}

使用命令分别生成项目:

dotnet publish --framework netcoreapp1.0 --configuration release --output publish

把两个项目的输出放到一个文件夹中,按上面的配置,应该最终得到8个文件,其中两个pdb文件可以安全删除。

然后,我们将这些文件上传到Azure Blob Storage与HDInsight集群对应的容器中的/example/coreapp目录下(如果没有这个目录请先新建,如果不使用这个目录,在下文的提交任务的代码中要把程序路径换成相应的路径)

接下来需要修改下提交任务的代码:

private static void SubmitMRJob()
{
    var paras = new MapReduceStreamingJobSubmissionParameters
    {
        Files = new List<string>()
        {
            "/example/coreapp",
        },
        Mapper = "dotnet coreapp/NetCoreMapper.dll",
        Reducer = "dotnet coreapp/NetCoreReducer.dll",
        Input = "/example/data/gutenberg/davinci.txt",
        Output = "/example/data/StreamingOutput/wc.txt"

    };

    Console.WriteLine("Submitting the MR job to the cluster...");
    var jobResponse = _hdiJobManagementClient.JobManagement.SubmitMapReduceStreamingJob(paras);
    var jobId = jobResponse.JobSubmissionJsonResponse.Id;
    Console.WriteLine("Response status code is " + jobResponse.StatusCode);
    Console.WriteLine("JobId is " + jobId);

    Console.WriteLine("Waiting for the job completion ...");

    // Wait for job completion
    var jobDetail = _hdiJobManagementClient.JobManagement.GetJob(jobId).JobDetail;
    while (!jobDetail.Status.JobComplete)
    {
        Thread.Sleep(1000);
        jobDetail = _hdiJobManagementClient.JobManagement.GetJob(jobId).JobDetail;
    }

    // Get job output
    var storageAccess = new AzureStorageAccess(DefaultStorageAccountName, DefaultStorageAccountKey,
        DefaultStorageContainerName);
    var output = (jobDetail.ExitValue == 0)
        ? _hdiJobManagementClient.JobManagement.GetJobOutput(jobId, storageAccess) // fetch stdout output in case of success
        : _hdiJobManagementClient.JobManagement.GetJobErrorLogs(jobId, storageAccess); // fetch stderr output in case of failure

    Console.WriteLine("Job output is: ");

    using (var reader = new StreamReader(output, Encoding.UTF8))
    {
        string value = reader.ReadToEnd();
        Console.WriteLine(value);
    }
}

尤其需要注意的是FilesMapperReducer这三个属性中路径的写法,只有这样MR任务才能成功执行。不要问我怎么知道的,说多了都是泪。
运行任务提交程序,当看到控制台如下输出时表示任务已经成功提交并执行完毕。

否则,控制台会输出错误。
下面总结一下楼主遇到的错误以及MR任务查找错误原因的方法。
查找错误原因,可以访问ResourceManager管理界面(地址一般为headnode:8088),在界面中找到最近出错的任务,如下图:

点击进入任务详情界面:

点击“任务历史”链接进入HistoryServer的UI界面:

在任务历史中,点击图片中标出的Url,可以进入失败的Map或Reduce任务的列表界面:

点击Logs链接可以看到任务的详情,从中一定可以找到任务失败的原因。
下面两图就是楼主因为在SubmitMRJob方法中没有以正确的方式填充Files属性所报的错。

当时,楼主将.NET Core程序所输出的每个文件作为Files属性数组的一条进行提交,导致如图,程序文件被传到Blob后被放在不同的临时目录中,从而导致运行程序失败。
另外楼主遇到的问题还包括,目前Azure HDInsight预装的.NET Core版本较低(1.0.1),而使用VS2017来生成.NETCore App,即使所选的Target Framework为netcoreapp1.0,生成出来的程序也是1.0.4版,直接放到集群的主机上执行会报如下错误。

在使用Hadoop执行前,先直接执行下程序看看是否可以正确启动运行是一种很好的排除错误保证成功率的方法。

The specified framework 'Microsoft.NETCore.App', version '1.0.4' was not found.

  • Check application dependencies and target a framework version installed at:
    /usr/share/dotnet/shared/Microsoft.NETCore.App
  • The following versions are installed:
    1.0.1
  • Alternatively, install the framework version '1.0.4'.

上面的报错可以清楚的了解到,HDInsight集群的主机安装了.NETCore但是版本只有1.0.1,而VS2017生成的.NETCore App至少需要1.0.4版本的运行时。
按照官方文档的说明,卸载旧版并安装新版。安装后继续测试程序是否可以在shell里直接运行知道成功。
特别注意,我们需要在实际干活的结点,即NodeManager运行的结点,安装适当的.NET Core,因为这些结点是实际运行.NET Core程序的结点。参考前文图片,这些结点以wn开头,我们可以在建立隧道后通过IP ssh到这些主机并安装.NET Core以及测试。

如果不方便使用Azure HDInsight,来进行测试,使用HDP沙盒也同样可以。
唯一不便的就是不能使用Microsoft.Azure.Management.HDInsight.Job来方便的提交任务,而需要自行使用hadoop CLI来完成,但这对于我们测试.NET Core编写的业务逻辑没有影响。
关于这个话题详见这篇博文

微软4月份更新版的Azure在线文档中介绍了使用Mono在基于Linux的HDP集群上运行C#编写的MapReduce的方法,具体文章链接(这个连接可能在将来随时发生变化)。

最后

本文及后续文章都是对于将微软技术用于大数据可能性的一种探讨,欢迎大家一起讨论。

本文示例相关示例在此

posted @ 2017-06-05 10:19 hystar 阅读(...) 评论(...) 编辑 收藏