大数据实训笔记4:mapreduce

目录

核心思想

Mapper

Reducer

Driver

数据类型

简单案例wordcount

maven工程搭建

代码实现

在集群中测试

序列化

序列化和反序列化

自定义序列化

案例1:计算

案例2:过滤

案例3:排序

案例4:分区

案例5:组合

案例6:Join

mapreduce和mysql

环境准备

案例1:从mysql中读数据

案例2:往mysql中写数据

实际应用场景

案例1:日期转换

案例2:过滤+分区

核心思想

MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。其核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。

分布式的运算程序往往需要分成至少2个阶段。

  • 第一个阶段的MapTask并发实例,完全并行运行,互不相干。
  • 第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出。

MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行。

Mapper

  • 用户自定义的Mapper要继承自己的父类
  • Mapper的输入数据是KV对的形式(KV的类型可自定义)
  • Mapper中的业务逻辑写在map()方法中
  • Mapper的输出数据是KV对的形式(KV的类型可自定义)
  • map()方法(MapTask进程)对每一个<K,V>调用一次

Reducer

  • 用户自定义的Reducer要继承自己的父类
  • Reducer的输入数据类型对应Mapper的输出数据类型,也是KV
  • Reducer的业务逻辑写在Reducer()方法中
  • ReduceTask进程对每一组相k的<k,v>组调用一次reduce()方法

Driver

相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象。

数据类型

简单案例wordcount

需求:使用mapreduce统计分析以下城市出现的次数

maven工程搭建

maven工程搭建我们在上一节的笔记中已经详细介绍,大家可以参考:大数据实训笔记3:hdfs

我们在这里只介绍pom.xml中导入的依赖和log4j.properties日志的内容。

<!-- 自定义属性设置版本号 -->
<properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <hadoop-version>3.1.3</hadoop-version>
</properties>
<dependencies>
    <!--junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>RELEASE</version>
    </dependency>
    <!--slf4j日志-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>2.0.0-alpha2</version>
    </dependency>
    <!--hadoop中的common工具类-->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>${hadoop-version}</version>
    </dependency>
    <!--hadoop中的client-->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>${hadoop-version}</version>
    </dependency>
    <!--hadoop中的hdfs-->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-hdfs</artifactId>
        <version>${hadoop-version}</version>
    </dependency>
</dependencies>
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

 之后我们创建wordcount包,在这个包下面写Mapper, Reducer和Driver类。

代码实现

代码中有详细的注释,在此不再赘述。后续的mapreduce案例均按照此框架进行代码的编写,大家一定要借助这个简单的案例理解透彻。注意复制代码之后导入的包要正确!

//Mapper类
//<>中的数据类型分别为输入key类型、输入value类型、输出key类型、输入value类型
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    private Text k=new Text();                   //输出key
    private IntWritable v=new IntWritable(1);    //输出value,这里设置它恒为1

    //重载map方法,只要输入map后回车就可以自动生成方法框架
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();     //获取一行数据
        String[] split = line.split(" ");   //切割,以空格为分隔符
        for (String word : split) {         
            k.set(word);            //设置输出的key为每一个单词
            context.write(k,v);     //写出,例如(北京, 1), (上海, 1)
        }
    }
}
//Reducer类
//<>中的数据类型分别为:
//输入key类型(Mapper输出key类型)、输入value类型(Mapper输出value类型)
//输出key类型(最终输出key类型)、输出value类型(最终输出value类型)
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
    private IntWritable v=new IntWritable();    //输出value

    //重载reduce方法
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum=0;    //每一个城市出现的次数
        for (IntWritable value : values) {  //求和,算出一个城市出现了几次
            sum+=value.get();
        }
        v.set(sum);   //输出value为每一个城市出现的次数
        context.write(key,v);    //写出
    }
}
//Driver类,用来调度Mapper和Reducer类,最终执行的就是这个类的main方法
public class WordCountDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //设置输入输出路径
        //要分析的txt放在input\wordcount文件夹下
        //输出路径wordcount是不存在的,执行main方法之后自动创建,如果该文件夹已经存在会报错
        args=new String[]{"D:\\input\\wordcount","D:\\output\\wordcount"};
        //写一个配置对象
        Configuration conf=new Configuration();
        //获取job对象
        Job job = Job.getInstance(conf);
        //设置jar位置
        job.setJarByClass(WordCountDriver.class);
        //关联mapper和reducer
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);
        //设置mapper的输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);
        //设置最终输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        //设置输入输出路径
        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));
        //提交
        boolean result = job.waitForCompletion(true);
        System.exit(result?0:1);
    }
}

