ps.实际生产环境中并不会使用mapReduce,而是spark和flink,但是它可以建立分布式的思想。
1.MapReduce框架
2.mapReduce小项目练习
ps.基本流程:一般都是在代码层面引入hadoop依赖,然后在windows环境下进行代码编写测试,没有问题的话,把代码打包成jar包,然后拖入xShell,利用liunx执行测试.
(1) 数据序列化的类型:
Java类型 | Hadoop Writable类型 |
Boolean | BooleanWritable |
Byte | ByteWritable |
Int | IntWritable |
Float | FloatWritable |
Long | LongWritable |
Double | DoubleWritable |
String | Text |
Map | MapWritable |
Array | ArrayWritable |
Null | NullWritable |
(1) 前期准备
1.新建一个maven项目,在main/java/目录下面新建一个package,输入com.atguigu.mr.wordcount,在package下面新建三个类,分别是driver,map,reduce.
2.在核心配置文件pom.xml里面引入hadoop依赖和日志依赖
<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文件下新建xml文件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>
4.在核心配置文件pom.xml里面引入“打包插件”依赖
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
然后在maven处点击刷新即可。
(2)代码编写
建立三个类,wcMapper(负责map) wcReducer(负责reduce) wcDriver(主程序)
1.map模块的编写
//作用:输出(word,1) // 继承自Mapper<k,v,k,v> =>第一个k代表行号,v代表内容;第二个key代表word,v代表数字
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
// 数据开始之前调用setup
protected void setup(Context context) throws IOException, InterruptedException {
System.out.println("setup方法执行了");
}
private Text outk = new Text(); // 输出的k(类型为Text)
private IntWritable outv = new IntWritable(1); // 输出的1(类型为IntWritable)
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// 获取当前输入的数据
String line = value.toString();
// 切割数据
String[] datas = line.split(" ");
// 遍历集合 封装 输出数据的key和value
for (String data : datas) {
outk.set(data);
context.write(outk, outv);
}
System.out.println("map方法执行了");
}
// 数据结束之后调用cleanup
protected void cleanup(Context context)
throws IOException, InterruptedException {
System.out.println("cleanup执行了");
}
}
2.reduce模块编写
//作用:将mapper输出的(word,1)按照word累加
public class WordCountReducer extends Reducer<Text, IntWritable,Text, IntWritable> {
private Text outk = new Text();
private IntWritable outv = new IntWritable();
@Override //终于意识到继承的好处了,函数内部变量都不用写
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int total = 0;
// 遍历values
for (IntWritable value : values) { //迭代器循环还有这用处
// 对value累加进行累加 输出结果
total+=value.get();
}
// 封装key和value
outk.set(key);
outv.set(total);
context.write(outk, outv);
}
}
3.driver模块的编写
// 作用:新建job并进行一系列设置(套路化)
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 声明配置对象
Configuration conf = new Configuration();
// 声明Job对象
Job job = Job.getInstance(conf);
// 指定当前Job的驱动类(找到包含driver程序的jar包(提前打包好的三个程序),分发到各个容器里面)
job.setJarByClass(WordCountDriver.class); //driver.class起定位jar包的作用
// 让程序与 Mapper和Reducer产生关联
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 指定Map段输出数据的key的类型和输出数据value的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 指定最终输出结果的key的类型和value的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 指定输入数据的目录 和 输出数据的目录
FileInputFormat.setInputPaths(job, new Path(args[0])); //输入文件目录(提交命令的第一个参数)
FileOutputFormat.setOutputPath(job, new Path(args[1])); //输出文件目录(第二个参数)
// 提交Job到yarn集群
job.waitForCompletion(true);
}
}
对以上代码的解释:
> 利用maven中的打包程序把package打成jar包,找到jar包目录,将它拖到桌面,重命名为wc.jar
> 将wc.jar包拖入集群,在集群中通过jar包执行指令:
> 启动9870端口号,在其中的input目录下上传测试文件
> 输入linux命令进行测试
(1)启动集群
[atguigu@hadoop102 hadoop-3.1.3]sbin/start-dfs.sh
[atguigu@hadoop103 hadoop-3.1.3]$ sbin/start-yarn.sh
(2)提交命令
// hadopp+jar+jar包名称 +主类名称 +输入路径+输出路径
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop jar wc.jar
com.atguigu.mr.wordcount.WordCountDriver /user/atguigu/input /user/atguigu/output
3.mr序列化
序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输
场景:比如mapTask在hadoop101,reduceTask在hadoop102,跨设备就需要序列化操作
(2)反序列化就是将收到字节序列(或其他数据传输协议)或者是硬盘的持久化数据,转换成内存中的对象。
序列化步骤
(1)必须实现Writable接口
ps.Java的序列化是一个重量级序列化框架(Serializable),不便于在网络中高效传输。hadoop自己开发了一套序列化机制(Writable)
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造
(3)重写序列化方法
(4)重写反序列化方法
(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写toString(),且用"\t"分开,方便后续用
(7)如果需要将自定义的bean放在key中传输,则还需要实现comparable接口,因为mapreduce框中的shuffle过程一定会对key进行排序
序列化小练习:
统计每一个手机号耗费的总上行流量、总下行流量、总流量
(1)输入数据格式:
7 13560436666 120.196.100.99 1116 954 200 id 手机号码 网络ip 上行流量 下行流量 网络状态码 |
(2)期望输出数据格式
13560436666 1116 954 2070 手机号码 上行流量 下行流量 总流量 |
思路:手机号为key,流量为value
在企业开发中往往常用的基本序列化类型不能满足所有需求,比如在Hadoop框架内部传递一个bean对象,那么该对象就需要实现序列化接口。
代码编写
(1)编写流量统计的Bean对象
1.继承Writable接口并实现里面的方法
public class FlowBean implements Writable {
// 序列化方法
public void write(DataOutput out) throws IOException {
}
// 反序列化方法
public void readFields(DataInput in) throws IOException {
}
}
2. 定义三个属性并一键生成set/get方法
// 三个属性
private Integer upFlow; // 上行流量
private Integer downFlow; // 下行流量
private Integer sumFlow; // 总流量
// 一键生成三者的set和get方法
public Integer getUpFlow() {
return upFlow;
}
public void setUpFlow(Integer upFlow) {
this.upFlow = upFlow;
}
public Integer getDownFlow() {
return downFlow;
}
public void setDownFlow(Integer downFlow) {
this.downFlow = downFlow;
}
public Integer getSumFlow() {
return sumFlow;
}
public void setSumFlow() { //修改sumFlow的set方法,改为无参的
this.sumFlow = this.upFlow+this.downFlow; // 修改方法内部
}
3. 填充序列化与反序列化方法
// 序列化方法
public void write(DataOutput out) throws IOException {
out.writeInt(upFlow);
out.writeInt(downFlow);
out.writeInt(sumFlow);
}
// 反序列化方法
public void readFields(DataInput in) throws IOException {
upFlow = in.readInt();
downFlow = in.readInt();
sumFlow = in.readInt();
}
4.重新toString()方法,按规定的格式输出,可用”\t”分开,方便后续用(Alt+Insert)
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
(2) 编写FlowMapper类
public class FlowCountMapper extends Mapper<LongWritable, Text, Text, FlowBean>{
// 创建输出对象
FlowBean v = new FlowBean();
Text k = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行
String line = value.toString();
// 2 切割字段
String[] fields = line.split("\t");
// 3 封装对象
// 取出手机号码
String phoneNum = fields[1];
// 取出上行流量和下行流量(倒着数,因为正着数可能有空格)
long upFlow = Long.parseLong(fields[fields.length - 3]);
long downFlow = Long.parseLong(fields[fields.length - 2]);
k.set(phoneNum);
v.set(downFlow, upFlow);
// 4 写出
context.write(k, v);
}
}
(3) 编写flowCountReducer类
public class FlowCountReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context)throws IOException, InterruptedException {
long sum_upFlow = 0;
long sum_downFlow = 0;
// 1 遍历所用bean,将其中的上行流量,下行流量分别累加
for (FlowBean flowBean : values) {
sum_upFlow += flowBean.getUpFlow();
sum_downFlow += flowBean.getDownFlow();
}
// 2 封装对象
FlowBean resultBean = new FlowBean(sum_upFlow, sum_downFlow);
// 3 写出
context.write(key, resultBean);
}
}
(4)编写driver驱动
public class FlowsumDriver {
public static void main(String[] args) throws IllegalArgumentException, IOException, ClassNotFoundException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[] { "e:/input/inputflow", "e:/output1" };
// 1 获取配置信息,或者job对象实例
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2 指定本程序的jar包所在的本地路径
job.setJarByClass(FlowsumDriver.class);
// 3 指定本业务job要使用的mapper/Reducer业务类
job.setMapperClass(FlowCountMapper.class);
job.setReducerClass(FlowCountReducer.class);
// 4 指定mapper输出数据的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
// 5 指定最终输出的数据的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 6 指定job的输入原始文件所在目录
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 将job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}