79-93-Hadoop-MapReduce-序列化

51 篇文章 1 订阅
27 篇文章 0 订阅

79-Hadoop-MapReduce-序列化:

Hadoop 序列化

1)什么是序列化

序列化将内存中的对象,转换成字节序列存储到磁盘(持久化)和网络传输。

反序列化将收到字节序列或磁盘的持久化数据,转换成内存中的对象。

2)为什么要序列化

传输

3)为什么不用 Java 的序列化
Java 的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,Header,继承体系等),不便于在网络中高效传输。

4)Hadoop 序列化特点:
紧凑,快速,支持多语言的交互(多语言互相序列化和反序列化)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-shp0OUdg-1668950316111)(png/1623857128825.png)]

自定义bean 对象实现序列化接口(Writable)

对象就需要实现序列化接口。 具体实现 bean 对象序列化步骤如下 7 步。
(1)必须实现 Writable 接口
(2)反序列化时,需要反射调用空参构造函数,所以必须有空参构造

public FlowBean() {
	super();
}

(3)重写序列化方法

@Override
public void write(DataOutput out) throws IOException {
    out.writeLong(upFlow);
    out.writeLong(downFlow);
    out.writeLong(sumFlow);
}

(4)重写反序列化方法

@Override
public void readFields(DataInput in) throws IOException {
    upFlow = in.readLong();
    downFlow = in.readLong();
    sumFlow = in.readLong();
}

(5)注意反序列化的顺序和序列化的顺序完全一致
(6)要想把结果显示在文件中,需要重写 toString(),可用"\t"分开,方便后续用。
(7)如果需要将自定义的 bean 放在 key 中传输,则还需要实现 Comparable 接口,因为MapReduce 框中的 Shuffle 过程要求对 key 必须能排序。

@Override
public int compareTo(FlowBean o) {
    // 倒序排列,从大到小
    return this.sumFlow > o.getSumFlow() ? -1 : 1;
}

案例测试:https://www.bilibili.com/video/BV1Qp4y1n7EN?p=81

1)需求

统计每一个手机号耗费的总上行流量、总下行流量、总流量

(1)输入数据

1,13879807654,192.169.100.1,www.ceshi.com,2541,485,200
2,13873601632,192.169.100.2,www.xiaomi.com,1526,4875,200
3,14287320327,192.169.100.3,www.ceshi.com,154,154,200
4,15683220121,192.169.100.4,,324,35,200
5,13623230980,192.169.100.5,www.huawei.com,3234,1315,200
6,13879807654,192.169.100.6,,1254,784,200
7,13879807654,192.169.100.7,www.ceshi.com,531,654,200
8,17873601111,192.169.100.1,,4895,200,200
9,16378293013,192.169.100.3,www.xiaohong.com,22,458,404
10,15683220121,192.169.100.4,www.zh.com,100,10,200
11,13623230932,192.169.100.5,www.ceshi.com,157,1587,500
12,8739092,192.169.100.8,,1254,84,200
13,3873242654,192.169.100.9,www.ceshi.com,531,654,200
14,14343601111,192.169.100.10,,895,200,200
15,16378235013,192.169.100.11,www.xiaohong.com,422,458,404
16,15683223411,192.169.100.12,www.zh.com,100,10,200
17,13234520932,192.169.100.13,www.ceshi.com,157,1587,500

(2)输入数据格式:

7 13560436666 120.196.100.99 1116 954 200
id 手机号码 网络 ip 上行流量 下行流量 网络状态码

(3)期望输出数据格式

13560436666 1116 954 2070
手机号码 上行流量 下行流量 总流量

2)需求分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ERIhtjDR-1668950316112)(png/1623857361910.png)]

编写MapReduce程序

(1)编写流量统计的 Bean 对象

package com.zh.mapreduce.writable;

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

/**
 * 1.定义类实现writable接口
 * 2、重写序列化和反序列化
 * 3、空参构造
 * 4、重写tostring用于打印
 */
