Hadoop第二篇:使用Maven开发Hadoop编程进阶

Posted 牧竹子

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hadoop第二篇:使用Maven开发Hadoop编程进阶相关的知识,希望对你有一定的参考价值。

Hadoop第二篇:使用Maven开发Hadoop编程进阶

如何进行java代码开发进行符合自己需求的实践,先从Wordcount看看它是如何做的,这里从使用java接口看看一些基本的操作开始。

基础环境

  • win7

  • JDK1.8

  • maven安装

  • IDEA

  • Hadoop在centos上部署完成(单机版,请参看前一篇)

java代码

IDEA创建一个java项目

pom.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bamboo</groupId>
    <artifactId>hadoop-1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.7.5</version>
        </dependency>

        <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.2</version>
        </dependency>
    </dependencies>

</project>

文件的创建和读取

直接本地运行即可

使用mapredace进行数据处理

代码重现wordCount的内容,这里类取名为hadoop2

MapReduce理论

这部分可以跳过直接后面的代码,然后再回过头看看。

MapReduce分成了两个部分:
      1)映射(Mapping)对集合里的每个目标应用同一个操作。如把大象放进冰箱分几个步骤,那么就可以把每个步奏当成一个mapping.
      2)化简(Reducing)遍历集合中的元素来返回一个综合的结果。即,每个步奏的输出结果的过程,这个任务属于reducing。
 
你向MapReduce框架提交一个计算作业时
1.它会首先把计算作业拆分成若干个Map任务,然后分配到不同的节点上去执行,每一个Map任务处理输入数据中的一部分
2.当Map任务完成后,它会经历Shuffle将多个map结果合并生成一些中间文件
3.这些中间文件将会作为Reduce任务的输入数据,经过Reduce处理之后把结果合并输出

Shuffle的主要目标就是把前面若干个Map的输出合并成若干个租(相当于group by)。

Reduce任务的主要目标就是把Shuffle合并后的数据作为输入数据处理后输出
   MapReduce的伟大之处就在于编程人员在不会分布式并行编程的情况下,将自己的程序运行在分布式系统。

案例讲解

在map(v1)->k2函数中,输入端v1代表的是一行数据,输出端的k2可以代表是被引用的专利,在一行数据中所有v2和引用的次数。

在reduce函数中,k2还是被引用的专利,而[v2]是一个数据集,这里是将k2相同的键的v2数据合并起来。最后输出的是自己需要的数据k3代表的是被引用的专利,v3是引用的次数。

以wordcount为例,有如下数据集2份文档
hello word
hello hadoop

处理过程如下

1.每个个文档都有一个mapping
·每读取一行就按空格分割字符串为单词,则每个单词作为key,值默认为1
2.reduce处理:
·把所有的mapping处理结果合并,如单词hello的结果是 hello,[1,1] 因为出现了两次hello,这个过程由框架自己完成 我们不用处理
.按照key遍历所有的vlue集合需要手工处理,这里把所有的1相加就是该单词出现的个数

3.把结果集合输出出来

代码

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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

import java.io.IOException;


/**
 * MapReduce作业:功能和wordcount一样,在运行jar时设置文件输源和结果输出源
 *
 * 补充:
 * 设置命令行参数变量来编程
   这里需要借助Hadoop中的一个类Configured、一个接口Tool、ToolRunner(主要用来运行Tool的子类也就是run方法)

 * <p>源码出自https://blog.csdn.net/yinbucheng/article/details/70243593</p>
 * 拷贝人
 */


public class Hadoop2 {

    //输入资源映射处理
    public static class MyMapper extends Mapper<Object, Text, Text, IntWritable>{

