背景

公司的物流业务系统目前实现了使用storm集群进行过门事件的实时计算处理,但是还有一个需求,我们需要存储每个标签上传的每条明细数据,然后进行定期的标签报表统计,这个是目前的实时计算框架无法满足的,需要考虑离线存储和计算引擎。

标签的数据量是巨大的,此时存储在mysql中是不合适的,所以我们考虑了分布式存储系统HDFS。目前考虑的架构是,把每条明细数据存储到HDFS中,利用Hive或者其他类SQL的解析引擎,定期进行离线统计计算。

查找相关资料后,我下载了深入理解Haddoop这本书,从大数据的一些基础原理开始调研,这一系列的笔记就是调研笔记。

系列文章:

深入理解Hadoop读书笔记-1

深入理解Hadoop读书笔记-2

深入理解Hadoop读书笔记-3

深入理解Hadoop-类比SQL的MapReduce开发模式

书中以一个航空公司的数据集来解释相关概念,原本我是想把数据下载下来,然后对照着书中的例子实际编写代码测试,但是下载链接中无法下载数据,只能作罢。

我会自己写一些数据,然后仿照书中的例子实际编写代码测试,代码也会放在下面。

下面按照书中的逻辑,通过类比SQL来学习MapReduce不同类型业务需求的开发。

Select

关于select的查询数据的需求,一般不需要使用reduce任务,只需要使用map任务即可。在map任务中,我们需要对读入的每行数据做一些数据处理,然后输出。只包含Map阶段的任务和同时包含map阶段和reduce阶段的任务相比,因为不包含sort/shuffle阶段(这个阶段负责把Map阶段输出的结果通过网络输入到reduce任务中),所以执行速度会快一些。

要点如下:

  1. 如果输入和输出都是文件,并且逐行读入和逐行写出,那么使用TextInputFormat.class和TextOutputFormat.class即可

  2. 如果不需要键,只需要值,可以把输出值的类型设置为NullWritable

  3. 需要人为指定 map(reduce,如果有的话)的输入和输出的键值类型 和 最终结果的输出键值类型,因为Java代码的泛型在实际执行时会擦除

    1. 如果使用TextInputFormat,那么map的输入键值对类型就为 LongWritable和Text类型,默认情况下map的输出类型和输入类型一致,如果修改,需要单独配置
    2. 如果使用TextInputFormat,默认把键值输出为字符串,中间用制表符分割,如果键类型为NullWritable,则只输出值。
  4. 如果只有map阶段,需要编写下面这行代码,如果不写,默认会建立一个reduce,会导致额外的网络传输降低性能。

    job.setNumReduceTasks(0)
  5. 有几个输入文件,就对应有几个输出文件,可以使用tail 查看文件的后几行。

聚合

类似Group by sum max等业务需求,如何使用MapReduce来开发呢?

比如,统计某个时间段内的某种业务数据的总次数和某些比例数据(某业务的次数/总次数),这种需求是很普遍的。

要点如下:

  1. 统计不同的业务的比例,最好先在map中实现一部分计算,把需要统计的不同业务转换为字段较短的标识,然后传递给reduce,可以大大减少网络传输
  2. 在reduce中,使用上面定义的标识来分别编写代码计算处理总数和比例

下面举一个具体的例子

业务原始数据,所有数据按照#分割