public class FlowBean implements Writable {
    private long upFlow; //上行流量
    private long downFlow; //下行流量
    private long sumFlow; //总流量
    //空参构造
    public FlowBean() {
    }

    public long getUpFlow() {
        return upFlow;
    }

    public void setUpFlow(long upFlow) {
        this.upFlow = upFlow;
    }

    public long getDownFlow() {
        return downFlow;
    }

    public void setDownFlow(long downFlow) {
        this.downFlow = downFlow;
    }

    public long getSumFlow() {
        return sumFlow;
    }

    public void setSumFlow() {
        this.sumFlow = this.upFlow + this.downFlow;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeLong(upFlow);
        out.writeLong(downFlow);
        out.writeLong(sumFlow);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.upFlow = in.readLong();
        this.downFlow = in.readLong();
        this.sumFlow = in.readLong();
    }

    @Override
    public String toString() {
        return upFlow + "\t" + downFlow + "\t" + sumFlow;
    }
}

(2)编写 Mapper 类

package com.zh.mapreduce.writable;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
    private Text outK = new Text();
    private FlowBean outV = new FlowBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //获取一行,1	13879807654	192.169.100.1	www.ceshi.com		2541	485		200
        String line = value.toString();

        //切割
        String[] split = line.split(",");

        //抓取要的数据,手机号,上行流量,下行流量
        String phone = split[1];
        String up = split[split.length - 3];
        String down = split[split.length - 2];

        //封装
        outK.set(phone);
        outV.setUpFlow(Long.parseLong(up));
        outV.setDownFlow(Long.parseLong(down));
        outV.setSumFlow();

        //写出
        context.write(outK, outV);
    }
}

(3)编写 Reducer 类

package com.zh.mapreduce.writable;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class FlowReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
    private FlowBean outv = new FlowBean();
    @Override
    protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
        //遍历集合累加值
        long totalup = 0;
        long totaldown = 0;
        for (FlowBean value : values){
            totalup+=value.getUpFlow();
            totaldown+=value.getDownFlow();
        }
        //封装outk,outv
        outv.setUpFlow(totalup);
        outv.setDownFlow(totaldown);
        outv.setSumFlow();
        //写出
        context.write(key,outv);
    }
}

(4)编写 Driver 驱动类

package com.zh.mapreduce.writable;

import com.zh.mapreduce.wordcount.WordCountMapper;
import com.zh.mapreduce.wordcount.WordCountReducer;
import com.zh.mapreduce.wordcount.WorkCountDriver;
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;

import java.io.IOException;

public class FlowDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //1.获取job
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf);

        //2.设置jar包路径
        job.setJarByClass(FlowDriver.class);

        //3.关联mapper和reducer
        job.setMapperClass(FlowMapper.class);
        job.setReducerClass(FlowReducer.class);

        //4.设置map输出的k,v类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(FlowBean.class);

        //5.设置最终输出的k,v类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(FlowBean.class);

        //6.设置输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path("E:\\input\\input-flow"));
        FileOutputFormat.setOutputPath(job, new Path("E:\\output\\output-flow"));

        //7.提交到job
        boolean result = job.waitForCompletion(true);//监控和打印job信息

        System.exit(result ? 0 : 1);
    }
}


FlowBean.class);

        //6.设置输入路径和输出路径
        FileInputFormat.setInputPaths(job, new Path("E:\\input\\input-flow"));
        FileOutputFormat.setOutputPath(job, new Path("E:\\output\\output-flow"));

        //7.提交到job
        boolean result = job.waitForCompletion(true);//监控和打印job信息

        System.exit(result ? 0 : 1);
    }
}

在这里插入图片描述

Hadoop-MapReduce-切片相关:

MapReduce 框架原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5TBbAQZQ-1668950596581)(png/1623916056588.png)]

InputFormat 数据输入

1切片与MapTask并行度决定机制

1)问题引出

MapTask 的并行度决定 Map 阶段的任务处理并发度,进而影响到整个 Job 的处理速度。

思考:1G 的数据,启动 8 个 MapTask,可以提高集群的并发处理能力。那么 1K 的数