执行Driver类中的main方法,我们来到输出路径下,就可以查看输出结果了。 如果输出的中文为乱码,把txt文档的字符编码改为utf-8即可。

在集群中测试

除了在本地运行,我们还可以把刚刚编写的程序打包上传到集群中测试。打包之前,记得把自定义的路径(args)注释掉,否则会报错。 

首先,我们需要在pom.xml中加入打包插件。其中的com.mr.wordcount.WordCountDriver是我的Driver类所在位置,大家可以选中自己的Driver类,右键-Copy Reference获取。

<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>
                <archive>
                    <manifest>
                        <mainClass>com.mr.wordcount.WordCountDriver</mainClass>
                    </manifest>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

然后,点击右边栏的Maven-mapreduce_demo(项目名称)-Lifecycle,依次运行clean, install, package,就可以在项目下的target文件夹找到我们打包好的jar包了。

将其重命名后,上传到集群测试。Alt+P跳转到上传界面。

sftp> cd /opt/module/hadoop-3.1.3

 拖拽上传即可。上传成功后,我们就可以测试并查看结果。

//com.mr.wordcount.WordCountDriver是Driver类的位置,记得更改
//将要统计分析的txt放在input文件夹下
//output文件夹必须不存在,执行后自动生成
[hadoop@hadoop101 hadoop-3.1.3]$ hadoop jar wordcount.jar com.mr.wordcount.WordCountDriver /input /output

之后还会介绍很多案例,也可以用同样的方法打包、上传、测试。 

序列化

序列化和反序列化

序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。

反序列化就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。

一般来说,“活的”对象只生存在内存里,关机断电就没有了。而且“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。然而序列化可以存储”活的”对象,可以将”活的”对象发送到远程计算机。

Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,Header,继承体系等),不便于在网络中高效传输。所以,Hadoop自己开发了一套序列化机制(Writable)。

自定义序列化

  • 写一个类,必须实现Writable接口
  • 重写序列化方法
  • 重写反序列化方法
  • 写空参构造方法
  • 重写toString()

案例1:计算

需求:对以下学生的语文、数学、英语成绩求每位学生的最高分、最低分、总分、平均分。

//自定义序列化
public class StudentBean implements Writable {
    //对应txt中的几个变量和我们需要求的变量
    private String name;
    private long chinese;
    private long math;
    private long english;
    private long maxScore;
    private long minScore;
    private long sumScore;
    private double avgScore;
    
    //序列化
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(name);
        out.writeLong(chinese);
        out.writeLong(math);
        out.writeLong(english);
        out.writeLong(maxScore);
        out.writeLong(minScore);
        out.writeLong(sumScore);
        out.writeDouble(avgScore);
    }

    //反序列化
    @Override
    public void readFields(DataInput in) throws IOException {
        name = in.readUTF();
        chinese = in.readLong();
        math = in.readLong();
        english = in.readLong();
        maxScore = in.readLong();
        minScore = in.readLong();
        sumScore = in.readLong();
        avgScore = in.readDouble();
    }

    //空参构造
    public StudentBean() {
    }
    
    //有参构造
    public StudentBean(String name, Long chinese, Long math, Long english) {
        this.name = name;
        this.chinese = chinese;
        this.math = math;
        this.english = english;
        this.maxScore = Math.max(Math.max(chinese, math), english);
        this.minScore = Math.min(Math.min(chinese, math), english);
        this.sumScore = (chinese + math + english);
        this.avgScore = this.sumScore / 3.0;
    }

    //以下这些右键-Generate-Getter & Setter就可以自动生成
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getChinese() {
        return chinese;
    }

    public void setChinese(Long chinese) {
        this.chinese = chinese;
    }

    public Long getMath() {
        return math;
    }

    public void setMath(Long math) {
        this.math = math;
    }

    public Long getEnglish() {
        return english;
    }

    public void setEnglish(Long english) {
        this.english = english;
    }

    public Long getMaxScore() {
        return maxScore;
    }

    public void setMaxScore(Long maxScore) {
        this.maxScore = maxScore;
    }

    public Long getMinScore() {
        return minScore;
    }

    public void setMinScore(Long minScore) {
        this.minScore = minScore;
    }

    public Long getSumScore() {
        return sumScore;
    }

    public void setSumScore(Long sumScore) {
        this.sumScore = sumScore;
    }

    public Double getAvgScore() {
        return avgScore;
    }

    public void setAvgScore(Double avgScore) {
        this.avgScore = avgScore;
    }

    //重载toString方法,后续Mapper和Reducer会用到
    //不输出name是因为后续我们用name做key,输出的时候已经有name了,不需要重复输出
    @Override
    public String toString() {
        return chinese +
                "\t" + math +
                "\t" + english +
                "\t" + maxScore +
                "\t" + minScore +
                "\t" + sumScore +
                "\t" + avgScore ;
    }
}