1573fe8955e64a3c8f97af674d0ad409#2020-07-30 11:34:59#1111#1#1#39
17ad961f25d340f18ebd60d6d0de7b57#2020-08-04 17:32:25#1111#1#3#39
1a6da9e15d2a4abba1fbe9cdf936508f#2020-07-30 15:07:07#1111#1#1#39
26d846ed92034b0fbecd271c390d9a5c#2020-08-04 16:48:13#1111#1#3#39
2b8a6a9d2845462f9bbf3cff06cb9ff7#2020-08-04 17:24:45#1111#1#3#39
38bd8f1756fe4181bc0606ac1871329d#2020-08-04 16:49:56#1111#2#1#39
3cba6ffa8922481e828c7005210fb68a#2020-07-30 11:36:19#1111#2#1#39
4e30b5863bed442f9d0f4a29d95967f2#2020-07-30 15:07:26#1111#2#1#39
570f98bef1c84b11a1868376318f9509#2020-08-04 17:24:25#1111#1#1#39
59c9d98e5e244078aa4974bb1423ce76#2020-08-04 17:29:05#1111#1#3#39
63c997ff330040119ac3fe773814134d#2020-07-30 15:06:27#1111#1#3#39
6d77b76646314c419efd164dd1d477c6#2020-08-04 16:48:13#1111#1#1#39
81954b10c702480392d5d17cbd05fc97#2020-07-30 14:15:27#1111#1#2#39
9781f0e163ec4e8a85f800f63ddd7127#2020-08-04 16:50:25#1111#1#1#39
b1696070c5214bc19c81c51b8a38838c#2020-07-30 11:34:20#1111#2#1#39
b3ef5de5432a4aefb58c885cd844d45b#2020-07-30 11:30:54#1111#1#1#39
c1ff40e93aa04c1285104a53a5066be5#2020-08-04 17:29:36#1111#1#3#39
dada5853f0e44dc1bf195f04766fa3b4#2020-07-30 14:47:47#1111#2#1#39
f617de93fa8345ccac1a5f99a836f8d5#2020-08-04 17:24:15#1111#2#1#39
f96831bbe1b24b3f927519b915d1f72a#2020-07-30 11:37:19#1111#1#1#40

字段的解释如下:

我们现在需要按照统计每个月的报警总次数,非法上架次数,非法下架的次数,非法上架比例,非法下架比例这些数据

代码如下:

package org.example.sql;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
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 java.io.IOException;
import java.net.URI;
import java.text.DecimalFormat;
import java.util.Iterator; /**
* only have map !
*/
public class GroupBySample { private static final IntWritable RECORD = new IntWritable(0);
private static final IntWritable UP = new IntWritable(1);
private static final IntWritable DOWN = new IntWritable(2);
private static final IntWritable OTHER = new IntWritable(3); private static class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] strings = value.toString().split("#"); String[] dates = strings[1].split("-");
String month = dates[1];
context.write(new Text(month), RECORD);
IntWritable content = new IntWritable(Integer.parseInt(strings[3]));
if (UP.equals(content)) {
context.write(new Text(month), UP);
} else if (DOWN.equals(content)) {
context.write(new Text(month), DOWN);
} else {
context.write(new Text(month), OTHER);
}
}
} private static class MyReducer extends Reducer<Text, IntWritable, NullWritable, Text> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
int up = 0;
int down = 0;
int other = 0; Iterator<IntWritable> iterator = values.iterator();
while (iterator.hasNext()) {
IntWritable next = iterator.next();
if (RECORD.equals(next)) {
sum++;
} if (UP.equals(next)) {
up++;
} if (DOWN.equals(next)) {
down++;
} if (OTHER.equals(next)) {
other++;
}
} //calculate
DecimalFormat decimalFormat = new DecimalFormat("0.0000");
StringBuilder sb = new StringBuilder(key.toString());
sb.append(",").append(sum);
sb.append(",").append(up);
sb.append(",").append(down);
sb.append(",").append(decimalFormat.format((double) up / sum));
sb.append(",").append(decimalFormat.format((double)down / sum));
sb.append(",").append(decimalFormat.format((double)other / sum));
context.write(NullWritable.get(), new Text(sb.toString()));
}
} public static void main(String[] args) throws Exception {
System.setProperty("HADOOP_USER_NAME", "ging");
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS", "hdfs://192.168.202.129:9000");
Job job = Job.getInstance(configuration); job.setJarByClass(GroupBySample.class);
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class); // //增加combiner操作
// job.setCombinerClass(WordCountReducer.class); job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(NullWritable.class);
job.setOutputValueClass(Text.class); FileSystem fileSystem = FileSystem.get(new URI("hdfs://192.168.202.129:9000"), configuration, "ging"); Path output = new Path("/hdfs/test/output");
if (fileSystem.exists(output)) {
fileSystem.delete(output, true);
} FileInputFormat.setInputPaths(job, new Path("/hdfs/test/input/groupby"));
FileOutputFormat.setOutputPath(job, output); boolean result = job.waitForCompletion(true);
System.out.println(result);
} }

输出结果如下:

