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

二、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 812 15:45 id_rsa
    -rw-r--r--. 1 root root  393 812 15:45 id_rsa.pub
    -rw-r--r--. 1 root root  183 812 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 812 15:47 authorized_keys
    -rw-------. 1 root root 1679 812 15:45 id_rsa
    -rw-r--r--. 1 root root  393 812 15:45 id_rsa.pub
    -rw-r--r--. 1 root root  183 812 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 812 16:12 hadoop-root-datanode-hadoop.log
-rw-r--r--. 1 root root   714 812 16:12 hadoop-root-datanode-hadoop.out
-rw-r--r--. 1 root root 30953 812 16:17 hadoop-root-namenode-hadoop.log
-rw-r--r--. 1 root root   714 812 16:12 hadoop-root-namenode-hadoop.out
-rw-r--r--. 1 root root 22304 812 16:13 hadoop-root-secondarynamenode-hadoop.log
-rw-r--r--. 1 root root   714 812 16:12 hadoop-root-secondarynamenode-hadoop.out
-rw-r--r--. 1 root root     0 812 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个文件总共128MB10000个元数据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

    在这里插入图片描述

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

  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 /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

结论:

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

结论:

  1. 决定了计算的结果以何种格式保存到指定的存储系统中
  2. 校验计算结果的输出位置是否合法
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值