接下来编写Mapper, Reducer和Driver类,与上个案例类似,这里只给出部分代码。

//Mapper类
public class StudentMapper extends Mapper<LongWritable, Text, Text, StudentBean> {
    private  Text k = new Text();
    private  StudentBean v = new StudentBean();
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString(); 
        //这是分割的另一种方法,可以按照\t,\n,空格等进行分割                    
        StringTokenizer sti = new StringTokenizer(line);    
        v.setName(sti.nextToken());
        v.setChinese(Long.parseLong(sti.nextToken()));
        v.setMath(Long.parseLong(sti.nextToken()));
        v.setEnglish(Long.parseLong(sti.nextToken()));
        k.set(v.getName());
        context.write(k, v);
    }
}
//Reducer类
public class StudentReducer extends Reducer<Text, StudentBean, Text, StudentBean> {
    @Override
    protected void reduce(Text key, Iterable<StudentBean> values, Context context) throws IOException, InterruptedException {
        StudentBean bean = values.iterator().next();
        //计算在这步完成,我们编写的有参构造函数会帮我们完成计算
        StudentBean v = new StudentBean(bean.getName(), bean.getChinese(), bean.getMath(), bean.getEnglish());
        context.write(key, v);
    }
}

运行结果如下: 

案例2:过滤

需求:根据日志信息,统计每个联系方式上网流量,上行流量、下行流量、总流量。

这个案例与案例1类似,大家可以自行编写。不过观察数据,我们会发现,有一些数据为空。因此,在Mapper的编写中,要格外注意。

//在Mapper类中,获取上行流量与下行流量时
//因为不一定有ip地址,因此不能直接写split[3]
v.setUpFlow(Long.parseLong(split[split.length - 3]));       
v.setDownFlow(Long.parseLong(split[split.length - 2]));
//Reducer类
public class FlowReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
    @Override
    protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
        long sumUpFlow = 0;
        long sumDownFlow = 0;
        //对电话号码相同的用户,计算出其上下行流量的总和
        for (FlowBean bean : values) {          
            sumUpFlow += bean.getUpFlow();
            sumDownFlow += bean.getDownFlow();
        }
        FlowBean v = new FlowBean(key.toString(), sumUpFlow, sumDownFlow);
        context.write(key, v);
    }
}

运行结果如下: 

案例3:排序

需求:将数据先按年龄升序排序,如果年龄相等,再比较身高。

和前面的案例相似,只是需要多写一个MySort类负责比较。

//MySort类
public class MySort implements WritableComparable<MySort2> {
    private int age;
    private int height;

    //相等返回0,小于返回-1,大于返回1
    @Override
    public int compareTo(MySort2 other) {
        if(this.age == other.age) {
            if(this.height == other.height) {
                return 0;
            }
            else if(this.height < other.height) {
                return  -1;
            }
            else {
                return 1;
            }
        }
        else if(this.age < other.age) {
            return -1;
        }
        else {
            return 1;
        }
    }
    
    //序列化
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeInt(age);
        out.writeInt(height);
    }

    //反序列化
    @Override
    public void readFields(DataInput in) throws IOException {
        age = in.readInt();
        height = in.readInt();
    }

    public MySort() {
    }

    public MySort(int age, int height) {
        this.age = age;
        this.height = height;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public String toString() {
        return age + height + "";
    }

    @Override
    public int hashCode() {
        return age + height;
    }
}
//Mapper类中,将输出类型设置为MySort
private MySort k = new MySort();
k.setAge(v.getAge());
k.setHeight(v.getHeight());