ging@ubuntu:~/hadoop/hadoop-2.10.0$ bin/hdfs dfs -text /hdfs/test/output/part-r-00000
07,10,6,4,0.6000,0.4000,0.0000
08,10,8,2,0.8000,0.2000,0.0000

可以看到,有两行数据,分别代码7月和8月的相关数据。

分区

分区的作用是,当数据通过mapper经过业务处理,传递出来一个个键值对后,Hadoop框架会调用默认的partitioner把键分配到reducer中进行处理,默认的分区器是hash分区器,可以保证同样的键,一定被同一个reducer处理。下面使用前一部分讲解的业务例子来帮助大家进一步理解分区的概念。

上面的例子中只使用了一个reducer,所有的键值对最终都会分配到同一个reducer处理。如果现在想加快处理速度(会使用2个reducer),并且要一个reducer只处理奇数月份或者偶数月份的数据,那么就需要实现自定义的partitioner来进行分区。如果不自定义实现,就无法保证一个reducer只处理一个key的业务需求,同一个reducer可能处理多个key的数据。

增加代码如下:

main方法

job.setPartitionerClass(MyPartioner.class);
job.setNumReduceTasks(2);

自定义Partioner

 /**
* 2 reducer
*/
private static class MyPartioner extends Partitioner<Text, MapWritable> { @Override
public int getPartition(Text text, MapWritable mapWritable, int i) {
String month = text.toString();
return Integer.parseInt(month) % 2;
}
}

跑完结果如下,在output文件夹下生成了两个结果文件,分别查看内容,发现7月和8月的数据已经成功分离出来。

ging@ubuntu:~/hadoop/hadoop-2.10.0$ bin/hdfs dfs -ls /hdfs/test/output
Found 3 items
-rw-r--r-- 3 ging supergroup 0 2020-09-21 19:21 /hdfs/test/output/_SUCCESS
-rw-r--r-- 3 ging supergroup 31 2020-09-21 19:21 /hdfs/test/output/part-r-00000
-rw-r--r-- 3 ging supergroup 31 2020-09-21 19:21 /hdfs/test/output/part-r-00001
ging@ubuntu:~/hadoop/hadoop-2.10.0$ bin/hdfs dfs -text /hdfs/test/output/part-r-00000
08,10,8,2,0.8000,0.2000,0.0000
ging@ubuntu:~/hadoop/hadoop-2.10.0$ bin/hdfs dfs -text /hdfs/test/output/part-r-00001
07,10,6,4,0.6000,0.4000,0.0000

总结

这一部分通过结合实际的业务场景,编写了MR代码,实现了基本的功能,这部分的目的就达到了,了解完这部分,我们可以基本写出一个不错误的MR程序。

深入理解Hadoop读书笔记-4的更多相关文章

  1. Hadoop读书笔记(二)HDFS的shell操作

    Hadoop读书笔记(一)Hadoop介绍:http://blog.csdn.net/caicongyang/article/details/39898629 1.shell操作 1.1全部的HDFS ...

  2. Hadoop读书笔记(四)HDFS体系结构

    Hadoop读书笔记(一)Hadoop介绍:http://blog.csdn.net/caicongyang/article/details/39898629 Hadoop读书笔记(二)HDFS的sh ...

  3. java内存区域——深入理解JVM读书笔记

    本内容由<深入理解java虚拟机>的部分读书笔记整理而成,本读者计划连载. 通过如下图和文字介绍来了解几个运行时数据区的概念. 方法区:它是各个线程共享的区域,用于内存已被VM加载的类信息 ...

  4. 00-深入理解C#读书笔记说明

    带着问题去看书 尝试着,根据每一小节,先列出大纲.然后根据自己原先的认知和理解以及不理解,对每一个小的chapter,我会先自我提问,带着问题去阅读,然后把我的理解以及不理解记录下来,对于错误的地方做 ...

  5. [hadoop读书笔记] 第十五章 sqoop1.4.6小实验 - 将mysq数据导入hive

    安装hive 1.下载hive-2.1.1(搭配hadoop版本为2.7.3) 2.解压到文件夹下 /wdcloud/app/hive-2.1.1 3.配置环境变量 4.在mysql上创建元数据库hi ...

  6. [hadoop读书笔记] 第十五章 sqoop1.4.6小实验 - 将mysq数据导入HBASE

    导入命令 sqoop import --connect jdbc:mysql://192.168.200.250:3306/sqoop --table widgets --hbase-create-t ...

  7. [hadoop读书笔记] 第十五章 sqoop1.4.6小实验 - 数据在mysq和hdfs之间的相互转换

    P573 从mysql导入数据到hdfs 第一步:在mysql中创建待导入的数据 1.创建数据库并允许所有用户访问该数据库 mysql -h 192.168.200.250 -u root -p CR ...

  8. [hadoop读书笔记] 第十章 管理Hadoop集群

    P375 Hadoop管理工具 dfsadmin - 查询HDFS状态信息,管理HDFS. bin/hadoop dfsadmin -help 查询HDFS基本信息 fsck - 检查HDFS中文件的 ...

  9. [hadoop读书笔记] 第九章 构建Hadoop集群

    P322 运行datanode和tasktracker的典型机器配置(2010年) 处理器:两个四核2-2.5GHz CPU 内存:16-46GN ECC RAM 磁盘存储器:4*1TB SATA 磁 ...

  10. [hadoop读书笔记] 第五章 MapReduce工作机制

    P205 MapReduce的两种运行机制 第一种:经典的MR运行机制 - MR 1 可以通过一个简单的方法调用来运行MR作业:Job对象上的submit().也可以调用waitForCompleti ...