        private Text event = new Text();//字符串
        private final static IntWritable one = new IntWritable(1);//默认字符串的个数为1
        //字符串的分隔
        public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
            int idx = value.toString().indexOf(" ");//空格的位置
            if (idx > 0) {
                String e = value.toString().substring(0, idx);//
                event.set(e);
                context.write(event, one);//反正结果<e,1>
            }
        }
    }

    //对输出结果集处理
    public static class MyReducer extends Reducer<Text,IntWritable,Text,IntWritable> {
        private IntWritable result = new IntWritable();

        //把多个map中返回的<e,1>合并<key,values>即<e,[1,1,1]>后遍历
        public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int sum = 0;
            for (IntWritable val : values) {//遍历相同key的所有结果集,这里保存的是int计算结果和
                sum += val.get();
            }
            result.set(sum);//把结果放入int
            context.write(key, result);//注意:在map或reduce上面的打印语句是没有办法输出的,但会记录到日志文件当中。
        }
    }

    /**
     *  args 在运行jar时设置参数
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
        //如果args的参数长度小于2直接退出程序
        if (otherArgs.length < 2) {
            System.err.println("Usage: EventCount <in> <out>");
            System.exit(2);
        }

        Job job = Job.getInstance(conf, "event count");//构建作业配置job

        //设置该作业所要执行的类
        job.setJarByClass(Hadoop2.class);

        job.setMapperClass(MyMapper.class);//设置map
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        job.setCombinerClass(MyReducer.class);//设置

        //设置自定义的Reducer类以及输出时的类型
        job.setReducerClass(MyReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);


        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));//设置文件输入源的路径
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));//设置结果输出流的路径


        System.exit(job.waitForCompletion(true) ? 0 : 1);//提交作业完成后退出
    }
}

运行代码

java项目打包

mvn package

把包上传到centos上

/mnt/hadoop

创建手工数据源

cd /mnt/hadoop/input/event
[root@localhost input]# touch evenet.log.1
[root@localhost input]# touch evenet.log.2

依次vi打开粘贴如下内容

JOB_NEW ...  
JOB_NEW ...  
JOB_FINISH ...  
JOB_NEW ...  
JOB_FINISH ...  

查看数据源,清除不需要的数据源,清空数据输出路径(第一次运行时可以跳过这一步)

hdfs dfs -ls /mnt/input
hdfs dfs -rm  /mnt/input/evenet.log*
hadoop fs -rmr  /mnt/out

数据源放入dfs

hdfs dfs -ls /mnt/input
hadoop fs -mkdir -p /mnt/input
hdfs dfs -put /mnt/hadoop/input/event/*   /mnt/input
hadoop fs -ls /mnt/input/input

运行代码

hadoop jar hadoop-1-1.0-SNAPSHOT.jar Hadoop2 /mnt/input /mnt/out

等待它执行结束,查看结果

hadoop dfs -cat /mnt/out/*
DEPRECATED: Use of this script to execute hdfs command is deprecated.
Instead use the hdfs command for it.

JOB_FINISH	4
JOB_NEW	6

MapReduce深入学习进阶样例

从上面的样例中我们了解了代码处理数据的大致过程,那么如何实现更多业务场景的数据处理呢,毕竟我们不是只做单表数据的个数统计,现实中可能会有更加复杂的运用,就像我们队数据库操作一样,单表到多表,复合统计等等,列出几个比喻的例子:

日志log如下
user.log
112 王小二
113 李小明

trade.log

10:48:20.669 INFO [pool-9-thread-1] c.z.e.a.domain.job.TransSuccessJob [TransSuccessJob.java : 42] 积分交易成功通知 notifyVo={"uid":112,"coin":"yxt","amount":10,"price":13.5555,"oPrice":13.5555,"transFee":0.2711,"oTransFee":0.2711,"tid":4339,"buy":true,"ext":"","dealTime":"2018-09-04 10:03:10"}

1.比如会查询单suer表特定几个字段的结果,单表[uid,coin,amount]统计结果,如结果要求如下
23 yxt 200 ETH 10
2.查询[uaerName,coin,amout]的统计结果,这个就是多表关联查询,如结果要求如下
李三 yxt 200 ETH 10

看上去是不是很像数据库的统计,但是这里操作的不是数据库,而是文本文件甚至流式文件,MapReduce就是要实现从分布式文件中的超大数据中处理结果。而实现的关键就在于两个实现类Mapper和Reducer

而这两个实现类都需要使用的一个地方就是MapReduce<key,value>,从上面的处理过程讲解我们知道,每一个步奏都是把数据处理成为了key,value键值输出并且作为下一个处理过程的输入,第二个处理过程接收到的依然是(key,vaule)键值对的数据类型,如何实现符合我们需要的键值类型才是整个数据处理的最重要的部分,毕竟我们不可能只处理一些基本类型的数据。

一些常见的基础类型数据对<key,value>
(Text,IntWritable)前面的wordCount就用到了,学生成绩统计也可以使用
(LongWritable,LongWritable)比如手机号的流量统计,单位是KB

MapReduce: <key,value>

MapReduce常用数据类型

ByteWritable:单字节数值
IntWritable:整型数
LongWritable:长整型数
FloatWritable:浮点数
DoubleWritable:双字节数值
BooleanWritable:标准布尔型数值
Text:使用UTF8格式存储的文本
NullWritable:当<key,value>中的key或value为空时使用

以上类型中 IntWritable LongWritable Text NullWritable DoubleWritable较为常用,实际中我们可能遇到复杂的类型,比如上面举例的user.log特定字段的统计,vule的值就不是一个基础类型,而是好几个字段的复合类型,这种情况我们该如何处理呢。

MapReduce自定义数据类型

  • key实现WritableComparable

  • value通常只需要实现Writable

关键就在这里,
key决定了是不是同一个数据,比如是不是同一个人,同一个手机号,因此Comparable的功能必须有;
value则决定了你需要处理的数据是什么格式的

案例1:手机上网流量统计

需求目标:
手机号码 上行数据包总数 下行数据包总数 上行总流量 下行总流量
13123412431 1238 17823 3424 2342
13622311238 17223 34214 23421 1231
13123412431 12380 1828 34 23

Map阶段结果:
把数据分割成 key,value这里的valeu是一个对象类型

13123412431        (123,2423,3453,234)
13123412431        (789,1231,4353,234)
13123412431        (1231,231,342,23) 

Shuffle阶段的结果

    key                value list
13123412431        (123,2423,3453,234),(789,1231,4353,234),(1231,231,342,23) ...
13622311238        (1233,23,342353,23234),(78239,123231,42353,2234) ...

Reduce最终结果:

 hadoop dfs -cat /mnt/out/*
DEPRECATED: Use of this script to execute hdfs command is deprecated.
Instead use the hdfs command for it.

13123412431 1276    17846   3448    2384
13123412432 1278    17848   3450    2386
13123412433 1280    17850   3452    2388
13123412434 1282    17852   3454    2390
13123412435 1284    17854   3456    2392
13123412436 1286    17856   3458    2394
13123412437 1288    17858   3460    2396
13123412438 1290    17860   3462    2398
13123412439 1292    17862   3464    2400
13123412440 1294    17864   3466    2402

构造复合类型的数据类型

package entity;

import org.apache.hadoop.io.Writable;

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

public class DataTotalWritable implements Writable {

    // 上行数据包总数
    private long upPackNum ;
    // 下行数据包总数
    private long downPackNum ;
    // 上行总流量
    private long upPayLoad ;
    // 下行总流量
    private long downPayLoad ;

    public DataTotalWritable() {
    }

    public DataTotalWritable(long upPackNum, long downPackNum, long upPayLoad, long downPayLoad) {
        this.set(upPackNum, downPackNum, upPayLoad, downPayLoad);
    }

    public void set (long upPackNum, long downPackNum, long upPayLoad,long downPayLoad) {
        this.upPackNum = upPackNum;
        this.downPackNum = downPackNum;
        this.upPayLoad = upPayLoad;
        this.downPayLoad = downPayLoad;
    }

    public long getUpPackNum() {
        return upPackNum;
    }

    public void setUpPackNum(long upPackNum) {
        this.upPackNum = upPackNum;
    }

    public long getDownPackNum() {
        return downPackNum;
    }

    public void setDownPackNum(long downPackNum) {
        this.downPackNum = downPackNum;
    }

    public long getUpPayLoad() {
        return upPayLoad;
    }

    public void setUpPayLoad(long upPayLoad) {
        this.upPayLoad = upPayLoad;
    }

    public long getDownPayLoad() {
        return downPayLoad;
    }

    public void setDownPayLoad(long downPayLoad) {
        this.downPayLoad = downPayLoad;
    }

    //^为异或运算, << 带符号左移, >>带符号右移, >>> 无符号右移
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (downPackNum ^ (downPackNum >>> 32));
        result = prime * result + (int) (downPayLoad ^ (downPayLoad >>> 32));
        result = prime * result + (int) (upPackNum ^ (upPackNum >>> 32));
        result = prime * result + (int) (upPayLoad ^ (upPayLoad >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        DataTotalWritable other = (DataTotalWritable) obj;
        if (downPackNum != other.downPackNum)
            return false;
        if (downPayLoad != other.downPayLoad)
            return false;
        if (upPackNum != other.upPackNum)
            return false;
        if (upPayLoad != other.upPayLoad)
            return false;
        return true;
    }

    @Override
    public String toString() {
        return upPackNum + "\t"    + downPackNum + "\t"
                + upPayLoad + "\t"    + downPayLoad ;
    }

    public void write(DataOutput out) throws IOException {
        out.writeLong(upPackNum);
        out.writeLong(downPackNum);
        out.writeLong(upPayLoad);
        out.writeLong(downPayLoad);
    }

    public void readFields(DataInput in) throws IOException {
        this.upPackNum = in.readLong() ;
        this.downPackNum = in.readLong() ;
        this.upPayLoad = in.readLong() ;
        this.downPayLoad = in.readLong() ;
    }

}

map实现类和Reduce实现类

import java.io.IOException;

import entity.DataTotalWritable;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.IOException;


/**
 * MapReduce复核类型数据处理
 *
  * <p>源码出自https://blog.csdn.net/helpless_pain/article/details/7015383</p>
 * 拷贝人 bamoboo
 * 2018-9-14
 */