据,也启动 8 个 MapTask,会提高集群性能吗?MapTask 并行任务是否越多越好呢?哪些因

素影响了 MapTask 并行度?

2)MapTask 并行度决定机制

数据块:Block 是 HDFS 物理上把数据分成一块一块。数据块是 HDFS 存储数据单位。(HDFS128M)

数据切片(逻辑上切片):数据切片只是在逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行

存储。数据切片是 MapReduce 程序计算输入数据的单位,一个切片会对应启动一个 MapTask。

数据切片与MapTask并行度决定机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZkcZm7l-1668950596582)(png/1623916087333.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2majaTyR-1668950596582)(png/1623916881251.png)]

Job 提交流程源码和切片源码详解

1Job提交流程源码详解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m7pcfgmO-1668950596583)(png/1623938770002.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rzIb9wKn-1668950596583)(png/1623938817846.png)]

waitForCompletion()
submit();
// 1 建立连接
connect();
// 1)创建提交 Job 的代理
new Cluster(getConfiguration());
// (1)判断是本地运行环境还是 yarn 集群运行环境
initialize(jobTrackAddr, conf); 
// 2 提交 job
submitter.submitJobInternal(Job.this, cluster)
// 1)创建给集群提交数据的 Stag 路径
Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
// 2)获取 jobid ,并创建 Job 路径
JobID jobId = submitClient.getNewJobID();
// 3)拷贝 jar 包到集群
copyAndConfigureFiles(job, submitJobDir);
rUploader.uploadFiles(job, jobSubmitDir);
// 4)计算切片,生成切片规划文件
writeSplits(job, submitJobDir);
maps = writeNewSplits(job, jobSubmitDir);
input.getSplits(job);
// 5)向 Stag 路径写 XML 配置文件
writeConf(conf, submitJobFile);
conf.writeXml(out);
// 6)提交 Job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(),

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DtCRVTKJ-1668950596583)(png/1623993316065.png)]

2)FileInputFormat 切片源码解析(input.getSplits(job))

(1)程序先找到你数据存储的目录。

(2)开始遍历处理(规划切片)目录下的每一个文件

(3)遍历第一个文件ss.txt

a)获取文件大小fs.sizeOf(ss.txt)

b)计算切片大小

computeSplitSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M

c)默认情况下,切片大小=blocksize

d)开始切,形成第1个切片:ss.txt—0:128M 第2个切片ss.txt—128:256M 第3个切片ss.txt—256M:300M

(每次切片时,都要判断切完剩下的部分是否大于块的1.1倍,不大于1.1倍就划分一块切片)

e)将切片信息写到一个切片规划文件中

f)整个切片的核心过程在getSplit()方法中完成

g)InputSplit只记录了切片的元数据信息,比如起始位置、长度以及所在的节点列表等。

(4)提交切片规划文件到YARN上,YARN上的MrAppMaster就可以根据切片规划文件计算开启MapTask个数。

源码(他娘的,自己多debug好了):

job.waitForCompletion(true);//监控和打印job信息
	submit()提交
		ensureState(JobState.DEFINE);状态判断,defalue,running,以及cluster
		setUseNewAPI();新旧版本兼容
		connect();连接,idea本地,或者集群
			initialize(jobTrackAddr, conf);初始化yarnclient和logcalclient
				localProviderList = {ArrayList@1599}  size = 2
                 0 = {YarnClientProtocolProvider@1603} 
                  Class has no fields
                 1 = {LocalClientProtocolProvider@1604} 
                  Class has no fields
		submitter.submitJobInternal(Job.this, cluster);提交流程入口
		checkSpecs(Job job);检查
			checkOutputSpecs(JobContext job);检查输出路径是否存在,存在报错,不能存在
		job.getConfiguration();获取默认core-default.xml配置
		JobSubmissionFiles.getStagingDir(cluster, conf);Stag 路径
		submitClient.getNewJobID();//获取jobid,job_local1628542666_0001
			临时路径file:/tmp/hadoop/mapred/staging/º1628542666/.staging/job_local1628542666_0001
		copyAndConfigureFiles(job, submitJobDir);拷贝 jar 包到集群
			rUploader.uploadResources(job, jobSubmitDir);上传jar包
		int maps = writeSplits(job, submitJobDir);获取切片
			writeNewSplits(job, jobSubmitDir);
				getSplits(JobContext job);
					listStatus(job);输入路径下的文件信息,长度,等
						具体分片逻辑
						while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                        blkLocations[blkIndex].getHosts(),
                        blkLocations[blkIndex].getCachedHosts()));
            bytesRemaining -= splitSize;
          				}对每一个文件单独进行分片,分片完成后会有临时的分片信息以及校验到临时路径下
         writeConf(conf, submitJobFile);写入xml
         	 conf.writeXml(out);
         submitter.submitJobInternal(Job.this, cluster);提交
   		 state = JobState.RUNNING;修改状态

