5.MapReduce概述

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);
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值