案例4:分区

需求:在案例3的基础上,将数据按照年龄分为3个区。

//MyPartioner自定义分区
//泛型参数是mapper输出数据的key和value的数据类型
public class MyPartioner extends Partitioner<MySort, WorkSort> {
    @Override
    public int getPartition(MySort key, WorkSort value, int num) {
        //按照年龄分区
        //num代表在年龄范围内平均分成几个区
        return value.getAge()/(100/num);
    }
}
//在Driver类中
//设置分区
job.setPartitionerClass(MyPartioner.class);
job.setNumReduceTasks(3);

案例5:组合

需求:在案例3的基础上,统计各组中身高超过170的附近人数。

//MyGroupingComparator类
public class MyGroupingComparator extends WritableComparator {
    @Override
    public int compare(WritableComparable a, WritableComparable b) {
        //key1,key2是mapper输出的key
        MySort2 key1 = (MySort2) a;
        MySort2 key2 = (MySort2) b;
        //身高在170以上的返回0,可以把key所对应的value组合放到同一key的values中
        if(key1.getHeight() > 170 && key2.getHeight() > 170)
            return  0;
        else
            return -1;
    }
    public MyGroupingComparator() {
        //创建适合key的实例
        super(MySort2.class, true);
    }
}
//在Driver类中
//设置看哪些数据可以在reducer端进行一次处理
job.setGroupingComparatorClass(MyGroupingComparator.class);
job.setNumReduceTasks(1);

案例6:Join

需求:统计person表和score表有关联的数据信息。(score表第二列是person表的第一列id)

//Record类
public class Record implements Writable {
    private int isPerson;   //0表示score, 1表示person
    private String content; //输出内容
    //......
}

除了像上述几个案例那样将Mapper, Reducer和Driver类分开编写,我们还可以把它们作为一个类下的子类。

//MyJoin类
public class MyJoin {
    //Mapper类
    public static class JoinMapper extends Mapper<LongWritable, Text, IntWritable, Record> {
        private IntWritable k = new IntWritable();  //存放personId
        private Record v = new Record();            //存放输出内容

        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            String [] fields = value.toString().split("\t");    //获取一行数据,以tab为分隔符
            int personId = 0;
            if(fields.length == 6) {    //如果长度为6,说明为person表
                personId = Integer.parseInt(fields[0]);     //person表的第一列为personId
                v.setIsPerson(1);
                v.setContent(fields[1] + "\t" + fields[2] + "\t" + fields[3] + "\t" + fields[4] + "\t" + fields[5]);
            }
            else {  //否则是score表
                personId = Integer.parseInt(fields[1]);     //score表的第二列为personId
                v.setIsPerson(0);
                v.setContent(fields[2] + "\t" + fields[3]);
            }
            k.set(personId);
            context.write(k, v);
        }
    }
    
    //Reducer类
    public static class JoinReducer extends Reducer<IntWritable, Record, IntWritable, Text> {
        private Text v = new Text();
        @Override
        protected void reduce(IntWritable key, Iterable<Record> values, Context context) throws IOException, InterruptedException {
            String personStr = null;
            List<String> scoreList = new ArrayList();    //一个人可能有多门课的成绩
            for  (Record record: values) {
                if (record.getIsPerson() == 1){
                    personStr = record.getContent();
                }
                else {
                    scoreList.add(record.getContent());
                }
            }
            for (String score: scoreList) {
                v.set(personStr + "\t" + score);
                context.write(key, v);
            }
        }
    }

    //相当于Driver类
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //......
        Job job = Job.getInstance(conf, "queryMr");
        //......
    }
}

mapreduce和mysql

环境准备

自行安装mysql,可以安装Navicat,便于数据库操作。

在mysql中,新建mapreduce数据库。在mapreduce数据库下,新建tb_student表。设计如下:

插入几条数据:

在pom.xml中导入mysql依赖。注意替换成自己的版本。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>

案例1:从mysql中读数据

需求:统计学生信息中每个年龄的学生数量。

//Student类,注意一定要DBWritable类
public class Student implements DBWritable, Writable {
    //存放从mysql中读的数据
    private int sid;
    private String sname;
    private String sex;
    private int age;
    private String birthday;

    //序列化和反序列化的代码自行编写
    