public class Hadoop3  extends Configured implements Tool {

    //输入资源映射处理
    public static class DataTotalMapper extends
            Mapper<LongWritable, Text, Text, DataTotalWritable> {

        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {

            //split by '\t'
            String[] splits = value.toString().split("\t") ;

            //以手机号码作为output key
            String phoneNum = splits[0];
            Text mapOutputKey = new Text();
            mapOutputKey.set(phoneNum);

            // set map output value
            long upPackNum = Long.parseLong(splits[1]) ;
            long downPackNum = Long.parseLong(splits[2]) ;
            long upPayLoad = Long.parseLong(splits[3]) ;
            long downPayLoad = Long.parseLong(splits[4]) ;
            DataTotalWritable mapOutputValue = new DataTotalWritable() ;
            mapOutputValue.set(upPackNum, downPackNum, upPayLoad, downPayLoad);

            //map output
            context.write(mapOutputKey, mapOutputValue);
        }
    }

    public static class DataTotalReducer extends
            Reducer<Text, DataTotalWritable, Text, DataTotalWritable> {

        @Override
        protected void reduce(Text key, Iterable<DataTotalWritable> values,
                              Context context) throws IOException, InterruptedException {

            long upPackNumSum = 0;
            long downPackNumSum = 0;
            long upPayLoadSum = 0;
            long downPayLoadSum = 0;

            //iterator
            for(DataTotalWritable value : values){
                upPackNumSum += value.getUpPackNum() ;
                downPackNumSum += value.getDownPackNum() ;
                upPayLoadSum += value.getUpPayLoad() ;
                downPayLoadSum += value.getDownPayLoad()  ;
            }

            // set output value
            DataTotalWritable outputValue = new DataTotalWritable() ;
            outputValue.set(upPackNumSum, downPackNumSum, upPayLoadSum, downPayLoadSum);

            // output
            context.write(key, outputValue);
        }
    }

