Hadoop笔记

一、概述

大数据

大数据(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: 高效的分布式计算引擎(第三代数据分析引擎)

hadoop大数据实训总结 hadoop大数据分析培训_hadoop大数据实训总结

二、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]# vi etc/hadoop/core-site.xml
<property>
   <name>fs.defaultFS</name>
   <value>hdfs://hadoop: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]# vi 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=/usr/java/latest
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中

hadoop大数据实训总结 hadoop大数据分析培训_Hadoop_02

  • 在windows的hosts文件中添加主机名和IP地址的映射关系
  • hadoop大数据实训总结 hadoop大数据分析培训_HDFS_03

  • 重启开发工具
  • 配置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="/dingl":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("/dingl");
        fileSystem.copyFromLocalFile(src, dst);
    }

    @Test
    public void testUpload2() throws IOException {
        FileInputStream src = new FileInputStream("F:\\生态图.png");
        Path dst = new Path("/dingl/test");
        FSDataOutputStream dstOutputStream = fileSystem.create(dst);
        IOUtils.copyBytes(src, dstOutputStream, configuration);
    }

    /**
     * 下载文件
     * get
     * copyToLocal
     * moveToLocal
     */
    @Test
    public void testDownload() throws IOException {
        Path src = new Path("/dingl/test");
        Path dst = new Path("G:\\1.png");
        fileSystem.copyToLocalFile(src, dst);
    }

    @Test
    public void testDownload2() throws IOException {
        FSDataInputStream inputStream = fileSystem.open(new Path("/dingl/test"));
        FileOutputStream outputStream = new FileOutputStream("G:\\2.png");
        IOUtils.copyBytes(inputStream, outputStream, configuration);
    }

    /**
     * 删除文件
     */
    @Test
    public void testDelete() throws IOException {
        // fileSystem.delete(new Path("/dingl/test"),false);
        // true代表递归删除
        fileSystem.delete(new Path("/dingl"), true);
    }

    @Test
    public void testOther() throws IOException {
        // rwxrw-r--  /dingl
        // fileSystem.mkdirs(new Path("/dingl"), new FsPermission(FsAction.ALL, FsAction.READ_WRITE, FsAction.READ));
        boolean exists = fileSystem.exists(new Path("/dingl"));
        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所在主机的物理标识,标识主机的位置,优化存储和计算
架构图

hadoop大数据实训总结 hadoop大数据分析培训_hadoop大数据实训总结_04

Block的复制原理

hadoop大数据实训总结 hadoop大数据分析培训_HDFS_05

元数据(MetaData)的持久化机制

Namenode使用内存存储MetaData,存在安全风险,HDFS提供了元数据的持久化

好处: 保证元数据绝对不会丢失,并且fsimage加速Namenode元数据的恢复速度

hadoop大数据实训总结 hadoop大数据分析培训_Hadoop_06

HDFS常见问题

  • 为什么HDFS不适合小文件存储?

情况

Namenode占用

Datanode占用

10000个文件总共128MB

10000个元数据

128MN

1个128MB文件

1个元数据

128MB

  • 小文件过多,会过多占用namenode的内存,并浪费block
  • HDFS适用于高吞吐量,而不适合低时间延迟的访问。文件过小,寻道时间大于数据读写时间,这不符合HDFS的设计原则
  • Namenode和SecondaryNameNode区别?
    Namenode主要维护两个组件,一个是 fsimage ,一个是 editlog
  1. fsimage保存了最新的元数据检查点,包含了整个HDFS文件系统的所有目录和文件的信息。对于文件来说包括了数据块描述信息、修改时间、访问时间等;对于目录来说包括修改时间、访问权限控制信息(目录所属用户,所在组)等。
  2. editlog主要是在NameNode已经启动情况下对HDFS进⾏的各种更新操作进行记录,HDFS客户端执行所有的写操作都会被记录到editlog中。
为了避免editlog不断增加,secondary namenode会周期性合并fsimage和edits成新的fsimage

hadoop大数据实训总结 hadoop大数据分析培训_Hadoop_07

三、YARN

架构理解

https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YARN.html

Apache Hadoop YARN (Yet Another Resource Negotiator,另一种资源协调者)是一种新的 Hadoop 资源管理器,它是一个通用资源管理系统,可为上层应用提供统一的资源管理和调度,它的引入为集群在利用率、资源统一管理和数据共享等方面带来了巨大好处。

hadoop大数据实训总结 hadoop大数据分析培训_HDFS_08

  • 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应用程序的两个阶段:

  1. Mapper:将大任务拆分为若干个小任务,将非结构化的数据映射为KV结构数据

  2. Reducer:负责计算统计

准备样例文件
How are you
Where are you from
Welcome to BJ
Are you ok
将模拟数据上传到HDFS中
[root@hadoop ~]# hdfs dfs -put data.txt /dingl
定义Mapper任务
package com.dingl;

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.dingl;

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.dingl;

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("/dingl/data.txt"));
        // 注意:计算结果的输出目录必须不存在
        TextOutputFormat.setOutputPath(job,new Path("/dingl/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

hadoop大数据实训总结 hadoop大数据分析培训_Hadoop_09

测试运行
  • 将应用jar包 上传到Linux操作系统中

  • 使用命令提交MapReduce应用程序

  • 语法: hadoop jar xxx.jar 入口类的全限定名
  • hadoop大数据实训总结 hadoop大数据分析培训_HDFS_10

查看计算结果

hadoop大数据实训总结 hadoop大数据分析培训_HDFS_11

第二个案例(流量统计)

hadoop大数据实训总结 hadoop大数据分析培训_Hadoop_12

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/dingl/data.txt"));
// 注意:计算结果的输出目录必须不存在
TextOutputFormat.setOutputPath(job,new Path("hdfs://hadoop:9000/dingl/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程序的运行流程

hadoop大数据实训总结 hadoop大数据分析培训_hadoop_13

MapReduce任务提交的源码剖析

hadoop大数据实训总结 hadoop大数据分析培训_hadoop_14

InputFormat和OutputFormat

hadoop大数据实训总结 hadoop大数据分析培训_hadoop大数据实训总结_15

InputFormat

InputFormat数据的输入格式对象

[

TextInputFormat为例探讨背后事情

  • getSplits
  • hadoop大数据实训总结 hadoop大数据分析培训_HDFS_16


  • createRecordReader

结论:

  1. InputFormat决定了如何对计算的数据集进行逻辑切割(140.8MB
    1. InputFormat决定了如何解析读取数据切片(split)中的数据内容,并且map任务的keyIn和valueIn的类型由RecordReader中的key、value决定。
    2. 一个inputSplit会由一个Map任务进行映射处理
    3. 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

      hadoop大数据实训总结 hadoop大数据分析培训_hadoop大数据实训总结_17

      • 开发自定义的Writable对象,读写数据库表中的记录

        package com.dingl.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.dingl.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.dingl.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.dingl.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的计算结果输出到指定的存储系统中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1GZipV9-1570095866933)(assets/1565920835698.png)]

常见的OutputFormat
  • FileOutputFormat:基于文件的数据输出格式

    • TextOutputFormat

      特点: 计算的结果以文本的形式保存在文件中,文本中一行结果为Reduce方法的keyOut valueOut

  • DBOutputFormat: 基于数据库的数据输出格式

    • DBOutputFormat

      特点:将Reducer的计算结果输出保存到数据库,reduce方法每输出一次则在数据库产生一条记录

  • TableOutputFormat: 基于HBase的数据输出格式

OutputFormat作用

结论:

  1. 决定了计算的结果以何种格式保存到指定的存储系统中
    1. 校验计算结果的输出位置是否合法

Shuffle原理剖析

hadoop大数据实训总结 hadoop大数据分析培训_hadoop大数据实训总结_18

Shuffle,是指对Map输出结果进行分区、排序、合并等处理并交给Reduce的过程。分为Map端的操作和Reduce端的操作。

Shuffle过程

  • Map端的Shuffle

    Map的输出结果首先被缓存到内存,当缓存区容量到达80%(缓冲区默认100MB),就启动溢写操作。当启动溢写操作时,首先需要把缓存中的数据进行分区,然后对每个分区的数据进行排序和合并(combine),之后再写入磁盘文件。每次溢写操作会生成一个新的磁盘文件,随着Map任务的执行,磁盘中就会生成多个溢写文件。在Map任务全部结束前,这些溢写文件会被归并成一个大的磁盘文件,然后通知相应的Reduce任务来领取属于自己处理的数据。

  • 在Reduce端的Shuffle过程

    Reduce任务从Map端的不同Map机器领回属于自己处理的那部分数据,然后对数据进行合并排序后交给Reduce处理

作用

  • 保证每一个Reduce任务处理的数据大致是一致的

  • Map任务输出的key相同,一定是相同分区,并且肯定是相同的Reduce处理的,保证计算结果的准确性

  • Reduce任务的数量决定了分区的数量,Reduce任务越多计算处理的并行度也就越高

    Reduce任务的数量(默认为1)可以通过:job.setNumReduceTasks(数量)

特点

  • Map端溢写时,key相同的一定是在相同的分区
  • Map端溢写时,排序减少了Reduce的全局排序的复杂度
  • Map端溢写是,合并(combiner【可选】)减少溢写文件的体积,提高了Reduce任务在Fetch数据时的效率,它是一种MapReduce优化策略
  • Reduce端计算或者输出时,它的数据都是有序的

Shuffle源码追踪

  • MapTask

hadoop大数据实训总结 hadoop大数据分析培训_hadoop大数据实训总结_19

  • ReduceTask

    (略)

    建议阅读

数据清洗

数据清洗指将原始数据处理成有价值的数据的过程,就称为数据清洗。

企业大数据开发的基本流程:

  1. 采集数据(flume、logstash)先保存到MQ(Kafka)中

  2. 将MQ中的暂存数据存放到HDFS中保存

  3. 数据清洗(低价值密度的数据处理)存放到HDFS

  4. 算法干预(MapReduce),计算结果保存到HDFS或者HBase

  5. 计算结果的可视化展示(Echarts、HCharts)

需求

现有某系统某天的Nginx的访问日志,格式如下:

27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/common/faq.gif HTTP/1.1" 200 1127
110.52.250.126 - - [30/May/2013:17:38:20 +0800] "GET /data/cache/style_1_widthauto.css?y7a HTTP/1.1" 200 1292
27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/common/hot_1.gif HTTP/1.1" 200 680
27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/common/hot_2.gif HTTP/1.1" 200 682
27.19.74.143 - - [30/May/2013:17:38:20 +0800] "GET /static/image/filetype/common.gif HTTP/1.1" 200 90

大数据处理的算法,需要参数客户端的ip地址、请求时间、资源、响应状态码

正则表达式提取数据

Regex Expression主要作用字符串匹配抽取和替换

语法

规则

解释

.

匹配任意字符

\d

匹配任意数字

\D

匹配任意非数字

\w

配置a-z和A-Z

\W

匹配非a-z和A-Z

\s

匹配空白符

^

匹配字符串的开头

$

匹配字符串的末尾

规则的匹配次数

语法

解释

*

规则匹配0到N次

规则匹配1次

{n}

规则匹配N次

{n,m}

规则匹配n到m次

+

规则匹配1到N次(至少一次)

应用
# 匹配手机号码 11位数值构成
\d{11}

# 邮箱地址校验  @
.+@.+

使用正则表达式提取Nginx访问日志中的四项指标

测试站点:http://regex101.com

hadoop大数据实训总结 hadoop大数据分析培训_hadoop大数据实训总结_20

分析后得到需要的正则表达式

^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*\[(.*)\]\s"\w*\s(.*)\sHTTP\/1.1"\s(\d{3}).*$

使用MapReduce分布式并行计算框架进行数据清洗

注意: 因为数据清洗不涉及统计计算,所以MapReduce程序通常只有map任务,而没有Reduce任务

job.setNumReduceTasks(0)

实现代码

数据清洗的Mapper

package com.dingl.dataclean;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DataCleanMapper extends Mapper<LongWritable, Text, Text, NullWritable> {

    /**
     * @param key
     * @param value   nginx访问日志中的一行记录(原始数据)
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        final String regex = "^(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*\\[(.*)\\]\\s\"\\w*\\s(.*)\\sHTTP\\/1.1\"\\s(\\d{3}).*$";
        String line = value.toString();

        final Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE);
        final Matcher matcher = pattern.matcher(line);

        while (matcher.find()) {
            // 四项关键指标  ip 请求时间 请求资源 响应状态码
            String clientIp = matcher.group(1);
            // yyyy-MM-dd HH:mm:ss
            String accessTime = matcher.group(2);
            String accessResource = matcher.group(3);
            String status = matcher.group(4);

            // 30/May/2013:17:38:21 +0800
            // 30/05/2013:17:38:21
            SimpleDateFormat sdf = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss", Locale.ENGLISH);
            try {
                Date date = sdf.parse(accessTime);
                SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                String finalDate = sdf2.format(date);
                context.write(new Text(clientIp + " " + finalDate + " " + accessResource + " " + status), null);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
}

初始化类

package com.dingl.dataclean;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 DataCleanApplication {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Job job = Job.getInstance(new Configuration(), "data clean");
        job.setJarByClass(DataCleanApplication.class);

        job.setInputFormatClass(TextInputFormat.class);
        job.setOutputFormatClass(TextOutputFormat.class);

        TextInputFormat.setInputPaths(job,new Path("file:///E:/access.log"));
        TextOutputFormat.setOutputPath(job,new Path("file:///E:/final"));

        job.setMapperClass(DataCleanMapper.class);

        // 注意:数据清洗通常只有map任务而没有reduce
        job.setNumReduceTasks(0);

        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);

        job.waitForCompletion(true);
    }
}

数据倾斜

数据分区默认策略

hadoop大数据实训总结 hadoop大数据分析培训_Hadoop_21

数据倾斜指大量的key相同的数据交由一个reduce任务统计计算,造成”闲的闲死,忙的忙死“这样的现象。不符合分布式并行计算的设计初衷的。

现象

  • 某一个reduce运行特别耗时

  • Reduce任务内存突然溢出

解决方案

  • 增大Reduce任务机器JVM的内存(硬件的水平扩展)

  • 增加Reduce任务的数量,每个Reduce任务只负责极少部分的数据处理,并且Reduce任务的数量增加提高了数据计算的并行度

Reduce任务的正确数量: 0.95或者1.75 * (NodeManage数量 * 每个节点最大容器数量)

  • 自定义分区规则Partitioner
package com.dingl.partition;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapreduce.Partitioner;


/**
 * 自定义分区规则
 */
public class CustomPartitioner extends Partitioner<Text, LongWritable> {

    /**
     * @param key
     * @param value
     * @param i     numReduceTasks
     * @return 分区序号
     */
    public int getPartition(Text key, LongWritable value, int i) {
        if (key.toString().equals("CN-GD")) return 0;
        else if (key.toString().equals("CN-GX")) return 1;
        else if (key.toString().equals("CN-HK")) return 2;
        else if (key.toString().equals("JP-TY")) return 3;
        else return 4;
    }
}
  • 合适使用Combiner,将key相同的value进行整合合并

在combiner合并时,v必须得能支持迭代计算,并且不能够影响Reduce任务的输入

combiner通常就是Reducer任务

// 优化策略:combiner合并操作
job.setCombinerClass(MyReducer.class);

五、Hadoop完全分布式集群

架构

hadoop大数据实训总结 hadoop大数据分析培训_hadoop大数据实训总结_22

环境搭建

准备3台虚拟机

  • Node1: 192.168.12.130
  • Node2: 192.168.12.131
  • Node3: 192.168.12.132

服务划分

服务名

node1

node2

node3

Namenode

Y(主)

Y(备)

Datanode

Y

Y

Y

Journal node

Y

Y

Y

Zookeeper

Y

Y

Y

ResourceManager

Y(主)

Y(备)

NodeManger

Y

Y

Y

配置步骤

准备安装包
# centos7
# jdk8
# hadoop-2.6.0
# zookeeper
修改主机名和IP地址的映射文件
[root@nodex ~]# vi /etc/hosts
192.168.12.130  node1
192.168.12.131  node2
192.168.12.132  node3
关闭防火墙
[root@nodex ~]# systemctl stop firewalld
[root@nodex ~]# systemctl disable firewalld
Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
SSH免密登陆
[root@nodex ~]# ssh-keygen -t rsa
[root@nodex ~]# ssh-copy-id node1
[root@nodex ~]# ssh-copy-id node2
[root@nodex ~]# ssh-copy-id node3
同步时钟
[root@nodeX ~]# date -s '2018-12-1 20:06:00'
2018年 12月 01日 星期六 20:06:00 CST
[root@nodeX ~]# clock -w
[root@nodeX ~]# date
2018年 12月 01日 星期六 20:06:09 CST

注意:服务器集群时钟如果一致,可以跳过此步骤

修改服务器的主机名
[root@x ~]# vi /etc/hostname
# 192.168.12.130
node1
# 192.168.12.131
node2
# 192.168.12.132
node3

重启机器,生效

安装JDK
# 将集群搭建的安装包上传到某节点
# 利用scp命令拷贝到其它节点
[root@node1 ~]# scp jdk-8u191-linux-x64.rpm root@node2:~
jdk-8u191-linux-x64.rpm
[root@node1 ~]# scp jdk-8u191-linux-x64.rpm root@node3:~
jdk-8u191-linux-x64.rpm
安装ZooKeeper
[root@node1 ~]# scp zookeeper-3.4.6.tar.gz root@node2:~
zookeeper-3.4.6.tar.gz     100%   17MB  84.3MB/s   00:00
[root@node1 ~]# scp zookeeper-3.4.6.tar.gz root@node3:~
zookeeper-3.4.6.tar.gz     100%   17MB  73.4MB/s   00:00

[root@nodex ~]# tar -zxf zookeeper-3.4.6.tar.gz -C /usr
[root@nodex ~]# vi /usr/zookeeper-3.4.6/conf/zoo.cfg
tickTime=2000
dataDir=/root/zkdata
clientPort=2181
initLimit=5
syncLimit=2
server.1=node1:2887:3887
server.2=node2:2887:3887
server.3=node3:2887:3887

[root@nodex ~]# mkdir -p /root/zkdata

# node1执行此指令
[root@node1 ~]# cd zkdata/
[root@node1 zkdata]# vi myid
1

# node2执行此指令
[root@node2 ~]# cd zkdata/
[root@node2 zkdata]# vi myid
2

# node3执行此指令
[root@node3 ~]# cd zkdata/
[root@node3 zkdata]# vi myid
3

# 启动ZooKeeper集群
[root@nodex ~]# /usr/zookeeper-3.4.6/bin/zkServer.sh start /usr/zookeeper-3.4.6/conf/zoo.cfg
JMX enabled by default
Using config: /usr/zookeeper-3.4.6/conf/zoo.cfg
Starting zookeeper ... STARTED
# 确认zookeper服务是否正常:方法一
[root@nodex ~]# jps
1777 QuorumPeerMain
1811 Jps
# 确认zookeper服务是否正常:方法二
[root@nodex ~]# /usr/zookeeper-3.4.6/bin/zkServer.sh status /usr/zookeeper-3.4.6/conf/zoo.cfg
JMX enabled by default
Using config: /usr/zookeeper-3.4.6/conf/zoo.cfg
Mode: leader
安装Hadoop
#1. 将hadoop的安装包远程拷贝到其它的节点
[root@node1 ~]# scp hadoop-2.6.0_x64.tar.gz root@node2:~
hadoop-2.6.0_x64.tar.gz    100%  172MB 113.8MB/s   00:01
[root@node1 ~]# scp hadoop-2.6.0_x64.tar.gz root@node3:~
hadoop-2.6.0_x64.tar.gz    100%  172MB 120.8MB/s   00:01

#2. 安装
[root@nodex ~]# tar -zxf hadoop-2.6.0_x64.tar.gz -C /usr

#3. 配置java和hadoop的环境变量
[root@nodex ~]# vi ~/.bashrc
HADOOP_HOME=/usr/hadoop-2.6.0
JAVA_HOME=/usr/java/latest
CLASSPATH=.
PATH=$PATH:$JAVA_HOME/bin:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
export JAVA_HOME
export CLASSPATH
export PATH
export HADOOP_HOME
[root@nodex ~]# source ~/.bashrc
修改hadoop的配置文件
  • core-site.xml
[root@nodex ~]# vi /usr/hadoop-2.6.0/etc/hadoop/core-site.xml
<property>
  <name>fs.defaultFS</name>
  <value>hdfs://mycluster</value>
</property>
<property>
  <name>hadoop.tmp.dir</name>
  <value>/usr/hadoop-2.6.0/hadoop-${user.name}</value>
</property>
<property>
  <name>fs.trash.interval</name>
  <value>30</value>
</property>
<property>
  <name>net.topology.script.file.name</name>
  <value>/usr/hadoop-2.6.0/etc/hadoop/rack.sh</value>
</property>
  • 创建机架脚本文件,该脚本可以根据IP判断机器所处的物理位置
[root@nodex ~]# vi /usr/hadoop-2.6.0/etc/hadoop/rack.sh
while [ $# -gt 0 ] ; do
	nodeArg=$1
	exec</usr/hadoop-2.6.0/etc/hadoop/topology.data
	result=""
	while read line ; do
        ar=( $line )
        if [ "${ar[0]}" = "$nodeArg" ] ; then
        result="${ar[1]}"
	fi
	done
    shift
    if [ -z "$result" ] ; then
    echo -n "/default-rack"
    else
    echo -n "$result "
    fi
done

[root@nodex ~]# chmod u+x /usr/hadoop-2.6.0/etc/hadoop/rack.sh
[root@nodeX ~]# vi /usr/hadoop-2.6.0/etc/hadoop/topology.data
192.168.12.130 /rack1
192.168.12.131 /rack2
192.168.12.132 /rack2
[root@nodeX ~]# /usr/hadoop-2.6.0/etc/hadoop/rack.sh 192.168.23.137
/rack1
  • hdfs-site.xml
[root@nodex ~]# vi /usr/hadoop-2.6.0/etc/hadoop/hdfs-site.xml
<property>
	<name>dfs.replication</name>
	<value>3</value>
</property>
<property>
	<name>dfs.ha.automatic-failover.enabled</name>
	<value>true</value>
</property>
<property>
	<name>ha.zookeeper.quorum</name>
	<value>node1:2181,node2:2181,node3:2181</value>
</property>
<property>
    <name>dfs.nameservices</name>
    <value>mycluster</value>
</property>
<property>
    <name>dfs.ha.namenodes.mycluster</name>
    <value>nn1,nn2</value>
</property>
<property>
    <name>dfs.namenode.rpc-address.mycluster.nn1</name>
    <value>node1:9000</value>
</property>
<property>
    <name>dfs.namenode.rpc-address.mycluster.nn2</name>
    <value>node2:9000</value>
</property>
<property>
    <name>dfs.namenode.shared.edits.dir</name>
    <value>qjournal://node1:8485;node2:8485;node3:8485/mycluster</value>
</property>
<property>
    <name>dfs.client.failover.proxy.provider.mycluster</name>
    <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
<property>
    <name>dfs.ha.fencing.methods</name>
    <value>sshfence</value>
</property>
<property>
    <name>dfs.ha.fencing.ssh.private-key-files</name>
    <value>/root/.ssh/id_rsa</value>
</property>
  • slaves
[root@nodex ~]# vi /usr/hadoop-2.6.0/etc/hadoop/slaves
node1
node2
node3

启动HDFS HA集群的服务

[root@nodex ~]# hadoop-daemon.sh start journalnode
[root@node1 ~]# hdfs namenode -format
[root@node1 ~]# hadoop-daemon.sh start namenode
[root@node2 ~]# hdfs namenode -bootstrapStandby
[root@node2 ~]# hadoop-daemon.sh start namenode
# zkfc: zookeeper failover controller
[root@node1|2 ~]# hdfs zkfc -formatZK (可以在node1或者node2任意一台注册namenode信息)
[root@node1 ~]# hadoop-daemon.sh start zkfc (哨兵)
[root@node2 ~]# hadoop-daemon.sh start zkfc (哨兵)
[root@nodeX ~]# hadoop-daemon.sh start datanode

注意:CentOS7需要安装一个中间依赖服务

[root@nodex ~]# yum install -y psmisc

YARN的HA集群

  • mapred-site.xml

    [root@nodex ~]# cp /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml.template /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml
    [root@nodex ~]# vi /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml
    
    <property>
        <name>mapreduce.framework.name</name>
        <value>yarn</value>
    </property>
  • yarn-site.xml

    <property>
        <name>yarn.nodemanager.aux-services</name>
    	<value>mapreduce_shuffle</value>
    </property>
    <property>
        <name>yarn.resourcemanager.ha.enabled</name>
        <value>true</value>
    </property>
    <property>
        <name>yarn.resourcemanager.cluster-id</name>
        <value>cluster1</value>
    </property>
    <property>
        <name>yarn.resourcemanager.ha.rm-ids</name>
        <value>rm1,rm2</value>
    </property>
    <property>
        <name>yarn.resourcemanager.hostname.rm1</name>
        <value>node2</value>
    </property>
    <property>
        <name>yarn.resourcemanager.hostname.rm2</name>
        <value>node3</value>
    </property>
    <property>
        <name>yarn.resourcemanager.zk-address</name>
        <value>node1:2181,node2:2181,node3:2181</value>
    </property>
  • 启动YARN

    [root@node2 ~]# yarn-daemon.sh start resourcemanager
    [root@node3 ~]# yarn-daemon.sh start resourcemanager
    [root@nodeX ~]# yarn-daemon.sh start nodemanager
  • 查看ResourceManager HA状态

    [root@node1 ~]# yarn rmadmin -getServiceState rm1
    active
    [root@node1 ~]# yarn rmadmin -getServiceState rm2
    standby

p-daemon.sh start namenode

zkfc: zookeeper failover controller

[root@node1|2 ~]# hdfs zkfc -formatZK (可以在node1或者node2任意一台注册namenode信息)
[root@node1 ~]# hadoop-daemon.sh start zkfc (哨兵)
[root@node2 ~]# hadoop-daemon.sh start zkfc (哨兵)
[root@nodeX ~]# hadoop-daemon.sh start datanode

> 注意:CentOS7需要安装一个中间依赖服务 
>
>  `[root@nodex ~]# yum install -y psmisc`



#### YARN的HA集群

- `mapred-site.xml`

  ```xml
  [root@nodex ~]# cp /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml.template /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml
  [root@nodex ~]# vi /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml
  
  <property>
      <name>mapreduce.framework.name</name>
      <value>yarn</value>
  </property>
  • yarn-site.xml

    <property>
        <name>yarn.nodemanager.aux-services</name>
    	<value>mapreduce_shuffle</value>
    </property>
    <property>
        <name>yarn.resourcemanager.ha.enabled</name>
        <value>true</value>
    </property>
    <property>
        <name>yarn.resourcemanager.cluster-id</name>
        <value>cluster1</value>
    </property>
    <property>
        <name>yarn.resourcemanager.ha.rm-ids</name>
        <value>rm1,rm2</value>
    </property>
    <property>
        <name>yarn.resourcemanager.hostname.rm1</name>
        <value>node2</value>
    </property>
    <property>
        <name>yarn.resourcemanager.hostname.rm2</name>
        <value>node3</value>
    </property>
    <property>
        <name>yarn.resourcemanager.zk-address</name>
        <value>node1:2181,node2:2181,node3:2181</value>
    </property>
  • 启动YARN

    [root@node2 ~]# yarn-daemon.sh start resourcemanager
    [root@node3 ~]# yarn-daemon.sh start resourcemanager
    [root@nodeX ~]# yarn-daemon.sh start nodemanager
  • 查看ResourceManager HA状态

    [root@node1 ~]# yarn rmadmin -getServiceState rm1
    active
    [root@node1 ~]# yarn rmadmin -getServiceState rm2
    standby