    //从数据库中读取数据
    @Override
    public void readFields(ResultSet resultSet) throws SQLException {
        this.sid = resultSet.getInt("sid");
        this.sname = resultSet.getString("sname");
        this.sex = resultSet.getString("sex");
        this.age = resultSet.getInt("age");
        this.birthday = resultSet.getString("birthday");
    }

    //其他方法自行编写
}
//ReadSql类,负责从MySql中读取数据
public class ReadSql {
    //Mapper类
    public static class ReadDBMapper extends Mapper<LongWritable, Student, IntWritable, IntWritable> {
        private IntWritable k = new IntWritable();
        private IntWritable v = new IntWritable(1);     //默认为1
        @Override
        protected void map(LongWritable key, Student value, Context context) throws IOException, InterruptedException {
            k.set(value.getAge());    //按照需求,以年龄为key
            context.write(k, v);
        }
    }

    //Reducer类
    public static class ReadDBReducer extends Reducer<IntWritable, IntWritable, Text, NullWritable> {
        private Text k = new Text();
        @Override
        protected void reduce(IntWritable key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int count = 0;
            for (IntWritable value: values) {
                count += value.get();
            }
            k.set(key.get() + "岁的学生有" + count + "人");
            context.write(k, NullWritable.get());
        }
    }

    //相当于Driver类
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        //连接数据库
        //localhost连接本地数据库,3306为端口,字符集为utf-8,时区为GMT(这一项不设置我会报错,大家可以自行调整)
        //最后两个参数是用户名和密码
        DBConfiguration.configureDB(conf, "com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/mapreduce?characterEncoding=utf-8&serverTimezone=GMT", "root", "passwd");
        Job job = Job.getInstance(conf, "mrsql");
        //......
        //表中的属性名,不能出错,否则无法读取数据
        String[] fields = {"sid,", "sname", "sex", "age", "birthday"};
        //设置输入输出路径
        DBInputFormat.setInput(job, Student.class, "tb_student", null, "sid", fields);
        Path outPath = new Path("D:\\output\\sql");
        FileSystem fs = outPath.getFileSystem(conf);
        //如果输出路径存在,删除
        if(fs.exists(outPath)) {
            fs.delete(outPath, true);
        }
        FileOutputFormat.setOutputPath(job, outPath);
        //......
    }
}

案例2:往mysql中写数据

需求:把文件中数据写入到mysql中。

//在Student类中
//写入数据库
@Override
public void write(PreparedStatement statement) throws SQLException {
    statement.setInt(1, sid);
    statement.setString(2, sname);
    statement.setString(3, sex);
    statement.setInt(4, age);
    statement.setString(5, birthday);
}
//WriteSql类,负责往MySql中写入数据
public class WriteSql {
    //Mapper类和Reducer类自行编写
    //相当于Driver类
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //......
        //设置输入输出路径
        FileInputFormat.setInputPaths(job, new Path("D:\\input\\sql"));
        String[] fields = {"sid", "sname", "sex", "age", "birthday"};
        DBOutputFormat.setOutput(job, "tb_student", fields);
        //......
    }
}

在MySql中查看,数据写入成功。 

实际应用场景

可以看到,这次的文件中有一万多条,是疫情时期的真实数据。使用mapreduce能很快地对这么多数据进行统计分析。

案例1:日期转换

需求:把文件数据日期转换成xxxx年xx月xx日。

public class Date {
    //Mapper类
    public static class DateFormatMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
        private Text k=new Text();
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //获取一行数据
            String text=new String(value.getBytes(),0,value.getLength(),"GBK");
            //以逗号分割,因为我们的文件是.csv
            String[] line = text.split(",", -1);
            //计算日期
            String date="2021年"+line[0];
            k.set(date+","+line[1]+","+line[2]+","+line[3]+","+line[4]+","+
line[5]+","+line[6]+","+line[7]);
            context.write(k,NullWritable.get());
        }
    }
    //Reducer和Driver类自行编写
}

日期转换成功。把输出的文件后缀改为csv就可以用excel表格更清楚地查看。

案例2:过滤+分区

需求:对于2021年疫情数据,过滤出省份为湖北省的数据,非湖北省的数据单独放在一个文件夹中,只需要前6列。

