一、概述
大数据
大数据(big data)是指无法在一定时间范围内用常规软件工具进行捕捉、管理和处理的数据集合,是需要新处理模式才能具有更强的决策力、洞察发现力和流程优化能力的海量、高增长率和多样化的信息资产
大数据的5V特点(IBM提出):
Hadoop是什么?
http://hadoop.apache.org
Apache Hadoop是一个开源、可靠、可扩展的分布式计算框架。
Hadoop框架允许用户在一个超大的规模的服务器集群中,对大数据集进行分布式的处理计算。Hadoop集群规模可以是单个(伪分布式集群)或者上千台的商用服务器(完全分布式集群)构成。Hadoop集群中每一个服务器都提供了本地计算和存储能力。Hadoop框架并不是通过硬件实现的高可用,而是通过应用层检测处理错误,那这样的话Hadoop集群就可以建立在廉价的商用服务器上。
- 狭义的Hadoop(六大模块)
- Hadoop Common: Hadoop框架通用支持库
- Hadoop Distributed File System (HDFS™): 分布式文件系统 提供了高吞吐能力的数据访问
- Hadoop YARN: 一个框架用来做任务的调度和分布式集群的资源管理
- Hadoop MapReduce: 基于YARN的系统,对大数据集进行分布式的并行计算处理
- Hadoop Ozone: Hadoop对象存储系统
- Hadoop Submarine: 机器学习的引擎
- 广义的Hadoop(泛指生态体系)
- Apache HBase : Big Table,用来存储海量的结构化数据
- Apache Zookeeper(动物园管理者): 分布式协调服务系统,主要解决Hadoop生态体系各个分布式系统存在的一些通用问题
- Apache Hive(小蜜蜂): 数据仓库的基础设施,用来简化Hadoop的操作
- Apache Flume(数据采集): 负责采集各种类型的数据,并且进行简单的预处理操作
- Apache Spark(scala语言): 更为高效的分布式计算引擎
- Apache Flink: 高效的分布式计算引擎(第三代数据分析引擎)
二、HDFS
HDFS是Hadoop的分布式文件系统( Hadoop Distributed File System ),类似于其它的分布式文件系统。HDFS支持高度容错,可以部署在廉价的硬件设备上,特别适宜于大型的数据集的分布式存储。
Google开源论文GFS的开源实现
环境搭建
构建HDFS的伪分布式集群(使用单台机器,模拟HDFS集群所有的服务)
-
安装CentOS
CentOS7.2版本
-
配置网络
# ip a 查看当前的服务器网络设置 vi /etc/sysconfig/network-scripts/ifcfg-ens33 # 将配置文件中的ONBOOT=yes systemctl restart network
-
关闭防火墙
[root@localhost ~]# systemctl stop firewalld [root@localhost ~]# systemctl disable firewalld Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service. Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
-
修改服务器的主机名
# 简化连接服务器操作 [root@localhost ~]# vi /etc/hostname # 删除localhost,新增hadoop(自定义的主机名)
-
配置主机名和ip地址的映射关系
[root@localhost ~]# vi /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 # 最后一行添加当前服务器ip地址和主机名的映射 192.168.12.129 hadoop # 测试 [root@localhost ~]# ping hadoop PING hadoop (192.168.12.129) 56(84) bytes of data. 64 bytes from hadoop (192.168.12.129): icmp_seq=1 ttl=64 time=0.107 ms 64 bytes from hadoop (192.168.12.129): icmp_seq=2 ttl=64 time=0.053 ms
-
配置SSH(Secure Shell)免密远程登录
[root@hadoop ~]# ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa Generating public/private rsa key pair. Your identification has been saved in /root/.ssh/id_rsa. Your public key has been saved in /root/.ssh/id_rsa.pub. The key fingerprint is: SHA256:/VJcuTQzpC4EDqiiEKWwwtYAqS9Von3ssc12fM+ldvQ root@hadoop The key's randomart image is: +---[RSA 2048]----+ |++. .. . . | |=o+ o o . o . | |=* * . . . B | |B + + o o o = | |o+ o = .S o + . | |o . o + o .+ o | | . . . ..o.+ . | | .= . E| | . . | +----[SHA256]-----+ [root@hadoop ~]# [root@hadoop ~]# [root@hadoop ~]# cd .ssh/ [root@hadoop .ssh]# ll 总用量 12 -rw-------. 1 root root 1679 8月 12 15:45 id_rsa -rw-r--r--. 1 root root 393 8月 12 15:45 id_rsa.pub -rw-r--r--. 1 root root 183 8月 12 15:43 known_hosts [root@hadoop .ssh]# cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys [root@hadoop .ssh]# ll 总用量 16 -rw-r--r--. 1 root root 393 8月 12 15:47 authorized_keys -rw-------. 1 root root 1679 8月 12 15:45 id_rsa -rw-r--r--. 1 root root 393 8月 12 15:45 id_rsa.pub -rw-r--r--. 1 root root 183 8月 12 15:43 known_hosts [root@hadoop .ssh]# chmod 0600 ~/.ssh/authorized_keys [root@hadoop .ssh]# [root@hadoop .ssh]# ssh hadoop Last login: Mon Aug 12 15:43:18 2019 from 192.168.12.1
- 安装JDK
[root@hadoop ~]# rpm -ivh jdk-8u191-linux-x64.rpm 警告:jdk-8u191-linux-x64.rpm: 头V3 RSA/SHA256 Signature, 密钥 ID ec551f03: NOKEY 准备中... ################################# [100%] 正在升级/安装... 1:jdk1.8-2000:1.8.0_191-fcs ################################# [100%] Unpacking JAR files... tools.jar... plugin.jar... javaws.jar... deploy.jar... rt.jar... jsse.jar... charsets.jar... localedata.jar... [root@hadoop ~]# java -version java version "1.8.0_191" Java(TM) SE Runtime Environment (build 1.8.0_191-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.191-b12, mixed mode)
- 安装Hadoop
[root@hadoop ~]# tar -zxf hadoop-2.6.0_x64.tar.gz -C /usr
- 修改HDFS集群的配置文件
[root@hadoop hadoop-2.6.0]# vim etc/hadoop/core-site.xml <property> <name>fs.defaultFS</name> <value>hdfs://CentOS :9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/usr/hadoop-2.6.0/hadoop-${user.name}</value> </property> [root@hadoop hadoop-2.6.0]# vim etc/hadoop/hdfs-site.xml <property> <name>dfs.replication</name> <value>1</value> </property> [root@hadoop hadoop-2.6.0]# vi etc/hadoop/slaves hadoop
- 添加环境变量配置
[root@hadoop ~]# vi .bashrc HADOOP_HOME=/usr/hadoop-2.6.0 JAVA_HOME=/opt/java/jdk1.8.0_171 CLASSPATH=. PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin export JAVA_HOME export CLASSPATH export PATH export HADOOP_HOME [root@hadoop ~]# source .bashrc
服务启动
- 初始化操作
[root@hadoop ~]# hdfs namenode -format
NOTE:
初始化操作只需要在第一次启动HDFS集群之前执行,后续不需要执行,跳过直接启动服务即可
- 启动HDFS集群
[root@hadoop ~]# start-dfs.sh
Starting namenodes on [hadoop]
hadoop: starting namenode, logging to /usr/hadoop-2.6.0/logs/hadoop-root-namenode-hadoop.out
hadoop: starting datanode, logging to /usr/hadoop-2.6.0/logs/hadoop-root-datanode-hadoop.out
Starting secondary namenodes [0.0.0.0]
The authenticity of host '0.0.0.0 (0.0.0.0)' can't be established.
ECDSA key fingerprint is SHA256:yDvdRHO65GeTfU6PJQjEKMap+lEZb8a/JeuesbTsMYs.
ECDSA key fingerprint is MD5:d4:bf:fe:86:d3:ed:2d:fc:5f:a2:2b:e5:86:0c:ae:ee.
Are you sure you want to continue connecting (yes/no)? yes
0.0.0.0: Warning: Permanently added '0.0.0.0' (ECDSA) to the list of known hosts.
0.0.0.0: starting secondarynamenode, logging to /usr/hadoop-2.6.0/logs/hadoop-root-secondarynamenode-hadoop.out
- 验证服务是否启动成功
# 1. java的指令 jps,查看java进程列表
[root@hadoop ~]# jps
10995 SecondaryNameNode # HDFS小蜜
10796 NameNode # HDFS Master
10877 DataNode # HDFS Slaves
# 2. 访问hdfs的web ui
http://服务器地址:50070
# 3. 分布式系统学会看日志
[root@hadoop hadoop-2.6.0]# cd logs/
[root@hadoop logs]# ll
总用量 92
-rw-r--r--. 1 root root 24249 8月 12 16:12 hadoop-root-datanode-hadoop.log
-rw-r--r--. 1 root root 714 8月 12 16:12 hadoop-root-datanode-hadoop.out
-rw-r--r--. 1 root root 30953 8月 12 16:17 hadoop-root-namenode-hadoop.log
-rw-r--r--. 1 root root 714 8月 12 16:12 hadoop-root-namenode-hadoop.out
-rw-r--r--. 1 root root 22304 8月 12 16:13 hadoop-root-secondarynamenode-hadoop.log
-rw-r--r--. 1 root root 714 8月 12 16:12 hadoop-root-secondarynamenode-hadoop.out
-rw-r--r--. 1 root root 0 8月 12 16:12 SecurityAuth-root.audit
- 关闭服务
[root@hadoop logs]# stop-dfs.sh
指令操作
HDFS分布式文件系统,操作类似于Linux文件系统
比如Linux:cp、mv、rm、cat、mkdir 常用指令非常类似
语法:hdfs dfs -参数
Usage: hadoop fs [generic options]
[-appendToFile <localsrc> ... <dst>]
[-cat [-ignoreCrc] <src> ...] # 查看文本文件内容
[-checksum <src> ...]
[-chgrp [-R] GROUP PATH...] # 修改属组
[-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...] # 修改权限
[-chown [-R] [OWNER][:[GROUP]] PATH...] # 修改属主
[-copyFromLocal [-f] [-p] [-l] <localsrc> ... <dst>] # 从本地拷贝到HDFS
[-copyToLocal [-p] [-ignoreCrc] [-crc] <src> ... <localdst>] # 从HDFS拷贝到本地
[-count [-q] [-h] <path> ...] # 计数
[-cp [-f] [-p | -p[topax]] <src> ... <dst>] # 拷贝
[-createSnapshot <snapshotDir> [<snapshotName>]]
[-deleteSnapshot <snapshotDir> <snapshotName>]
[-df [-h] [<path> ...]]
[-du [-s] [-h] <path> ...]
[-expunge]
[-get [-p] [-ignoreCrc] [-crc] <src> ... <localdst>] # 下载
[-getfacl [-R] <path>]
[-getfattr [-R] {-n name | -d} [-e en] <path>]
[-getmerge [-nl] <src> <localdst>]
[-help [cmd ...]] # 帮助
[-ls [-d] [-h] [-R] [<path> ...]] # 查看目录列表
[-mkdir [-p] <path> ...] # 创建文件夹
[-moveFromLocal <localsrc> ... <dst>] # 从本地移动到HDFS
[-moveToLocal <src> <localdst>] # 将HDFS中的文件移动到本地
[-mv <src> ... <dst>] # HDFS中的文件或文件夹的移动
[-put [-f] [-p] [-l] <localsrc> ... <dst>] # 上传
[-renameSnapshot <snapshotDir> <oldName> <newName>]
[-rm [-f] [-r|-R] [-skipTrash] <src> ...] # 删除
[-rmdir [--ignore-fail-on-non-empty] <dir> ...] # 删除文件夹
[-setfacl [-R] [{-b|-k} {-m|-x <acl_spec>} <path>]|[--set <acl_spec> <path>]]
[-setfattr {-n name [-v value] | -x name} <path>]
[-setrep [-R] [-w] <rep> <path> ...]
[-stat [format] <path> ...]
[-tail [-f] <file>] # 查看文本文件的末尾内容
[-test -[defsz] <path>]
[-text [-ignoreCrc] <src> ...]
[-touchz <path> ...]
[-usage [cmd ...]]
JAVA API操作
-
环境搭建(windows平台为例)
-
解压缩Hadoop的安装包
# 如解压缩安装到E:\\根目录
-
拷贝兼容文件到安装目录bin中
-
在windows的hosts文件中添加主机名和IP地址的映射关系
-
重启开发工具
-
配置HADOOP_HOME环境变量
-
-
实战
-
创建Maven工程,并导入HDFS Client Driver
<dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>2.6.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
-
测试代码
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FSDataInputStream; import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.permission.FsAction; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.io.IOUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; /** * hdfs java api测试 * FileSystem */ public class HDFSDemo { /** * hdfs 客户端操作对象 */ private FileSystem fileSystem = null; private Configuration configuration = null; @Before public void doBefore() throws URISyntaxException, IOException { URI uri = new URI("hdfs://hadoop:9000"); configuration = new Configuration(); fileSystem = FileSystem.get(uri, configuration); } /** * 文件上传 * put * copyFromLocal * moveFromLocal * * @org.apache.hadoop.security.AccessControlException: Permission denied: user=Administrator, access=WRITE, inode="/baizhi":root:supergroup:drwxr-xr-x * 解决方案: 1. 修改权限 (UGO) o+w 2. 修改操作hdfs用户身份:-DHADOOP_USER_NAME=root 3. 关闭hdfs权限检查功能: hdfs-site.xml <property> <name>dfs.permissions.enabled</name> <value>false</value> </property> */ @Test public void testUpload() throws IOException { Path src = new Path("G:\\apache-tomcat-7.0.85.zip"); Path dst = new Path("/baizhi"); fileSystem.copyFromLocalFile(src, dst); } @Test public void testUpload2() throws IOException { FileInputStream src = new FileInputStream("F:\\生态图.png"); Path dst = new Path("/baizhi/test"); FSDataOutputStream dstOutputStream = fileSystem.create(dst); IOUtils.copyBytes(src, dstOutputStream, configuration); } /** * 下载文件 * get * copyToLocal * moveToLocal */ @Test public void testDownload() throws IOException { Path src = new Path("/baizhi/test"); Path dst = new Path("G:\\1.png"); fileSystem.copyToLocalFile(src, dst); } @Test public void testDownload2() throws IOException { FSDataInputStream inputStream = fileSystem.open(new Path("/baizhi/test")); FileOutputStream outputStream = new FileOutputStream("G:\\2.png"); IOUtils.copyBytes(inputStream, outputStream, configuration); } /** * 删除文件 */ @Test public void testDelete() throws IOException { // fileSystem.delete(new Path("/baizhi/test"),false); // true代表递归删除 fileSystem.delete(new Path("/baizhi"), true); } @Test public void testOther() throws IOException { // rwxrw-r-- /baizhi // fileSystem.mkdirs(new Path("/baizhi"), new FsPermission(FsAction.ALL, FsAction.READ_WRITE, FsAction.READ)); boolean exists = fileSystem.exists(new Path("/baizhi")); System.out.println(exists?"存在":"不存在"); } @After public void doAfter() throws IOException { fileSystem.close(); } }
-
HDFS架构
HDFS采用master/slave架构。一个HDFS集群是由一个Namenode和一定数目的Datanodes组成。Namenode是一个中心服务器器,负责管理文件系统的名字空间(namespace)以及客户端对文件的访问。集群中的Datanode一般是一个节点一个,负责管理它所在节点上的存储。 HDFS暴露了了文件系统的名字空间,用户能够以文件的形式在上面存储数据。从内部看,一个文件其实被分成一个或多个数据块,这些块存储在一组Datanode上。 Namenode执行文件系统的名字空间操作,比如打开、关闭、重命名文件或目录。它也负责确定数据块到具体Datanode节点的映射。 Datanode负责处理文件系统客户端的读写请求。在Namenode的统一调度下进行数据块的创建、删除和复制。
- Namenode : 存储系统元数据、 namespace、管理datanode、接受datanode状态汇报
- Datanode: 存储块数据,响应客户端的块的读写,接收namenode的块管理理指令
- Block: HDFS存储数据的基本单位,默认值是128MB,实际块大小0~128MB
- Rack: 机架,对datanode所在主机的物理标识,标识主机的位置,优化存储和计算
架构图
Block的复制原理
元数据(MetaData)的持久化机制
Namenode使用内存存储MetaData,存在安全风险,HDFS提供了元数据的持久化
好处: 保证元数据绝对不会丢失,并且
fsimage
加速Namenode元数据的恢复速度
HDFS常见问题
-
为什么HDFS不适合小文件存储?
情况 Namenode占用 Datanode占用 10000个文件总共128MB 10000个元数据 128MN 1个128MB文件 1个元数据 128MB - 小文件过多,会过多占用namenode的内存,并浪费block
- HDFS适用于高吞吐量,而不适合低时间延迟的访问。文件过小,寻道时间大于数据读写时间,这不符合HDFS的设计原则
-
Namenode和SecondaryNameNode区别?
Namenode主要维护两个组件,一个是 fsimage ,一个是 editlog- fsimage保存了最新的元数据检查点,包含了整个HDFS文件系统的所有目录和文件的信息。对于文件来说包括了数据块描述信息、修改时间、访问时间等;对于目录来说包括修改时间、访问权限控制信息(目录所属用户,所在组)等。
- editlog主要是在NameNode已经启动情况下对HDFS进⾏的各种更新操作进行记录,HDFS客户端执行所有的写操作都会被记录到editlog中。
为了避免editlog不断增加,secondary namenode会周期性合并fsimage和edits成新的fsimage
三、YARN
架构理解
https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YARN.html
Apache Hadoop YARN (Yet Another Resource Negotiator,另一种资源协调者)是一种新的 Hadoop 资源管理器,它是一个通用资源管理系统,可为上层应用提供统一的资源管理和调度,它的引入为集群在利用率、资源统一管理和数据共享等方面带来了巨大好处。
- ResourceManager:是在系统中的所有应用程序之间仲裁资源的最终权限。
- NodeManager:是每台机器框架代理,负责容器,监视其资源使用情况(CPU,内存,磁盘,网络)并将其报告给ResourceManager的Scheduler
- App Master :应用的Master负责任务计算过程中的任务监控、故障转移,每个Job只有一个。
- Container:表示一个计算进程
环境搭建
-
修改
mapred-site.xml
[root@hadoop ~]# cd /usr/hadoop-2.6.0/ [root@hadoop hadoop-2.6.0]# mv etc/hadoop/mapred-site.xml.template etc/hadoop/mapred-site.xml [root@hadoop hadoop-2.6.0]# vi etc/hadoop/mapred-site.xml # 添加以下内容 <property> <name>mapreduce.framework.name</name> <value>yarn</value> </property>
-
修改
yarn-site.xml
[root@hadoop hadoop-2.6.0]# vi etc/hadoop/yarn-site.xml <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property> <property> <name>yarn.resourcemanager.hostname</name> <value>hadoop</value> </property>
-
启动YARN的服务
伪分布式的YARN集群
[root@hadoop hadoop-2.6.0]# start-yarn.sh starting yarn daemons starting resourcemanager, logging to /usr/hadoop-2.6.0/logs/yarn-root-resourcemanager-hadoop.out hadoop: starting nodemanager, logging to /usr/hadoop-2.6.0/logs/yarn-root-nodemanager-hadoop.out [root@hadoop hadoop-2.6.0]# jps 6892 ResourceManager # master 6974 NodeManager # slave
四、MapReduce
思想理解
Hadoop MapReduce是一个软件框架,基于该框架能够容易地编写应用程序,这些应用程序能够运行在由上千个商用机器组成的大集群上,并以一种可靠的,具有容错能力的方式并行地处理上TB级别的海量数据集。这个定义里面有着这些关键词:
一是软件框架,二是并行处理,三是可靠且容错,四是大规模集群,五是海量数据集。
MapReduce擅长处理大数据,它为什么具有这种能力呢?这可由MapReduce的设计思想发觉。MapReduce的思想就是“分而治之”或者“化繁为简”。
Mapper
负责“分”,即把复杂的任务分解为若干个“简单的任务”来处理。 “简单的任务”包含三层含义:- 是数据或计算的规模相对原任务要大大缩小;
- 是就近计算原则,即任务会分配到存放着所需数据的节点上进行计算;
- 是这些小任务可以并行计算,彼此间几乎没有依赖关系。
Reducer
主要负责对map阶段的结果进行汇总
基本开发
新建Maven工程,导入依赖
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-common</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>2.6.0</version>
</dependency>
开发MapReduce应用程序
单词计数的应用程序
MapReduce应用程序的两个阶段:
- Mapper:将大任务拆分为若干个小任务,将非结构化的数据映射为KV结构数据
- Reducer:负责计算统计
准备样例文件
How are you
Where are you from
Welcome to BJ
Are you ok
将模拟数据上传到HDFS中
[root@hadoop ~]# hdfs dfs -put data.txt /baizhi
定义Mapper任务
package com.baizhi;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* *Writable表示的Hadoop提供的序列化对象
* LongWritable
* IntWritable
* String ---> Text
* ...
* <p>
* Mapper阶段
* keyIn: LongWritable 每行数据的首字符的offset(位置)
* valueIn: Text 一行记录
* keyOut: Text 单词
* valueOut: IntWritable 初始值 1
*/
public class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
/**
* 映射方法
* How are you ---> (how,1) (are,1) (you,1)
*
* @param key keyIn
* @param value valueIn
* @param context 上下文(MapReduce应用程序运行的上下文信息)
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] words = line.toLowerCase().split(" ");
for (String word : words) {
// 输出处理完成kv数据
context.write(new Text(word), new IntWritable(1));
}
}
}
定义Reducer任务
package com.baizhi;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.Iterator;
/**
* reducer阶段 统计和计算
* keyIn:类型等价于Mapper的keyOut
* valueIn:类型等价于Mapper的valueOut
* keyOut:单词 Text
* valueOut:总次数 IntWritable
*/
public class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
/**
* 统计计算方法
* how are you
* are you ok
* are [1,1]
* you [1,1]
* how [1]
*
* @param key 单词
* @param values key相同的初始值的集合
* @param context 上下文对象
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int count = 0;
Iterator<IntWritable> iterator = values.iterator();
while (iterator.hasNext()){
int num = iterator.next().get(); // 1
count += num;
}
// 计算完成后 输出结算结果
context.write(key,new IntWritable(count));
}
}
初始化类
package com.baizhi;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.IOException;
/**
* 单词计数的初始化类
*/
public class WordCountApplication {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//1. 创建MapReduce任务对象
Configuration conf = new Configuration();
String jobName = "wordcount";
Job job = Job.getInstance(conf,jobName);
job.setJarByClass(WordCountApplication.class);
//2. 设置计算数据的输入格式和计算结果的输出格式(文本)
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
//3. 指定计算数据的来源位置以及计算结果的输出位置
TextInputFormat.addInputPath(job,new Path("/baizhi/data.txt"));
// 注意:计算结果的输出目录必须不存在
TextOutputFormat.setOutputPath(job,new Path("/baizhi/result"));
//4. 指定MapReduce应用的Mapper阶段和Reducer阶段的实现类
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
//5. 设置Mapper阶段和Reducer阶段的KeyOut和ValueOut的类型
job.setMapOutputKeyClass(Text.class); // mapper的keyOut的类型
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//6. 任务提交
job.waitForCompletion(true); // true 输出运行日志
}
}
打包MapReduce应用程序为jar
测试运行
-
将应用jar包 上传到Linux操作系统中
-
使用命令提交MapReduce应用程序
语法:
hadoop jar xxx.jar 入口类的全限定名
查看计算结果
第二个案例(流量统计)
MapReduce应用程序的其它运行方式
注意:
在生产环境中,MapReduce Application一定是运行在YARN分布式集群中的
但是在测试开发MapReduce程序,我们可以使用以下方式,来测试代码
本地计算 + 本地数据
本地计算指的是借助于Windows平台的hadoop环境模拟运行MapReduce程序
本地数据指的是计算的数据来源于Windows平台,并且输出到本地
- 修改初始化类中如下代码
// 注意:file:/// 表示使用本地文件系统中的数据
TextInputFormat.addInputPath(job,new Path("file:///e:\\ssby.txt"));
// 注意:计算结果的输出目录必须不存在
TextOutputFormat.setOutputPath(job,new Path("file:///e:\\result"));
-
运行程序
右键初始化类 --> Run as
# 如出现以下异常 Exception in thread "main" java.lang.UnsatisfiedLinkError: org.apache.hadoop.io.nativeio.NativeIO$Windows.access0(Ljava/lang/String;I)Z # 解决方案: 1. 在项目的根目录中新建包 org.apache.hadoop.io.nativeio 2. 在包中新建类 NativeIO 3. 找到Hadoop的NativeIO类将所有的代码复制到自建的NativeIO中 4. 修改NativeIO中的源码(关联源码是557,未关联287行),将return true; 5. 重新运行,得到运行结果
本地计算 + 远程数据
- 修改初始化类
//3. 指定计算数据的来源位置以及计算结果的输出位置
TextInputFormat.addInputPath(job,new Path("hdfs://hadoop:9000/baizhi/data.txt"));
// 注意:计算结果的输出目录必须不存在
TextOutputFormat.setOutputPath(job,new Path("hdfs://hadoop:9000/baizhi/result3"));
- 运行程序
右键初始化类 --> Run as
- 访问控制异常,添加虚拟机参数
-DHADOOP_USER_NAME=root
远程计算 + 远程数据
远程计算指MapReduce应用程序依然运行在YARN集群中
远程数据指数据依赖来源于HDFS或者输出到HDFS
-
修改初始化类
// 添加远程计算的支持 //=============================================================== conf.set("fs.defaultFS", "hdfs://hadoop:9000/"); conf.set("mapreduce.job.jar", "file:///F:\\IdeaProjects\\20190812\\hadoop-mapreduce\\target\\hadoop-mapreduce-1.0-SNAPSHOT.jar"); conf.set("mapreduce.framework.name", "yarn"); conf.set("yarn.resourcemanager.hostname", "hadoop"); conf.set("yarn.nodemanager.aux-services", "mapreduce_shuffle"); conf.set("mapreduce.app-submission.cross-platform", "true"); conf.set("dfs.replication", "1"); //===============================================================
-
将Maven项目重新打包
maven plugin ---> package---> xxx.jar
-
运行程序
右键初始化类 --> Run as
- 有某系统的访问日志的样例数据,访问日志的格式如下:
# 客户端的ip地址 请求时间 请求方式 访问资源 响应的字节大小 状态码
192.168.0.3 2019-08-14 15:30:15 GET /index.jsp 300 200
11.135.14.110 2019-08-14 15:32:10 POST /user/login.do 500 404
..,,
-
PV
(Page View): 系统的访问量mapreduce map: k: 日期 v: 1 reduce: k: 日期 values: [1,1,1,1]
-
UV
(Unique View): 独立用户的访问量mapreduce map: k: 日期 v:ip reduce: k: 日期 values:[ip,ip,ip] values ---> Set
MapReduce程序的运行流程
MapReduce任务提交的源码剖析
InputFormat和OutputFormat
InputFormat
InputFormat数据的输入格式对象
TextInputFormat为例探讨背后事情
getSplits
createRecordReader
结论:
InputFormat
决定了如何对计算的数据集进行逻辑切割(140.8MB)InputFormat
决定了如何解析读取数据切片(split)中的数据内容,并且map任务的keyIn和valueIn的类型由RecordReader中的key、value决定。- 一个
inputSplit
会由一个Map任务进行映射处理inputformat
负责输入数据的合法性校验
常见的InputFormat
-
FileInputFormat
-
TextInputFormat
: 基于文本的数据输入格式对象特点:按行读取文本中的数据,KeyIn:LongWritable ValueIn:Text
-
NLineInputFormat
特点:将文本中的N行(默认为1行)数据作一个数据切片,KeyIn:LongWritable ValueIn:Text
设置N行:
conf.set("mapreduce.input.lineinputformat.linespermap","3");
-
KeyValueLineRecordReader
特点:按照KV解析文本中的数据. KeyIn:Text ValueIn:Text
数据切片的计算规则等同于
TextInputFormat
数据切片的读取方式按照KV的结构进行解析
mapreduce.input.keyvaluelinerecordreader.key.value.separator
默认为\t
例如:
conf.set("mapreduce.input.keyvaluelinerecordreader.key.value.separator",",");
-
FixedLengthInputFormat
-
CombineTextInputFormat
特点:将多个小文件的内容整合到一个数据切片中, KeyIn:LongWritable ValueIn: Text
-
-
DBInputFormat
-
DBInputFormat
特点: 从数据库中获取数据,将获得的数据作为Map任务的输入
KeyIn: LongWritable ValueIn:extends DBWritable
-
开发自定义的Writable对象,读写数据库表中的记录
package com.baizhi.inputformat.db; import org.apache.hadoop.mapreduce.lib.db.DBWritable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Date; /** * 通过OrderWritable对象读写数据库的记录 */ public class OrderWritable implements DBWritable { private Integer orderId; private Double totalMoney; private Date createTime; private Integer userId; public OrderWritable() { } public OrderWritable(Integer orderId, Double totalMoney, Date createTime, Integer userId) { this.orderId = orderId; this.totalMoney = totalMoney; this.createTime = createTime; this.userId = userId; } public void write(PreparedStatement pstm) throws SQLException { pstm.setInt(2, this.orderId); pstm.setDouble(3, this.totalMoney); java.sql.Date date = new java.sql.Date(this.createTime.getTime()); pstm.setDate(4, date); pstm.setInt(5, this.userId); } public void readFields(ResultSet rs) throws SQLException { this.orderId = rs.getInt("order_id"); this.totalMoney = rs.getDouble("total_money"); this.createTime = rs.getDate("create_time"); this.userId = rs.getInt("user_id"); } }
-
开发处理的Map任务
package com.baizhi.inputformat.db; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; import java.util.Date; public class OrderMapper extends Mapper<LongWritable, OrderWritable, Text, DoubleWritable> { /** value: 数据库一行记录 */ @Override protected void map(LongWritable key, OrderWritable value, Context context) throws IOException, InterruptedException { Date createTime = value.getCreateTime(); Integer userId = value.getUserId(); Double totalMoney = value.getTotalMoney(); String month = createTime.getYear() + "-" + createTime.getMonth() + "-" + userId; context.write(new Text(month), new DoubleWritable(totalMoney)); } }
-
开发统计的Reduce任务
package com.baizhi.inputformat.db; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; import java.util.Iterator; public class OrderReducer extends Reducer<Text, DoubleWritable, Text, DoubleWritable> { @Override protected void reduce(Text key, Iterable<DoubleWritable> values, Context context) throws IOException, InterruptedException { double sum = 0.0D; Iterator<DoubleWritable> iterator = values.iterator(); while (iterator.hasNext()) { DoubleWritable money = iterator.next(); sum += money.get(); } context.write(key, new DoubleWritable(sum)); } }
-
设置初始化类
package com.baizhi.inputformat.db; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.DoubleWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapred.lib.db.DBInputFormat; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.db.DBConfiguration; import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; import org.iq80.leveldb.DB; import java.io.IOException; public class OrderComputApplication { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { Configuration configuration = new Configuration(); // 设置数据源信息 configuration.set(DBConfiguration.DRIVER_CLASS_PROPERTY,"com.mysql.jdbc.Driver"); configuration.set(DBConfiguration.URL_PROPERTY,"jdbc:mysql://localhost:3306/vue"); configuration.set(DBConfiguration.USERNAME_PROPERTY,"root"); configuration.set(DBConfiguration.PASSWORD_PROPERTY,"root"); Job job = Job.getInstance(configuration, "order"); job.setJarByClass(OrderComputApplication.class); job.setInputFormatClass(DBInputFormat.class); job.setOutputFormatClass(TextOutputFormat.class); // select order_id,total_money... from t_order where ... order by ... DBInputFormat.setInput(job,OrderWritable.class,"t_order",null,null, "order_id","total_money","create_time","user_id"); TextOutputFormat.setOutputPath(job,new Path("file:///E:/result5")); job.setMapperClass(OrderMapper.class); job.setReducerClass(OrderReducer.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(DoubleWritable.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(DoubleWritable.class); job.waitForCompletion(true); } }
-
引入数据源驱动的Jar包
本地计算:在Maven项目中导入mysql的依赖即可
远程计算:将MySQL的驱动jar包上传到
hadoop安装目录的/share/hadoop/yarn/lib
中
-
-
OutputFormat
OutputFormat数据的输出格式对象,决定了如何将Reducer的计算结果输出到指定的存储系统中
常见的OutputFormat
-
FileOutputFormat:基于文件的数据输出格式
-
TextOutputFormat
特点: 计算的结果以文本的形式保存在文件中,文本中一行结果为Reduce方法的keyOut valueOut
-
-
DBOutputFormat: 基于数据库的数据输出格式
-
DBOutputFormat
特点:将Reducer的计算结果输出保存到数据库,reduce方法每输出一次则在数据库产生一条记录
-
-
TableOutputFormat: 基于HBase的数据输出格式
OutputFormat作用
结论:
- 决定了计算的结果以何种格式保存到指定的存储系统中
- 校验计算结果的输出位置是否合法