本文适合大数据初学者学习MapReduce统计分析业务问题的步骤和基础的MapReduce编程方法,初步掌握Hadoop对计算任务的管理。
本文末尾有全部数据集和完整代码连接。
1.准备工作
安装Hadoop:Hadoop 3.3.2 离线安装-CSDN博客
按照好Hadoop之后要检查一下datanode运行情况,Hadoop3.x的默认端口是9870
2.上传数据文件到HDFS
准备数据集
将数据上传到HDFS
[root@master sbin]# hadoop fs -mkdir -p /mapred-case`
`[root@master sbin]# hadoop fs -put /home/国家.txt /mapred-case/国家.txt`
`[root@master sbin]# hadoop fs -put /home/类型.txt /mapred-case/类型.txt`
`[root@master sbin]# hadoop fs -put /home/评分.txt /mapred-case/评分.txt`
`[root@master sbin]# hadoop fs -put /home/评价.txt /mapred-case/评价.txt`
`[root@master sbin]# hadoop fs -chmod a+w /mapred-case
3.编写统计分析代码
3.1 Windows系统配置Hadoop开发环境
在windows上开发和调试Hadoop代码需要下载编译好的Hadoop二进制包。
还要下载winutils,放到Hadoop的bin目录。
winutils的下载地址是:吴所谓/winutils
注意这里的Hadoop版本和服务器上的Hadoop版本虽然不一致,但是不影响程序调试。
并且windows上的Hadoop不需要启动,因为这个步骤只是为了解决MapReduce程序运行开始检测Hadoop环境会报错的问题。
配置Hadoop环境变量,并且将D:\Dataware\data_cmpt\hadoop\hadoop-2.8.5\bin 添加到Path环境变量。
3. 2 . 新建 maven 工程 , 添加 Had o o p 依赖 和 配置 信息
` `<dependencies>`
`<!--` `Hadoop相关依赖包-->`
`<dependency>`
`<groupId>org.apache.hadoop</groupId>`
`<artifactId>hadoop-common</artifactId>`
`<version>3.3.1</version>`
`</dependency>`
`<dependency>`
`<groupId>org.apache.hadoop</groupId>`
`<artifactId>hadoop-hdfs</artifactId>`
`<version>3.3.1</version>`
`</dependency>`
`<dependency>`
`<groupId>org.apache.hadoop</groupId>`
`<artifactId>hadoop-client</artifactId>`
`<version>3.3.1</version>`
`</dependency>`
`<dependency>`
`<groupId>junit</groupId>`
`<artifactId>junit</artifactId>`
`<version>4.12</version>`
`</dependency>`
`</dependencies>
resources目录下增加core-site.xml文件,并添加如下配置信息
` `<property>`
`<!--` `URI 定义主机名称和 namenode 的 RPC 服务器工作的端口号 -->`
`<name>fs.defaultFS</name>`
`<value>hdfs://10.16.60.31:8020</value>`
`</property>
resources目录下增加mapred-site.xml文件,并添加如下配置信息
<configuration>`
`<!-- 远程提交到 Linux 的平台上 -->`
`<property>`
`<name>mapred.remote.os</name>`
`<value>Linux</value>`
`<description>Remote` `MapReduce` `framework's` `OS,` `can` `be` `either` `Linux` `or` `Windows</description>`
`</property>`
`<!--允许跨平台提交 解决 /bin/bash:` `line` `0:` `fg:` `no` `job` `control` `-->`
`<property>`
`<name>mapreduce.app-submission.cross-platform</name>`
`<value>true</value>`
`</property>`
`</configuration>
添加log4j.properties文件,用于配置日志输出信息。
log4j.appender.A1.Encoding=UTF-8`
`log4j.rootLogger=INFO,` `stdout`
`log4j.appender.stdout=org.apache.log4j.ConsoleAppender`
`log4j.appender.stdout.layout=org.apache.log4j.PatternLayout`
`log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE}` `|` `%-5.5p` `|` `%-16.16t` `|` `%-32.32c{1}` `|` `%-32.32C` `%4L` `|` `%m%n
3. 3 . 按照国家和地区统计电影的产地信息
首先看下国家.txt中的数据
新建Map类,用于统计文档中的单词信息。
` `//` `Mapper抽象类的核心方法,三个参数`
`public` `void` `map(Object` `key,` `// 首字符偏移量`
`Text` `value,` `// 文件的一行内容`
`Context` `context)` `//` `Mapper端的上下文,与 OutputCollector 和 Reporter 的功能类似`
`throws` `IOException,` `InterruptedException` `{`
`String[]` `ars` `=` `value.toString().split("['.;,?| \t\n\r\f]");`
`for` `(String` `tmp` `:` `ars)` `{`
`if` `(tmp` `==` `null` `||` `tmp.length()` `<=` `0)` `{`
`continue;`
`}`
`word.set(tmp);`
`context.write(word,` `one);`
`}`
`}`
`
新建Reduce类,用于文档中每个单词出现的次数。
` `//` `Reducer抽象类的核心方法,三个参数`
`public` `void` `reduce(Text` `key,` `//` `Map端 输出的 key 值`
`Iterable<IntWritable>` `values,` `//` `Map端 输出的 Value 集合(相同key的集合)`
`Context` `context)` `//` `Reduce 端的上下文,与 OutputCollector 和 Reporter 的功能类似`
`throws` `IOException,` `InterruptedException` `{`
`int` `sum` `=` `0;`
`for` `(IntWritable` `val` `:` `values)` `// 遍历 values集合,并把值相加`
`{`
`sum` `+=` `val.get();`
`}`
`map.put(key.toString(),` `sum);`
`System.out.println(new` `SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new` `Date())` `+` `":"` `+` `key` `+` `"出现了"` `+` `sum);`
`}`
`
新建主程序
public` `class` `CountryMain` `{`
`public` `static` `void` `main(String[]` `args)` `throws` `Exception` `{`
`Configuration` `conf` `=` `new` `Configuration();`
`//` `if` `(args.length` `!=` `2)` `{`
`//` `System.exit(2);`
`//` `}`
`String` `input` `=` `"/mapred-case/国家.txt";`
`String` `output` `=` `"/mapred-case/国家分布统计";`
`Job` `job` `=` `new` `Job(conf,` `"CountryCount");` `// 新建一个 job,传入配置信息`
`job.setJarByClass(CountryMain.class);` `// 设置 job 的主类`
`job.setMapperClass(CountryMap.class);` `// 设置 job 的 Mapper 类`
`job.setCombinerClass(CountryReduce.class);` `// 设置 job 的 作业合成类`
`job.setReducerClass(CountryReduce.class);` `// 设置 job 的 Reducer 类`
`job.setOutputKeyClass(Text.class);` `// 设置 job 输出数据的关键类`
`job.setOutputValueClass(IntWritable.class);` `// 设置 job 输出值类`
`FileInputFormat.addInputPath(job,` `new` `Path(input));` `// 输入路径(数据所在目录)`
`FileOutputFormat.setOutputPath(job,` `new` `Path(output));` `// 输出路径(必须不存在的文件夹)`
`boolean` `result` `=` `false;`
`try` `{`
`result` `=` `job.waitForCompletion(true);`
`}` `catch` `(Exception` `e)` `{`
`e.printStackTrace();`
`}`
`System.out.println(new` `SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new` `Date())` `+`
`(result ? "电影类型(Country)统计完毕!!!"` `:` `"统计失败!!!"));`
`}`
`}`
`
IDEA中启动主程序,可以看到程序执行日志信息如下:
浏览器打开Hadoop管理页面,下载part-r-00000文件,并用记事本打开
可以看到电影数据已经按照国家这个分类统计出来了。
3.4 电影评论数量排行榜
首先看下数据,这是电影名称和评论数量的列表。但是评论的数量并不是有序的,我们需要将电影名按照评论数量排序。
新建一个Map类,将文档中的电影名和评论数量存入一个HashMap中。并调用上一步的排序函数进行排序。
` `protected` `void` `map(LongWritable` `k1,` `Text` `v1,` `Context` `context)` `throws` `IOException,` `InterruptedException` `{`
`String` `line` `=` `v1.toString();`
`String[]` `fields` `=` `line.split("\t");`
`String` `province` `=` `fields[0];`
`if` `(isNumeric(fields[1])){`
`Integer` `critics` `=` `Integer.parseInt(fields[1]);`
`map.put(province,` `critics);`
`}`
`}`
`
新建一个启动类,创建一个Hadoop Job,并执行上一步的统计和排序函数。
public` `class` `CommentMain` `{`
`public` `static` `void` `main(String[]` `args)` `{`
`try` `{`
`// 运行jar包程序指令输入错误,直接退出程序`
`//` `if` `(args.length` `!=` `2)` `{`
`//` `System.exit(100);`
`//` `}`
`String` `input` `=` `"/mapred-case/评价.txt";`
`String` `output` `=` `"/mapred-case/评价数量统计";`
`Configuration` `conf` `=` `new` `Configuration();//job需要的配置参数`
`Job` `job` `=` `Job.getInstance(conf,` `"CommentMain");//创建一个job作业`
`job.setJarByClass(CommentMain.class);//设置入口类`
`FileInputFormat.setInputPaths(job,` `new` `Path(input));//指定输入路径(可以是文件,也可以是目录)`
`FileOutputFormat.setOutputPath(job,` `new` `Path(output));//指定输出路径(只能是指定一个不存在的目录)`
`job.setMapperClass(CommentMap.class);`
`// 指定K2的输出数据类型`
`job.setMapOutputKeyClass(Text.class);`
`// 指定v2的输出数据类型`
`job.setMapOutputValueClass(IntWritable.class);`
`// 指定Reduce阶段的相关类`
`job.setNumReduceTasks(0);`
`//提交作业job`
`job.waitForCompletion(true);`
`}` `catch` `(Exception` `e)` `{`
`e.printStackTrace();`
`}`
`}`
`}`
`
执行成功之后,我们看下执行日志。
浏览器中打开Hadoop管理页面,并下载执行结果文件。
看下执行结果,电影名称已经按照评论数量降序排列好了。
3.5 电影评分分布
首先看一下数据,文档中是电影的评分。
新建Map程序,将评分写入上下文。
`
`//` `Mapper抽象类的核心方法,三个参数`
`public` `void` `map(Object` `key,` `// 首字符偏移量`
`Text` `value,` `// 文件的一行内容`
`Context` `context)` `//` `Mapper端的上下文,与 OutputCollector 和 Reporter 的功能类似`
`throws` `IOException,` `InterruptedException` `{`
`String[]` `ars` `=` `value.toString().split("\t\n");`
`for` `(String` `tmp` `:` `ars)` `{`
`if` `(tmp` `==` `null` `||` `tmp.length()` `<=` `0)` `{`
`continue;`
`}`
`word.set(tmp);`
`context.write(word,` `one);`
`}`
`}`
`
新建Reduce类,统计每个评分的梳理,并放入HashMap,key是评分,value是次数。
` `//` `Reducer抽象类的核心方法,三个参数`
`public` `void` `reduce(Text` `key,` `//` `Map端 输出的 key 值`
`Iterable<IntWritable>` `values,` `//` `Map端 输出的 Value 集合(相同key的集合)`
`Context` `context)` `//` `Reduce 端的上下文,与 OutputCollector 和 Reporter 的功能类似`
`throws` `IOException,` `InterruptedException` `{`
`int` `sum` `=` `0;`
`for` `(IntWritable` `val` `:` `values)` `// 遍历 values集合,并把值相加`
`{`
`sum` `+=` `val.get();`
`}`
`map.put(key.toString(),` `sum);`
`System.out.println(new` `SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new` `Date())` `+` `":"` `+` `key` `+` `"出现了"` `+` `sum);`
`}`
`
新建MapReduce程序启动类,创建一个Job,并执行。
public` `class` `ScoreMain` `{`
`public` `static` `void` `main(String[]` `args)` `throws` `Exception` `{`
`Configuration` `conf` `=` `new` `Configuration();`
`//` `if` `(args.length` `!=` `2)` `{`
`//` `System.exit(2);`
`//` `}`
`String` `input` `=` `"/mapred-case/评分.txt";`
`String` `output` `=` `"/mapred-case/评分分布";`
`Job` `job` `=` `new` `Job(conf,` `"ScoreCount");` `// 新建一个 job,传入配置信息`
`job.setJarByClass(ScoreMain.class);` `// 设置 job 的主类`
`job.setMapperClass(ScoreMap.class);` `// 设置 job 的 Mapper 类`
`job.setCombinerClass(ScoreReduce.class);` `// 设置 job 的 作业合成类`
`job.setReducerClass(ScoreReduce.class);` `// 设置 job 的 Reducer 类`
`job.setOutputKeyClass(Text.class);` `// 设置 job 输出数据的关键类`
`job.setOutputValueClass(IntWritable.class);` `// 设置 job 输出值类`
`FileInputFormat.addInputPath(job,` `new` `Path(input));` `// 文件输入`
`FileOutputFormat.setOutputPath(job,` `new` `Path(output));` `// 文件输出`
`boolean` `result` `=` `false;`
`try` `{`
`result` `=` `job.waitForCompletion(true);`
`}` `catch` `(Exception` `e)` `{`
`e.printStackTrace();`
`}`
`System.out.println(new` `SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new` `Date())` `+` `(result ? "电影评分(Score)统计完毕!!!"` `:` `"统计失败!!!"));`
`}`
`}
执行后,统计结果如下。
3.6 电影类型分布
数据是电影的分类
新建Map类,将电影类型写入上下文。
` `//` `Mapper抽象类的核心方法,三个参数`
`public` `void` `map(Object` `key,` `// 首字符偏移量`
`Text` `value,` `// 文件的一行内容`
`Context` `context)` `//` `Mapper端的上下文,与 OutputCollector 和 Reporter 的功能类似`
`throws` `IOException,` `InterruptedException` `{`
`String[]` `ars` `=` `value.toString().split("['.;,?| \t\n\r\f]");`
`for` `(String` `tmp` `:` `ars)` `{`
`if` `(tmp` `==` `null` `||` `tmp.length()` `<=` `0)` `{`
`continue;`
`}`
`word.set(tmp);`
`context.write(word,` `one);`
`}`
`}
新建Reduce类,统计每个分类的数量。
` `//` `Reducer抽象类的核心方法,三个参数`
`public` `void` `reduce(Text` `key,` `//` `Map端 输出的 key 值`
`Iterable<IntWritable>` `values,` `//` `Map端 输出的 Value 集合(相同key的集合)`
`Context` `context)` `//` `Reduce 端的上下文,与 OutputCollector 和 Reporter 的功能类似`
`throws` `IOException,` `InterruptedException` `{`
`int` `sum` `=` `0;`
`for` `(IntWritable` `val` `:` `values)` `// 遍历 values集合,并把值相加`
`{`
`sum` `+=` `val.get();`
`}`
`map.put(key.toString(),` `sum);`
`System.out.println(new` `SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new` `Date())` `+` `":"` `+` `key` `+` `"出现了"` `+` `sum);`
`}
新建MapReduce程序启动类,创建一个Job,并执行。
public` `class` `TypeMain` `{`
`public` `static` `void` `main(String[]` `args)` `throws` `Exception` `{`
`Configuration` `conf` `=` `new` `Configuration();`
`//` `if` `(args.length` `!=` `2)` `{`
`//` `System.exit(2);`
`//` `}`
`String` `input` `=` `"/mapred-case/类型.txt";`
`String` `output` `=` `"/mapred-case/类型分布";`
`Job` `job` `=` `new` `Job(conf,` `"TypeCount");` `// 新建一个 job,传入配置信息`
`job.setJarByClass(TypeMain.class);` `// 设置 job 的主类`
`job.setMapperClass(TypeMap.class);` `// 设置 job 的 Mapper 类`
`job.setCombinerClass(TypeReduce.class);` `// 设置 job 的 作业合成类`
`job.setReducerClass(TypeReduce.class);` `// 设置 job 的 Reducer 类`
`job.setOutputKeyClass(Text.class);` `// 设置 job 输出数据的关键类`
`job.setOutputValueClass(IntWritable.class);` `// 设置 job 输出值类`
`FileInputFormat.addInputPath(job,` `new` `Path(input));` `// 文件输入`
`FileOutputFormat.setOutputPath(job,` `new` `Path(output));` `// 文件输出`
`boolean` `result` `=` `false;`
`try` `{`
`result` `=` `job.waitForCompletion(true);`
`}` `catch` `(Exception` `e)` `{`
`e.printStackTrace();`
`}`
`System.out.println(new` `SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new` `Date())` `+` `(result ? "电影标签(Type)统计完毕!!!"` `:` `"统计失败!!!"));`
`}`
`}
本文全部数据集和代码连接https://download.csdn.net/download/shangjg03/88596022