public class Data implements WritableComparable<Data> {
    //定义6个变量用来存储6列数据
    //都设置为String是因为后四个数据都可能为空,不能设置为int
    private String date;
    private String city;
    private String district;
    private String sick;
    private String discharge;
    private String death;

    //重载比较方法,使最后数据能够按顺序输出
    @Override
    public int compareTo(Data o) {
        int i = date.compareTo(o.date);
        int j = city.compareTo(o.city);
        int m = district.compareTo(o.district);
        int n = sick.compareTo(o.sick);
        int g = discharge.compareTo(o.discharge);
        int h = death.compareTo(o.death);
        if (i == 0) {
            if (j == 0) {
                if (m == 0) {
                    if (n == 0) {
                        if (g == 0) {
                            return h;
                        } else {
                            return g;
                        }
                    } else {
                        return n;
                    }
                } else {
                    return m;
                }
            } else {
                return j;
            }
        } else {
            return i;
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;
        Data data = (Data) o;
        return Objects.equals(date, data.date) &&
                Objects.equals(city, data.city) &&
                Objects.equals(district, data.district) &&
                Objects.equals(sick, data.sick) &&
                Objects.equals(discharge, data.discharge) &&
                Objects.equals(death, data.death);
    }

    @Override
    public int hashCode() {
        return Objects.hash(date, city, district, sick, discharge, death);
    }

    //序列化和反序列化以及其他方法自行编写
}
public class Hubei {
    //过滤出湖北省的分区
    public static class DataPartioner extends Partitioner<Data, NullWritable> {
        //重写分区方法
        @Override
        public int getPartition(Data data, NullWritable nullWritable, int numPartitions) {
            //比较开头是否为湖北,是湖北就符合条件,筛选出来
            if(data.getCity().startsWith("湖北")){
                return 0;
            }
            return 1;
        }
    }

    //Mapper, Reducer和Driver类自行编写
    
    //删除输出路径下的一切文件夹和文件
    public static void deleteFile(File file){
        File[] files = file.listFiles();
        if(files!=null&&files.length!=0){
            for (int i = 0; i < files.length; i++) {
                deleteFile(files[i]);
            }
        }
        file.delete();
    }

    public static void main(String[] args) throws InterruptedException, IOException, ClassNotFoundException {
        args = new String[]{"D:\\input\\covid19","D:\\output\\covid19.2"};
        File file=new File(args[1]);
        if(file.exists()){
            deleteFile(file);
            initDriver(args);
        }
        else{
            initDriver(args);
        }
    }
}
wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MapReduce 是一种分布式计算模型,可以用于大规模数据处理。流量统计是 MapReduce 中的一个经典应用场景,下面是一个简单的 MapReduce 流量统计程序示例。 假设我们有一个日志文件,记录了用户在某个网站上的访问情况,每行记录包括访问时间、用户 ID、访问 URL、访问流量等信息。我们需要编写一个程序,统计每个 URL 的访问流量总和。 首先是 Map 阶段,我们需要将每行记录解析成 key-value 对,其中 key 是 URL,value 是访问流量。Map 函数的伪代码如下: ``` function Map(key, value): url = extract_url(value) traffic = extract_traffic(value) emit(url, traffic) ``` 其中 `extract_url` 和 `extract_traffic` 函数是自定义的解析函数,可以根据具体情况实现。`emit` 函数将 key-value 对输出到中间结果。 接着是 Shuffle 阶段,框架会将 Map 输出的中间结果按照 key 进行分组,得到一个 key-value 列表,其中 key 相同的 value 被分在同一个列表中。 最后是 Reduce 阶段,我们需要对每个 key 对应的 value 进行合并,并输出最终结果。Reduce 函数的伪代码如下: ``` function Reduce(key, values): total_traffic = 0 for traffic in values: total_traffic += traffic emit(key, total_traffic) ``` 其中 `emit` 函数将 key-value 对输出到最终结果。 整个流程可以用下图表示: ``` +-----------+ +------------+ +-----------+ | | | | | | | Input +-----> | Map +-----> | Shuffle | | | | | | | +-----------+ +------------+ +-----------+ | | v +-----------+ | | | Reduce | | | +-----------+ | | v +-----------+ | | | Output | | | +-----------+ ``` 以上就是一个简单的 MapReduce 流量统计程序示例。需要注意的是,实际应用中可能会有更多的细节问题需要考虑,比如数据倾斜、容错机制等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值