随机推荐

  1. 【饮食与健康】【AIGC创作】表观生理年龄逆转指北

    一.引言 我们都知道,岁月不饶人,但是谁又不想在岁月的长河中留下青春的容颜呢?在这个人人都追求健康和美丽的时代,我们的生活节奏却愈发紧张,高压的工作和不规律的作息让我们的身体时刻处于亚健康状态.这时候 ...

  2. 当github遇到了Halloween,神奇的彩蛋出现了!

    往年每个万圣节github都会修改配色方案,今天才发现,so记录这个不平凡的2020年的github的彩蛋,希望一切都会慢慢好起来.

  3. 利用mybatis拦截器记录sql,辅助我们建立索引(一)

    背景 由于现在的工作变成了带别的小伙子一起做项目,就导致,整个项目中的代码不再全部都是自己熟悉的,可能主要是熟悉其中的部分代码. 但是最终项目上线,作为技术责任人,线上出任何问题,我都有责任(不管是不 ...

  4. c# 多线程 lock

    模拟10个线程,每个线程模拟100次取钱: 其实就是相当于1000个人来同时取钱.当然实际情况是取钱的人分布在不同的地区的取款机取钱.同一个取款机只能一个人操作. 关键是要保证取钱的余额要准确,不能在 ...

  5. 免费-高清免费视频录像软件OBS

    OBS studio 是免费开源的. https://obsproject.com/download 中文绿色版: http://www.xitongzhijia.net/soft/151705.ht ...

  6. MongoDB:使用场景简介

  7. [译] WinForms:分析一下(我用 Visual Basic 写的)

    原文 | Klaus Loeffelmann 翻译 | 郑子铭 如果您从未看过电影<分析这一点>,下面是简短的介绍:假设一个纽约家族的成员有可疑的习惯,他决定认真考虑接受治疗以改善他的精神 ...

  8. Luogu P3177 树上染色 [ 蓝 ] [ 树形 dp ] [ 贡献思维 ]

    一道很好的树形 dp !!!!! 树上染色. 错误思路 定义 \(dp[u][i]\) 表示以 \(u\) 为根的子树中,把 \(i\) 个点染成黑色的最大收益. 但这样写,就在转移的时候必须枚举每一 ...

  9. JWT权限验证,兼容多方式验证

    前言 许久没写博文了,整合下这段时间所学吧,前进路上总要停下来回顾下学习成果. 本篇记录下项目的权限验证,WebApi项目中用权限验证来保证接口安全总是需要的,然而权限验证的方式多种多样,博主在项目中 ...

  10. 用python做时间序列预测八:Granger causality test(格兰杰因果检验)

    如果想知道一个序列是否对预测另一个序列有用,可以用Granger causality test(格兰杰因果检验). Granger causality test的思想 如果使用时间序列X和Y的历史值来 ...