2)FileInputFormat 切片源码解析(input.getSplits(job))

(1)程序先找到你数据存储的目录。

(2)开始遍历处理(规划切片)目录下的每一个文件

(3)遍历第一个文件ss.txt

​ a)获取文件大小fs.sizeOf(ss.txt)

​ b)计算切片大小

​ computeSplitSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M

​ c)默认情况下,切片大小=blocksize

​ d)开始切,形成第1个切片:ss.txt—0:128M 第2个切片ss.txt—128:256M 第3个切片ss.txt—256M:300M

(每次切片时,都要判断切完剩下的部分是否大于块的1.1倍,不大于1.1倍就划分一块切片)

​ e)将切片信息写到一个切片规划文件中

​ f)整个切片的核心过程在getSplit()方法中完成

​ g)InputSplit只记录了切片的元数据信息,比如起始位置、长度以及所在的节点列表等。

(4)提交切片规划文件到YARN上,YARN上的MrAppMaster就可以根据切片规划文件计算开启MapTask个数。

FileInputFormat 切片机制

1、切片机制

(1)简单地按照文件的内容长度进行切片

(2)切片大小,默认等于Block大小

(3)切片时不考虑数据集整体,而是逐个针对每一个文件单独切片

2、案例分析**

(1)输入数据有两个文件:

file1.txt 320M

file2.txt 10M

(2)经过FileInputFormat的切片机制

运算后,形成的切片信息如下:

file1.txt.split1-- 0~128

file1.txt.split2-- 128~256

file1.txt.split3-- 256~320

file2.txt.split1-- 0~10M

FileInputFormat切片大小的参数配置

(1)源码中计算切片大小的公式

Math.max(minSize, Math.min(maxSize, blockSize));

mapreduce.input.fileinputformat.split.minsize=1 默认值为1

mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue 默认值Long.MAXValue

因此,默认情况下,切片大小=blocksize。

2)切片大小设置

maxsize(切片最大值):参数如果调得比blockSize小,则会让切片变小,而且就等于配置的这个参数的值。

minsize(切片最小值):参数调的比blockSize大,则可以让切片变得比blockSize还大。

(3)获取切片信息API

// 获取切片的文件名称

String name = inputSplit.getPath().getName();

// 根据文件类型获取切片信息

FileSplit inputSplit = (FileSplit) context.getInputSplit();

Hadoop-MapReduce-切片相关-其他inputformat:

ctrl+h查看inputformat的所有实现类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fOZ9cFz-1668950817097)(png/1624064818499.png)]

TextInputFormat

1FileInputFormat实现类

思考:在运行 MapReduce 程序时,输入的文件格式包括:基于行的日志文件、二进制

格式文件、数据库表等。那么,针对不同的数据类型,MapReduce 是如何读取这些数据的呢?

FileInputFormat 常见的接口实现类包括:TextInputFormat、KeyValueTextInputFormat、

NLineInputFormat、CombineTextInputFormat 和自定义 InputFormat 等。

2TextInputFormat