    public int run(String[] args) throws Exception {

        //Job
        Configuration conf = super.getConf();
        Job job = Job.getInstance(conf);
        job.setJarByClass(getClass());

        //Mapper
        job.setMapperClass(DataTotalMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(DataTotalWritable.class);

        //Reducer
        job.setReducerClass(DataTotalReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(DataTotalWritable.class);

        //输入路径
        Path inPath = new Path(args[0]);
        FileInputFormat.addInputPath(job, inPath);
        //输出路径
        Path outPath = new Path(args[1]);
        FileSystem dfs = FileSystem.get(conf);
        if (dfs.exists(outPath)) {
            dfs.delete(outPath, true);
        }
        FileOutputFormat.setOutputPath(job, outPath);

        //Submit Job
        boolean isSuccess = job.waitForCompletion(true);
        return isSuccess ? 0 : 1;
    }


    //这里的运行输入和输出已经在程序中写死了不需要在执行时指定参数
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();


        //运行时设置输入 输出
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
        //如果args的参数长度小于2直接退出程序
        if (otherArgs.length < 2) {
            System.err.println("Usage: EventCount <in> <out>");
            System.exit(2);
        }

        //也可以直接写死
        //args = new String[] {"hdfs://blue01.mydomain:8020/input2", "hdfs://blue01.mydomain:8020/output2"};

        // run job
        int status = ToolRunner.run(conf,new Hadoop3(),args);

        System.exit(status);

       /* Long p=13123412431L;
        for (int i=0;i<10;i++){
            System.out.printf("%d\t%d\t%d\t%d\t%d\n",p++ ,38+i, 23+i,  24+i, 42+i);
        }*/
    }
}

运行数据

把程序中/resources下的phone.log.1,phone.log.1上传到/mnt/hadoop/input/phone/文件夹下

把打包后的程序hadoop-1-1.0-SNAPSHOT.jar上传到/mnt/hadoop路径下

清空数据源和结果集输出源

hdfs dfs -rm  /mnt/input/*
hadoop fs -rmr  /mnt/out

数据提交到HDFS

hdfs dfs -put /mnt/hadoop/input/phone/*   /mnt/input
hdfs dfs -ls /mnt/input

运行程序并查看结果集

hadoop jar hadoop-1-1.0-SNAPSHOT.jar Hadoop3 /mnt/input /mnt/out
hadoop dfs -cat /mnt/out/*

结果就和上面中的结果集一模一样

多类型数据行转列

数据文件:
customer文件
用户CID,用户姓名,联系方式
1,Stephanie Leung,555-555-5555
2,Edward Kim,123-456-7890
3,Jose Madriz,281-330-8004
4,David Stork,408-555-0000

order文件
用户CID,订单号,价格,日期
3,A,12.95,02-Jun-2008
1,B,88.25,20-May-2008
2,C,32.00,30-Nov-2007
3,D,25.02,22-Jan-2009

目标:
用户CID,订单号,价格,时间,用户姓名,联系方式
1,B,88.25,20-May-2008,Stephanie Leung,555-555-5555
2,C,32.00,30-Nov-2007,Edward Kim,123-456-7890
3,D,25.02,22-Jan-2009,Jose Madriz,281-330-8004
3,A,12.95,02-Jun-2008,Jose Madriz,281-330-8004

思路:
选用: Join

map阶段:
** map task依次读取两个文件,切割,并设置key和value,取cid为key,同时给来自不同的文件的value打一个标签
value == flag + value

key                value list
3        customer('Jose Madriz,281-330-8004')
3       order('A,12.95,02-Jun-2008')
3       order('D,25.02,22-Jan-2009')

Shuffle阶段:

key                value list
3        customer('Jose Madriz,281-330-8004'),order('A,12.95,02-Jun-2008'),order('D,25.02,22-Jan-2009')

reduce阶段:
** Join 数据根据flag得不同摆放好次序合并
3 A,12.95,02-Jun-2008,Jose Madriz,281-330-8004
3 D,25.02,22-Jan-2009,Jose Madriz,281-330-8004

数据类型DataJoinWritable

 private String flag;
 private String data;
  public void set(String flag, String data) {
        this.flag = flag;
        this.data = data;
    }

数据拆分合并DataJoinMapper
1.按空格或者tab切割
2.splits.length长度不同则设置不同的flag
key,value存放格式为cid,(str1+str2)+str3的方式字符串拼接

     public static class DataJoinMapper extends
            Mapper<LongWritable, Text, Text, DataJoinWritable> {

        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {

            String[] splits = value.toString().split(",");

            //output key,以连接值为key
            String cid = splits[0];
            Text mapOutputKey = new Text();
            mapOutputKey.set(cid);

            //output value
            DataJoinWritable mapOutputValue = new DataJoinWritable();

            // length == 3 ==> customer
            if (splits.length == 3) {
                String name = splits[1];
                String phoneNum = splits[2];
                mapOutputValue.set("customer", name + "," + phoneNum);
            }

            // length == 4 ==> order
            if (splits.length == 4) {
                String name = splits[1];
                String price = splits[2];
                String date = splits[3];
                mapOutputValue.set("order", name + "," + price + "," + date);
            }
            
            context.write(mapOutputKey, mapOutputValue);
        }
    }

reduce数据在进行遍历时,由于cid相同的数据会成为一个list,根据不同的flag进行合并即可,这里customerInfo字符串 和所有OderList中的数据遍历合并,每合并一次就写出一行数据。

 public static class DataJoinReducer extends
            Reducer<Text, DataJoinWritable, NullWritable, Text> {

        @Override
        protected void reduce(Text key, Iterable<DataJoinWritable> values, Context context) 
                throws IOException, InterruptedException {

            String customerInfo = null;
            List<String> orderList = new ArrayList<String>();

            for (DataJoinWritable value : values) {
                if ("customer".equals(value.getFlag())) {
                    customerInfo = value.getData();
                } else if ("order".equals(value.getFlag())) {
                    orderList.add(value.getData());
                }
            }

            //遍历订单列表和客户信息合并为一行数据并写出
            Text outputValue = new Text();
            for (String order : orderList) {
                outputValue.set(key.toString() + "," + order + ","    + customerInfo);
                context.write(NullWritable.get(), outputValue);
            }
        }
    }

以上案例参考资料
https://blog.csdn.net/helpless_pain/article/details/70153834

参考资料

更多java HDFS操作讲解
https://www.cnblogs.com/zhangyinhua/p/7678704.html


人海茫茫孤影浮


伞如浮萍风飘雨

平平淡淡的

普通经历

一个工作多于生活的
程序员
自述
牧竹子


坐标/深圳

CSDN博客:blog.csdn.net/zjcjava/

Email:zjcjava@163.com 




以上是关于Hadoop第二篇:使用Maven开发Hadoop编程进阶的主要内容,如果未能解决你的问题,请参考以下文章

Hadoop实战——MapReduce实现主播的播放量等数据的统计及TopN排序(第二篇)

使用Maven搭建Hadoop开发环境

hadoop数仓建设之离线数据开发

maven怎么连接hadoop集群

eclipse怎样使用maven连接hadoop集群

maven仓库之第二篇