前提
本文主要介绍Hadoop的数据类型,然后写一个入门案例来统计wordcount的单词数量。通过本文的学习,可以了解到hadoop 与java不同的数据类型,然后入门MapReduce程序。
一、内置数据类型介绍
Java 类型 | Hadoop Writable类型 |
---|---|
Boolean | BooleanWritable |
Byte | ByteWritable |
Int | IntWritable |
Float | FloatWritable |
Long | LongWritable |
Double | DoubleWritable |
String | Text (使用UTF8格式存储的文本) |
Map | MapWritable |
Array | ArrayWritable |
Null | NullWritable (当[key,value]中的key或value为空使用) |
数据类型的使用:
package xl;
import org.apache.hadoop.io.*;
import org.junit.Test;
public class HadoopDataTypeTest {
@Test
public void testText(){
System.out.println("==== text ===");
Text text=new Text("lei study hadoop");
System.out.println(text.getLength());
System.out.println(text.find("o"));
System.out.println(text.toString());
System.out.println("=======ArrayWritable======");
ArrayWritable arrayWritable = new ArrayWritable(IntWritable.class);
IntWritable year = new IntWritable(2021);
IntWritable mouth = new IntWritable(4);
IntWritable date = new IntWritable(24);
arrayWritable.set(new IntWritable[]{year,mouth,date});
System.out.println(arrayWritable.get()[0]);
System.out.println(arrayWritable.get()[1]);
System.out.println(arrayWritable.get()[2]);
System.out.println("========== MapWritable =======");
MapWritable mapWritable = new MapWritable();
Text k1 = new Text("name");
Text k2 = new Text("passwd");
mapWritable.put(k1,new Text("lei"));
mapWritable.put(k2, NullWritable.get());
System.out.println(mapWritable.get(k1));
System.out.println(mapWritable.get(k2));
}
}
输出结果:
16
13
lei study hadoop
=======ArrayWritable======
2021
4
24
========== MapWritable =======
lei
(null)
二、wordcount 入门案例
2.1 MapReduce的编程规范养成
我们编写的程序分成三个部分内容:Mapper、Reduce和Drive
1、Mapper阶段
- 用户自定义的Mapper要继承自己的父类
- Mapper的输出数据是key-value的形式
- Mapper中的业务逻辑写在map方法中
- Mapper的输出数据是KV对的形式
- map方法对每个k-v调用一次
2、Reduce阶段
- 用户自定义的Reduce要继承自己的父类
- Reducer的输入数据类型要对应Mapper的输出数据类型,也是k-v
- Reduce的业务逻辑写在reduce方法中
- ReduceTask进程 对每一组相同的k-v调用一次reduce方法
3、Driver阶段
相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象。
2.2 wordcount思路讲解
这边可以准备两个文件:file1.txt 和file2.txt 两个文本文件,需要统计文本中每类单词的出现次数。 文本内容如下:
将文件拆分成split分片,所以每个文件为一个split,并将文件按行分割形成<key,value>对,这里的key是偏移量,由MapReduce自动计算出来的,包含回车所占的字符数。例如“hello lei”的偏移量是0,“bye lei”的偏移量是“12”。然后每行分割,map输出的形式为<单词,1>,键值对发到reduce,reduce将相同的单词的value进行累加后得到最终的键值对输出到文件。
2.3 环境准备
1、创建maven工程
2、在pom.xml文件中添加如下依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
</dependencies>
3、在项目的resources目录下,新建一个文件,命名“log4j2.xml”,在文件中填入。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" strict="true" name="XMLConfig">
<Appenders>
<!-- 类型名为Console,名称为必须属性 -->
<Appender type="Console" name="STDOUT">
<!-- 布局为PatternLayout的方式,
输出样式为[INFO] [2018-01-22 17:34:01][org.test.Console]I'm here -->
<Layout type="PatternLayout"
pattern="[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n" />
</Appender>
</Appenders>
<Loggers>
<!-- 可加性为false -->
<Logger name="test" level="info" additivity="false">
<AppenderRef ref="STDOUT" />
</Logger>
<!-- root loggerConfig设置 -->
<Root level="info">
<AppenderRef ref="STDOUT" />
</Root>
</Loggers>
</Configuration>
2.4 案例实操
1、编写Mapper类
/**
* 自定义的xxxMapper类,需要继承Hadoop提供的Mapper类,并重写map方法
* 四个泛型:两组KV对
* 按照当前wordcount程序来分析:
* KEYIN, :LongWritable 表示文件读取数据的偏移量,简单理解为从哪个位置开始读取数据
* VALUEIN, :Text 实际从文件中读取的数据
*
* KEYOUT :Text,表示一个单词
* VALUEOUT :IntWritable,表示这个单词出现了1次
*/
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
private Text k=new Text();
private IntWritable v=new IntWritable(1);
/**
* @param key KEYIN 输入数据的key
* @param value VALUEIN 输入数据的value
* @param context 上下文对象,负责整个Mapper类中方法的调用
**/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//1、获取到输入的一行数据 把value转为java 2、切割 3、将每个单词拼成kv写出
String value2=value.toString();
String[] words = value2.split(" ");
for (String word : words) {
k.set(word);
context.write(k,v);
}
}
}
2、编写Reducer类
/**
* 自定义的xxxReducer类需要继承Hadoop提供的Reducer类
* 根据当前的wordcount程序分析,4个泛型
* 输入的kv类型
* KEYIN, :Text map端写出的一个单词
* VALUEIN, :IntWritable 表示单词出现了一次
* 输出的kv类型
* KEYOUT, :表示一个单词
* VALUEOUT> :表示单词出现的次数
*/
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
int sum=0;
IntWritable v=new IntWritable();
/**
* @param key keyin ,表示一个单词
* @param values 迭代器对象,表示一个相同的单词,出现次数的封装,能够表明当前的单词总共出现了多少次
* @param context 上下文对象,负责reducer执行过程调度
**/
@Override
protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
// 1 累加求和
sum = 0;
for (IntWritable count : values) {
sum += count.get();
}
// 2 输出
v.set(sum);
context.write(key,v);
}
}
3、编写Driver驱动类
package com.atguigu.mapreduce.wordcount;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WordcountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取配置信息以及封装任务
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2 设置jar加载路径
job.setJarByClass(WordcountDriver.class);
// 3 设置map和reduce类
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
// 4 设置map输出
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path("D:/cs/input"));
FileOutputFormat.setOutputPath(job, new Path("D:/cs/output"));
// 7 提交
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
4、本地测试
本地的输出结果如图所示。
5、集群测试
将该程序打包成jar,提交到服务器上。 执行命令:
类名+ 输入+输出
hadoop jar wc.jar
xl.WordCountDriver /wcinput /wcoutput
三、一个小问题
执行MapReduce 程序的时候报错,如下
2019-07-12 00:51:53,868 INFO mapreduce.Job: Task Id : attempt_1562862697087_0005_m_000003_1001, Status : FAILED [2019-07-12 00:51:52.484]Exception from container-launch. Container id: container_1562862697087_0005_02_000011 Exit code: 127
[2019-07-12 00:51:52.490]Container exited with a non-zero exit code 127. Error file: prelaunch.err. Last 4096 bytes of prelaunch.err : Last 4096 bytes of stderr : /bin/bash: /bin/java: No such file or directory
很好理解 /bin/java 无法执行
在shell 终端执行 /bin/java 无法执行
需要创建一个/bin/java 的软连接 实际指向 jdk 真实目录
我的jdk真实目录是 /opt/sofeware/java8/
所以在shell 终端执行
ln -s /opt/sofeware/java8/ /bin/java
再执行 /bin/java
发现命令可以运行
如果hadoop是集群环境,需要在每台机器上创建软连接
再执行 MapReduce程序 就不会报错了