TextInputFormat 是默认的 FileInputFormat 实现类。按行读取每条记录。键是存储该行在整个文件中的起始字节偏移量, LongWritable 类型。值是这行的内容,不包括任何行终止

以下是一个示例,比如,一个分片包含了如下 4 条文本记录。

Rich learning form 
Intelligent learning engine 
Learning more convenient 
From the real demand for more close to the enterprise 

每条记录表示为以下键/值对:

(0,Rich learning form) 
(20,Intelligent learning engine) 
(49,Learning more convenient) 
(74,From the real demand for more close to the enterprise) 

CombineTextInputFormat 切片机制

框架默认的 TextInputFormat 切片机制是对任务按文件规划切片,不管文件多小,都会

是一个单独的切片,都会交给一个 MapTask,这样如果有大量小文件,就会产生大量的

MapTask,处理效率极其低下。

1)应用场景:

CombineTextInputFormat 用于小文件过多的场景,它可以将多个小文件从逻辑上规划到

一个切片中,这样,多个小文件就可以交给一个 MapTask 处理。

2)虚拟存储切片最大值设置

CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m

注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。

3)切片机制

生成切片过程包括:虚拟存储过程和切片过程二部分。

CombineTextInputFormat切片机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uwJYMBCZ-1668950817098)(png/1624065080537.png)]

(1)虚拟存储过程:

将输入目录下所有文件大小,依次和设置的

setMaxInputSplitSize 值比较,如果不

大于设置的最大值,逻辑上划分一个块。如果输入文件大于设置的最大值且大于两倍,

那么以最大值切割一块;当剩余数据大小超过设置的最大值且不大于最大值 2 倍,此时

将文件均分成 2 个虚拟存储块(防止出现太小切片)。

例如 setMaxInputSplitSize 值为 4M,输入文件大小为 8.02M,则先逻辑上分成一个

4M。剩余的大小为 4.02M,如果按照 4M 逻辑划分,就会出现 0.02M 的小的虚拟存储

文件,所以将剩余的 4.02M 文件切分成(2.01M 和 2.01M)两个文件。

(2)切片过程:

(a)判断虚拟存储的文件大小是否大于 setMaxInputSplitSize 值,大于等于则单独

形成一个切片。

(b)如果不大于则跟下一个虚拟存储文件进行合并,共同形成一个切片。

(c)测试举例:有 4 个小文件大小分别为 1.7M、5.1M、3.4M 以及 6.8M 这四个小

文件,则虚拟存储之后形成 6 个文件块,大小分别为:

1.7M,(2.55M、2.55M),3.4M 以及(3.4M、3.4M)

最终会形成 3 个切片,大小分别为:

(1.7+2.55)M,(2.55+3.4)M,(3.4+3.4)M

CombineTextInputFormat 案例实操(使用尚硅谷案例进行测试学习,请bilibili搜索尚硅谷)

1)需求

将输入的大量小文件合并成一个切片统一处理。

(1)输入数据

准备 4 个小文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ulxSvT4R-1668950817098)(png/1624066571121.png)]

(2)期望

期望一个切片处理 4 个文件

2)实现过程

(1)不做任何处理,运行 1.8 节的 WordCount 案例程序,观察切片个数为 4。

2021-06-19 09:34:49,818 INFO [org.apache.hadoop.mapreduce.JobSubmitter] - number of splits:4

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4gCaXBvc-1668950817099)(png/1624066690184.png)]

(b)运行如果为 3 个切片。

number of splits:3 

(3)在 WordcountDriver 中增加如下代码,运行程序,并观察运行的切片个数为 1。

(a)驱动中添加代码如下:

// 如果不设置 InputFormat,它默认用的是 TextInputFormat.class 
job.setInputFormatClass(CombineTextInputFormat.class); 
//虚拟存储切片最大值设置 20m 
CombineTextInputFormat.setMaxInputSplitSize(job, 20971520); 

(b)运行如果为 1 个切片

number of splits:1

学习路径:https://space.bilibili.com/302417610/,如有侵权,请联系q进行删除:3623472230

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值