hadoop笔记-pjj


文章内容输出来源:拉勾教育大数据训练营;

hadoop入门

大数据:大存储+大计算。

狭义hadoop:hdfs、mapreduce、yarn、common
-》hdfs 分布式文件系统
-》mapreduce 分布式计算框架
-》yarn 分布式资源管理和任务调度
-》common 公共组件,例如Configuration、RPC(跨节点通信)、序列化机制、日志操作等

广义hadoop(hadoop生态圈):hive、hbase、spark、storm、flume、kafka等等

离线计算:
flume+hdfs+spark+hbase

实时计算:
flume+kafka+spark streaming+hbase
flume+kafka+storm+hbase
flume+kafka+flink+hbase

配置文件介绍:
*-env.sh :配置程序环境变量,一般只改JDK
*-site.xml :配置程序属性
slaves :主从框架中的从节点信息

端口号:
hdfs :50070 http协议端口,webUI
hdfs :8020 rpc协议端口
yarn :8088 http协议端口,webUI
yarn :8032 rpc协议端口
端口号文件:etc/hadoop/core-site.xml

集群搭建过程

安装包准备:
apache版本的hadoop-2.9.2.tar.gz

软件安装

1、节点规划
在这里插入图片描述
2、在所有节点创建目录
mkdir -p /opt/lagou/software
mkdir -p /opt/lagou/servers

3、在131节点上传apache版本的hadoop到 /opt/lagou/software 目录下

4、安装hadoop:
tar -zxvf hadoop-2.9.2.tar.gz -C /opt/lagou/servers
安装完成后将文件改名为 hadoop-2.9.2

5、添加hadoop到系统环境变量:
vim /etc/profile
添加如下内容:
#HADOOP_HOME
export HADOOP_HOME=/opt/lagou/servers/hadoop-2.9.2
export PATH=$PATH:$HADOOP_HOME/bin
export PATH=$PATH:$HADOOP_HOME/sbin
修改完成刷新环境变量:source /etc/profile

6、验证是否安装成功
hadoop version

7、hdfs的集群配置
修改hadoop的jdk配置:
vim /opt/lagou/servers/hadoop-2.9.2/etc/hadoop/hadoop-env.sh

#export JAVA_HOME=${JAVA_HOME}
export JAVA_HOME=/opt/lagou/servers/jdk1.8.0_231

修改namenode节点和数据存放的路径:
vim /opt/lagou/servers/hadoop-2.9.2/etc/hadoop/core-site.xml

<configuration>
  <!--指定namenode节点,9000为namenode通信端口-->
  <property>
    <name>fs.defaultFS</name>
    <value>hdfs://linux131:9000</value>
  </property>

  <!--指定hadoop运行时产生的文件的存储目录-->
  <property>
    <name>hadoop.tmp.dir</name>
    <value>/opt/lagou/servers/hadoop-2.9.2/data/tmp</value>
  </property>
</configuration>

配置SecondaryNameNode节点和hdfs的副本数量:
vim /opt/lagou/servers/hadoop-2.9.2/etc/hadoop/hdfs-site.xml

<configuration>
  <!--指定SecondaryNameNode节点-->
  <property>
    <name>dfs.namenode.secondary.http-address</name>
    <value>linux134:50090</value>
  </property>
  <!--副本数量-->
  <property>
    <name>dfs.replication</name>
    <value>3</value>
  </property>
</configuration>

配置从节点信息:
vim /opt/lagou/servers/hadoop-2.9.2/etc/hadoop/slaves
注意不要空格和换行,也不要使用ip地址

linux131
linux132
linux134

8、MapReduce的集群配置
将jdk指定给MR:
vim /opt/lagou/servers/hadoop-2.9.2/etc/hadoop/mapred-env.sh

export JAVA_HOME=/opt/lagou/servers/jdk1.8.0_231

指定MR的运行框架为yarn资源调度框架:
mv mapred-site.xml.template mapred-site.xml
vim mapred-site.xml

<configuration>
  <!--指定MapReduce程序运行在yarn上-->
  <property>
    <name>mapreduce.framework.name</name>
    <value>yarn</value>
  </property>
</configuration>

9、Yarn的集群配置
将jdk指定给yarn:vim yarn-env.sh

export JAVA_HOME=/opt/lagou/servers/jdk1.8.0_231

指定ResourceManager节点:
vim yarn-site.xml

<configuration>
  <!--指定yarn的ResourceManager地址-->
  <property>
    <name>yarn.resourcemanager.hostname</name>
    <value>linux132</value>
  </property>
  <!--Reducer数据的获取方式-->
  <property>
    <name>yarn.nodemanager.aux-services</name>
    <value>mapreduce_shuffle</value>
  </property>
</configuration>

yarn的从节点与hdfs共用slaves文件,不需再配置

10、修改hadoop的所属用户和用户权限
chown -R root:root hadoop-2.9.2

11、分发hadoop-2.9.2和/etc/profile到其他节点

hadoop集群的启动

在NameNode节点进行格式化(格式化只能做一次):
hadoop namenode -format

格式化成功会提示如下:

20/08/02 19:33:42 INFO common.Storage: Storage directory /opt/lagou/servers/hadoop-2.9.2/data/tmp/dfs/name has been successfully formatted.

单节点启动hdfs

1、启动131节点的NameNode
hadoop-daemon.sh start namenode

2、启动131、132和134节点的DataNode
hadoop-daemon.sh start datanode

-》web端访问页面地址:http://linux131:50070

单节点启动yarn

1、启动132节点的 ResourceManager
yarn-daemon.sh start resourcemanager

2、启动131、132、134节点的NodeManager
yarn-daemon.sh start nodemanager

集群群起

单节点启动效率低,可改为群起,群起之前先关闭hadoop各进程
1、启动hdfs
start-dfs.sh

2、启动yarn
start-yarn.sh

hadoop的历史日志和日志聚集

历史日志

1、所有节点配置mapred-site.xml

  <!--指定历史服务器的节点-->
  <property>
    <name>mapreduce.jobhistory.address</name>
    <value>linux131:10020</value>
  </property>
  <!--指定历史服务器web端访问地址-->
  <property>
    <name>mapreduce.jobhistory.webapp.address</name>
    <value>linux131:19888</value>
  </property>

2、启动历史服务
mr-jobhistory-daemon.sh start historyserver

-》历史服务web端访问地址:http://linux131:19888

日志聚集

1、所有节点配置yarn-site.xml

  <!--开启日志聚集功能-->
  <property>
    <name>yarn.log-aggregation-enable</name>
    <value>true</value>
  </property>
  <!--日志的保留时间设置为7天-->
  <property>
    <name>yarn.log-aggregation.retain-seconds</name>
    <value>604800</value>
  </property>

2、重启yarn集群和历史服务

HDFS分布式文件系统

组合多台Linux文件系统,对外提供统一的访问入口,解决了传统单机无法存储大容量文件的问题
(假设每个服务器可以存20GB的数据,三台服务器就可以存60GB的文件)
在这里插入图片描述
hdfs文件的存储方式为分块存储,默认128M为1个block块
block默认保存3个副本,如果其中一个datanode挂了,namenode在心跳机制触发后,会寻找一台新的datanode节点生成一个新的副本,当挂掉的datanode恢复后,该节点上的副本会删除

hdfs主从架构

NameNode

1、接收读写请求
2、管理datanode
心跳机制:DN定时向NN反馈存活状态,3次反馈失败踢出集群
汇报机制:保证数据安全性,DN反馈自己存储了哪些block,如果有block丢失,则补副本
3、维护元数据信息
{block1:128M,3,[node1,node2,node3]}
//表示block1大小为128M,共3个副本,分别保存在哪些节点上

查看元数据信息:
cd $HADOOP_HOME/data/tmp/dfs/name/current
hdfs oiv -p XML -i fsimage_0000000000000000047 -o 生成的xml文件保存到哪个目录
hdfs oev -p XML -i edits_inprogress_0000000000000000048 生成的xml文件保存到哪个目录

DataNode

真正存储数据的节点

SeconderyNameNode

实现元数据的持久化同步:
-》元数据是存储在NameNode内存中的,如果NN服务器挂了,元数据会,因此需要持久化。
-》SNN将内存中的元数据保存到本地元数据文件(fsimage)中,每次NN启动时会加载fsimage文件中的元数据到内存
-》每次内存中的元数据发生改变时,操作日志会记录在edits文件中,SNN会定时将旧的fsimage与edits合成新的fsimage
-》元数据文件路径:$HADOOP_HOME/data/tmp/dfs/name/current/
-》SNN的启动:sbin/hadoop-daemon.sh start secondarynamenode

hdfs读写过程

写入:
client客户端提交写的请求(计算出需要存多少个block)
namenode先将操作写入edit文件,再将操作写入内存
namenode同时返回一个输出流对象和每个block可以存储在哪些datanode给客户端
client将文件按128M大小进行拆分,将每个block发送给最近的datanode节点
第一个存储block的datanode节点将数据备份给第二个datanode,第二个再备份给第三个
每个datanode节点成功保存block之后,返回确认信息,失败则重试直到成功
写完数据,关闭输出流,反馈结果给namenode

读取:
client提交读取请求给namenode
namenode查找元数据字典,返回文件的每个block所在位置和输入流对象
client挑选离自己最近的datanode节点获取block
客户端将block整合成完整文件,关闭输入流

hdfs命令

大部分命令与linux命令相似,详细见官网,有差异的命令:

创建文件:-touchz /aaaaa.txt

将hdfs目录的文件以合并的方式下载到本地:-getmerge <hdfs目录> <本地目录>
例如:-getmerge /data/* /opt/lagou/datas

向hdfs文件中追加内容:
1、将本地文件里的内容追加到hdfs文件中
hdfs dfs -appendToFile /opt/lagou/datas/aaaa.txt /bbbb.txt
2、将字符串内容追加到hdfs文件中
echo “xxxxx” | hdfs dfs -appendToFile - /bbbb.txt

bin/hdfs dfsadmin 执行管理命令
-report 打印集群状态
-safemode 安全模式
-refreshNodes 刷新集群节点(增删节点时)

bin/hdfs haadmin 管理HDFS高可用(两个NameNode手动切换管理)

bin/hdfs fsck 检查及修复文件系统

bin/hdfs balance 实现存储的负载均衡

集群迁移,旧集群数据迁到新集群,分布式拷贝distributed copy:
hadoop distcp hdfs://nn1:8020/sourcedir(旧集群) hdfs://nn2:8020/destinationdir(新集群)

安全模式

问:fsimage和edits文件中是没有记录block块所在节点的信息的,内存中存储的元数据是有block所在节点的信息的,节点信息从何而来?

答:在集群启动时,会首先进入一个30秒的安全模式,内存从fsimage和edits加载到内存中,datanode通过汇报机制向namenode反馈自身存储的block信息,故每个块的存放节点信息由此而来

手动开启安全模式:hdfs dfsadmin -safemode enter
手动关闭安全模式:hdfs dfsadmin -safemode leave

SecondaryNamenode的checkpoint

何时生成新的edits文件?固定时间生成,或固定次数生成
配置文件:hdfs-default.xml

<!--定时1小时-->
<property>
  <name>dfs.namenode.checkpoint.period</name>
  <value>3600</value>
</property>

<!--每分钟检查一次操作次数,当达到100万次时,执行一次-->
<property>
  <name>dfs.namenode.checkpoint.txns</name>
  <value>1000000</value>
</property>

<property>
  <name>dfs.namenode.checkpoint.check.period</name>
  <value>60</value>
</property>

hadoop限额

限定上传到hdfs目录下的文件大小和个数

举例:
能上传到/test01目录下的文件个数小于2个:
hdfs dfsadmin -setQuota 2 /test01

取消文件个数的限额:
hdfs dfsadmin -clrQuota /test01

能上传到/test01目录下的文件大小不能大于4k:
hdfs dfsadmin -setSpaceQuota 4k /test01

取消文件大小的限额
hdfs dfsadmin -clrSpaceQuota /test01

hadoop的归档

将多个小文件产生的元数据,归档成一个压缩后的格式进行存储,可节省存储资源
生成的 _index 文件记录了压缩的是哪些文件

hadoop archive -archiveName input.har -p /input_test /output_test

查看归档文件:hdfs dfs -lsr har://output_test/input.har

拷贝har里的数据:hdfs dfs -cp har://output_test/input.har/* /test02

hdfs的java API

windows环境准备:
1、用windows开始菜单搜索功能找到WinRAR,右键以管理员身份运行
2、文件 – 找到要解压的 hadoop-2.9.2.tar.gz – 解压到 – E:\Hadoop
3、配置windows的环境变量HADOOP_HOME
4、把 winutils.exe 放到 E:\Hadoop\hadoop-2.9.2\bin 目录下
5、把集群的节点,配置到C:\Windows\System32\drivers\etc\hosts

创建maven工程
1、导入maven依赖,需要hadoop-common,hadoop-client,hadoop-hdfs依赖
可在https://mvnrepository.com/ 搜索依赖

        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.9.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>2.9.2</version>
        </dependency>

2、为了测试方便,导入junit依赖

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>

3、导入log4j的日志依赖

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>

4、在 src --》 main --》 resources 目录下创建 log4j.properties 文件
可调节 log4j.rootLogger 为 ERROR 或 WARN 来控制日志输出的级别

log4j.rootLogger=INFO,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

5、api接口编程
创建 Package包: com.lagou.hdfs
创建 Java Class类:HdfsClientDemo

在hdfs上创建目录(mkdir)

package com.lagou.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.Test;

import java.net.URI;

public class HdfsClientDemo {

    @Test
    public void testMkdir() throws Exception {

        //1 获取hadoop集群的配置文件
        Configuration conf = new Configuration();
        //conf.set("dfs.replication","2") //conf的set方法可以修改配置文件

        //2 构建分布式文件系统对象FileSystem
        FileSystem fs =FileSystem.get(new URI("hdfs://linux131:9000"),conf,"root");

        //3 通过fs对象调用api操作hdfs
        fs.mkdirs(new Path("/test_mkdir"));

        //4 关闭文件系统释放资源
        fs.close();
    }
}

如果遇到报错:如果没有指定登录用户,则默认的是windows的Administrator用户

org.apache.hadoop.security.AccessControlException: Permission denied: user=31370, access=WRITE, inode="/":root:supergroup:drwxr-xr-x

解决1:每次使用都指定登录用户
解决2:关闭hdfs集群权限校验

vim hdfs-site.xml
添加:
<property>
  <name>dfs.permissions</name>
  <value>true</value>
</property>

解决3:hadoop fs -chmod -R 777 /

修改hdfs的配置属性

方法1、conf.set(“dfs.replication”,“2”)

方法2、在resources目录下创建hdfs-site.xml文件

属性生效的优先级:代码中的配置 > 工程中的配置 > 集群中的配置

上传和下载

package com.lagou.hdfs;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.net.URI;

public class HdfsClientDemo {
    FileSystem fs = null;

    @Before
    public void init() throws Exception {
        Configuration conf = new Configuration();
        fs =FileSystem.get(new URI("hdfs://linux131:9000"),conf,"root");
    }

    @After
    public void destroy() throws IOException {
        fs.close();
    }
    
    //copyFromLocal 上传文件
    @Test
    public void testCopyFromLocal() throws IOException {
        fs.copyFromLocalFile(new Path("e:/test/aaaa.txt"),new Path("/bbbb.txt"));
    }

    //copyToLocal 下载文件
    @Test
    public void testCopyToLocal() throws IOException {
        fs.copyToLocalFile(new Path("/bbbb.txt"),new Path("e:/test/cccc.txt"));
        //true表示删除源文件
        //fs.copyToLocalFile(true,new Path("/bbbb.txt"),new Path("e:/test/cccc.txt"));
    }
}

删除目录/文件

    //rm    删除文件/目录,true表示递归删除
    @Test
    public void delFile() throws IOException {
        fs.delete(new Path("/test_mkdir2"),true);
    }

获取文件详细信息

    //-du -h    查看目录/文件的详细信息
    @Test
    public void listFiles() throws IOException {
        //listFiles获取到的是包含所有文件详细信息的迭代器
        RemoteIterator<LocatedFileStatus> ri=fs.listFiles(new Path("/"),true); // true表示递归
        while (ri.hasNext()){
            LocatedFileStatus status = ri.next();

            //文件名称
            String fileName = status.getPath().getName();

            //文件长度
            long len = status.getLen();

            //文件权限
            FsPermission permission = status.getPermission();

            //文件所属组
            String group = status.getGroup();

            //文件所属用户
            String owner = status.getOwner();

            System.out.println(fileName+"\t"+len+"\t"+permission+"\t"+group+"\t"+owner);

            //文件块的信息,得到的是所有块信息组成的数组
            BlockLocation[] bls = status.getBlockLocations();
            for (BlockLocation bl:bls){
                //block存储的主机,得到的是一个数组
                String[] hosts = bl.getHosts();
                for (String host:hosts) {
                    System.out.println("主机名:"+host);
                }
            }

            System.out.println("--------------------------------------------------------");

        }
    }

判断是文件还是目录

    //判断是文件还是目录
    @Test
    public void isFile() throws IOException {
        FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
        for (FileStatus fileStatus:fileStatuses){
            //判断是不是目录
            boolean flag = fileStatus.isDirectory();
            if(flag){
                System.out.println("目录:"+fileStatus.getPath().getName());
            }else {
                System.out.println("文件:"+fileStatus.getPath().getName());
            }
        }
    }

FileStatus没有递归的重载方法,需要自己重写

IO流读写hdfs文件 – 上传

    @Test
    public void uploadFile() throws IOException {
        //输入流读取本地文件
        FileInputStream inputStream = new FileInputStream(new File("E:/test/aaaa.txt"));
        //输出流保存文件到hdfs
        FSDataOutputStream output = fs.create(new Path("/lagou1.txt"));
        //上传文件
        IOUtils.copyBytes(inputStream,output,conf);
        //关闭流
        IOUtils.closeStream(output);
        IOUtils.closeStream(inputStream);
    }

IO流读写hdfs文件 – 下载

    @Test
    public void downloadFile() throws IOException {
        //输入流读取hdfs文件
        FSDataInputStream input = fs.open(new Path("/lagou.txt"));
        //输出流保存文件到本地
        FileOutputStream outputStream = new FileOutputStream(new File("e:/test/dddd.txt"));
        //下载文件
        IOUtils.copyBytes(input,outputStream,conf);
        //关闭流
        IOUtils.closeStream(outputStream);
        IOUtils.closeStream(input);
    }

seek定位读取

    //seek定位读取,例如从hdfs文件的第2位开始读取,结果输出到控制台
    @Test
    public void seekUploadFile() throws IOException {
        FSDataInputStream input = fs.open(new Path("/lagou1.txt"));
        input.seek(2);
        IOUtils.copyBytes(input,System.out,4096,false);
        IOUtils.closeStream(input);
    }

双缓冲机制

为了避免高并发情况产生数据异常,要加锁,在锁时,生成唯一递增的 txid
buffer1 负责接收客户端的请求
buffer2 负责把元数据修改日志写入磁盘
在这里插入图片描述

Yarn分布式资源管理和任务调度

resourcemanager,负责资源管理,整合多台机器的资源(cpu、内存、硬盘等)
nodemanager,负责运行任务
container,任务的运行是在容器container中实现的
app master,负责任务调度,资源申请,数据分片,任务执行优先级

yarn上运行MR程序的过程

1)开发MR程序打成jar包
2)client提交运行请求给ResourceManager
3)RM根据每个NM的可用资源情况,挑选一个有空闲资源的NM初始化一个APP master管理任务
4)APPmaster向RM申请运行任务需要的资源
-》运行的NM节点有哪些
-》每个container的资源,默认1核1GB
-》运行job的jar包
5)APPmaster将申请到的资源和运行指令分发给每台NM
6)NM启动并监控各自的container,如果遇到资源不足等问题,强制杀掉进程重新向RM申请资源
7)每台NM启动Map task和Reduce task执行任务(优先处理自己节点的数据块)
8)所有NM的任务结束,反馈结果给APPmaster
9)APPmaster反馈最终结果给RM,任务结束

在这里插入图片描述

更详细的过程:
1、把程序jar包放到客户端节点
2、客户端提交任务请求
3、RM返回一个 app id 和资源提交路径 hdfs://…/staging
4、客户端将job运行所需的资源提交至hdfs路径(jar包、任务信息 job.xml 、分片信息 job.split)
5、客户端向 RM 申请 app master
6、RM 初始化一个Task任务(先进先出),并挑选一台 NM 节点作为 app master 的运行节点
7、NM 获取到任务
8、NM 创建 Container 容器(app master 的运行需要容器),app master 会计算出需要多少个 map task 和 reduce task
9、app master 节点从资源路径下载资源到本地
10、app master 向 RM 申请运行 map task 的容器
11、NM 获取到 map task 任务(NM 会与 app master 产生通信)
12、app master 向 NM 节点发送程序的启动脚本(yarn jar xxx.jar className input output)
13、app master 向 RM 节点申请运行 reduce task 的容器
14、NM 获取到 reduce task 任务
15、reduce task 从 map生成的文件获取属于自己的数据,运行 reduce task
16、整个任务结束后,app master 向 RM 提交注销申请

调度策略

在这里插入图片描述

FIFO

(先进先出)
按照任务到达的时间排序,先到先服务,只有一个队列
缺点:阻塞问题,如果前面的任务需要的资源量很大,如果该任务不完成,其他任务都要等待
在这里插入图片描述

Capacity Scheduler

(容量调度器)
使用多队列共享集群资源,可以进行配比
缺点:如果任务比较少,例如当前只有1个任务在队列a 中,则只能使用20%的集群资源,造成另外80%的资源浪费
在这里插入图片描述

Fair Scheduler

(公平调度器)
是对 Capacity Scheduler 的改进,抢占式资源分配,无竞争者是独占资源,有竞争者是平均分配
如果只有 队列a 的 任务1 ,会获得所有集群资源
如果此时加入了 队列b 的 任务11 ,会与 队列a 的 任务1 平分资源
如果此时加入了 队列b 的 任务12 ,则 任务1 占用集群的1/2资源,任务11和任务12 各占用集群的1/4资源

yarn多租户资源隔离配置

yarn集群资源设置为 A 和 B 两个队列
A 队列占用集群资源的 70% ,主要用来运行日常的定时任务
B 队列占用集群资源的30 % ,主要用来运行临时任务
两个队列之间可资源共享,假如 A 队列满了,B 队列资源比较充足,A 队列可使用 B 队列的资源

1、配置 yarn-site.xml

<!--指定任务调度策略采用 Fair Scheduler 方式-->
<property>
  <name>yarn.resourcemanager.scheduler.class</name>
  <value>org.apache.hadoop.yarn.server.resourcemanager.Scheduler.fair.FairScheduler</value>
</property>

2、在hadoop的配置文件目录下创建 fair-scheduler.xml 文件

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<allocations>
  <defaultQueueSchedulingPolicy>fair</defaultQueueSchedulingPolicy>
  <queue name="root" >
    <queue name="default">
      <aclAdministerApps>*</aclAdministerApps>
      <aclSubmitApps>*</aclSubmitApps>
      <maxResources>9216 mb,4 vcores</maxResources>
      <maxRunningApps>100</maxRunningApps>
      <minResources>1024 mb,1vcores</minResources>
      <minSharePreemptionTimeout>1000</minSharePreemptionTimeout>
      <schedulingPolicy>fair</schedulingPolicy>
      <weight>7</weight>
    </queue>
    <queue name="queue1">
      <aclAdministerApps>*</aclAdministerApps>
      <aclSubmitApps>*</aclSubmitApps>
      <maxResources>4096 mb,4vcores</maxResources>
      <maxRunningApps>5</maxRunningApps>
      <minResources>1024 mb, 1vcores</minResources>
      <minSharePreemptionTimeout>1000</minSharePreemptionTimeout>
      <schedulingPolicy>fair</schedulingPolicy>
      <weight>3</weight>
    </queue>
  </queue>
  <queuePlacementPolicy>
    <rule create="false" name="specified"/>
    <rule create="true" name="default"/>
  </queuePlacementPolicy>
</allocations>

3、重启 yarn
4、查看 http://linux132:8088/cluster/scheduler

MapReduce分布式计算

在这里插入图片描述

MapReduce的五大阶段

Input、Map、Shuffle、Reduce、Output
(也可以只有Input、Map、Output,即不需要Shuffle的分组和排序,也就没有了reduce)

在这里插入图片描述

Input:

1)默认从HDFS读取数据(数据源一般为HDFS和关系型数据库)
2)默认1个block封装成1个分片split,每个split启动一个map task进程
自定义split大小:
FileInputFormat.setMinInputSplitSize(129);//split大于block大小
FileInputFormat.setMaxInputSplitSize(127);//split小于block大小
3)split分片中的每行数据转换成键值对<key,value>的形式
key是行的偏移量(每个字符有对应的下标,下标从0开始,对应第一个分片的第一行第一个字符)
value是这一行的内容
hdfs在切块时,有可能将一行数据切分到不同的块中。偏移量不为0的切片在处理数据时,会从第2行开始读取数据,第1行的数据会存放在一个Text()匿名对象中,交给上一个map任务处理

Map:

1)根据input阶段的分片split,启动对应的Map task
2)Map task会实例化Mapper类的对象,每组kv调用一次map方法,对value进行处理,形成新的kv
3)新的kv保存在上下文对象context中

Shuffle:

MapReduce的核心: 分组+排序(难点在于如何设计key和value)

过程: 将map阶段的数据从内存写到磁盘,reduce阶段再从磁盘读到内存

如此设计的原因:
-》如果reduce阶段直接从map结果的内存中拿数据,内存占用过大;
-》map阶段和reduce阶段会跨节点传输数据,例如node1的map结果被node2的reduce处理,数据易丢

解决方案: map阶段各节点的结果会先落地到硬盘上,reduce阶段从硬盘上取数据

特点: 能实现大数据的解决方案,但耗时久。
(spark在工作中应尽量避免shuffle过程,mapreduce的shuffle是无法跳过的,除非没有reduce阶段)

shuffle的功能:
      分组:按key分组,相同的key对应的value放在同一个迭代器
      排序:对key进行排序
      分区:决定了map产生的每条数据,由哪个reduce进行处理
            默认分区方式:key的hash值对reduce的个数进行取余
                  例如reduce的个数是2个,
                  某个key对应的hash值是33,则33%2=1
                  某个key对应的hash值是34,则34%2=0
                  某个key对应的hash值是35,则35%2=1
                  …
                  结果为1的分配到同一个分区,结果为0的分配到同一个分区

底层逻辑: 分为map阶段的shuffle和reduce阶段的shuffle

map阶段的shuffle:

      -》spill溢写:
            1、map输出的数据逐条进入一个环形缓冲区,默认内存100M
            2、数据在缓冲区中分区和排序
                  分区:对key进行hash值取余,打上分区号的标记。
                  排序:相同分区的数据放在一起,且内部有序
在这里插入图片描述

            3、当缓冲区的存储达到80%,将这80%的数据写入磁盘生成一个小文件,同时另外20%继续处理数据,并行以提高效率。直到map的所有数据都输出成小文件(如果溢写得到的小文件个数>=3,则默认会触发一次combine)

      -》merge合并:
            合并:将所有小文件合并成一个大文件
            分区:根据分区标记,把相同分区的数据放到一起
            排序:内部依然有序
            每个map任务都生成了自己的大文件之后,APP master会通知reduce节点取数据
在这里插入图片描述

reduce阶段的shuffle:

      -》merge合并:
            每个reduce会到所有map task节点的磁盘文件上,根据分区标记取属于自己的数据并排序

      -》分组:
            将相同key的value放到迭代器中,作为reduce的输入数据

Reduce:

默认启动一个Reduce task,调用Reducer类的reduce方法将聚合结果放到context上下文对象中

Output:

接收reduce阶段的结果,默认保存到HDFS上

MapReduce编程

序列化:在网络传输或者持久化对象到文件时,需要把对象转为二进制结构
反序列化:把二进制结构转为对象

hadoop的序列化类型:
在这里插入图片描述

WordCount 单词计数案例

1、maven工程导入hadoop依赖、log4j、junit、打包插件

    <!--maven打包插件 -->
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

2、创建package:com.lagou.mr.wc
3、Mapper类

package com.lagou.mr.wc;

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;


public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {

    private Text word = new Text();
    private IntWritable one = new IntWritable(1);

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] words = line.split(" ");
        for(String w:words){
            word.set(w);
            context.write(word,one);
        }
    }
}

4、Reducer类

package com.lagou.mr.wc;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {

    private IntWritable total = new IntWritable();

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum=0;
        for (IntWritable v:values){
            sum += v.get();
        }
        total.set(sum);
        context.write(key,total);
    }
}

5、Driver类

package com.lagou.mr.wc;

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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class WordCountDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {

        //构建hadoop配置文件对象
        Configuration conf = new Configuration();

        //构建Job
        Job job = Job.getInstance(conf,"wordcount");
        job.setJarByClass(WordCountDriver.class);

        //Mapper
        job.setMapperClass(WordCountMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        //Reducer
        job.setReducerClass(WordCountReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        //输入文件
        FileInputFormat.setInputPaths(job,new Path(args[0]));

        //输出文件
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        //退出程序
        boolean flag = job.waitForCompletion(true);
        System.exit(flag?0:1);
    }
}

6、idea本地运行测试
-》先运行一次等报错
-》在 Edit Configurations 中填入参数 e:/test/aaaa.txt e:/test/bbbb ,再次运行

报错:

Exception in thread "main" java.lang.UnsatisfiedLinkError: org.apache.hadoop.io.nativeio.NativeIO$Windows.access0(Ljava/lang/String;I)Z

windows需要解压hadoop,并且要拷贝hadoop.dll和winutils.exe到bin目录下
在这里插入图片描述
7、yarn集群运行wordcount程序

maven将wordcount程序打包并上传到linux
准备测试数据上传到hdfs
	例如aaaa.txt,内容:
	hadoop hive spark
	hbase hadoop
	kafka flume hive spark
运行:
	yarn jar wc.jar com.lagou.mr.wc.WordCountDriver /test/aaaa.txt /test/output

自定义Writable类

案例需求:统计每台智能音箱设备内容播放时长
数据文件:speak.data
先观察数据结构

日志id	设备id	appkey硬件厂商		网络ip		自有内容时长		第三方内容时长		网络状态码
01		a00df6s		kar				120.196.100.99		384			33					200
11		0sfs01		kar				120.196.100.99		198			86					200
21		adfd00fd5	pandora			120.196.100.99		513			261					200

要求输出结果

设备id	自有内容时长		第三方内容时长		总时长
a00df6s		384				33				417

代码实现:
1、自定义一个实现了 Writable 接口的对象

package com.lagou.mr.speak;

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class SpeakBean implements Writable {
    //1、定义属性
    private String deviceId;
    private Long selfDuration;
    private Long thirdDuration;
    private Long sumDuration;

    //2、无参构造
    public SpeakBean() {}

    //3、有参构造
    public SpeakBean(String deviceId, Long selfDuration, Long thirdDuration) {
        this.deviceId = deviceId;
        this.selfDuration = selfDuration;
        this.thirdDuration = thirdDuration;
        this.sumDuration = selfDuration + thirdDuration;
    }

    //4、getter和setter方法
    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }

    public Long getSelfDuration() {
        return selfDuration;
    }

    public void setSelfDuration(Long selfDuration) {
        this.selfDuration = selfDuration;
    }

    public Long getThirdDuration() {
        return thirdDuration;
    }

    public void setThirdDuration(Long thirdDuration) {
        this.thirdDuration = thirdDuration;
    }

    public Long getSumDuration() {
        return sumDuration;
    }

    //5、重写toString方法
    @Override
    public String toString() {
        return "deviceId=" + deviceId + "\tselfDuration=" + selfDuration + "\tthirdDuration=" + thirdDuration + "\tsumDuration=" + sumDuration;
    }

    //6、序列化,所有的属性都需要序列化
    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(deviceId);
        out.writeLong(selfDuration);
        out.writeLong(thirdDuration);
        out.writeLong(sumDuration);
    }

    //7、反序列化,顺序要和序列化时的保持一致
    @Override
    public void readFields(DataInput in) throws IOException {
        this.deviceId=in.readUTF();
        this.selfDuration=in.readLong();
        this.thirdDuration=in.readLong();
        this.sumDuration=in.readLong();
    }

}

2、Mapper类

package com.lagou.mr.speak;

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

import java.io.IOException;

public class SpeakMapper extends Mapper<LongWritable, Text,Text,SpeakBean> {

    private Text device_id = new Text();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] params = line.split("\t");
        String deviceId = params[1];
        Long selfDuration = Long.parseLong(params[4]);
        Long thirdDuration = Long.parseLong(params[5]);
        SpeakBean speak = new SpeakBean(deviceId,selfDuration,thirdDuration);

        device_id.set(deviceId);
        context.write(device_id,speak);
    }
}

3、Reducer类

package com.lagou.mr.speak;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class SpeakReducer extends Reducer<Text,SpeakBean, Text,SpeakBean> {

    private Long self_duration = 0L;
    private Long third_duration = 0L;

    @Override
    protected void reduce(Text key, Iterable<SpeakBean> values, Context context) throws IOException, InterruptedException {

        for(SpeakBean v:values){
            self_duration += v.getSelfDuration();
            third_duration += v.getThirdDuration();
        }

        SpeakBean speak = new SpeakBean(key.toString(),self_duration,third_duration);
        context.write(key,speak);
    }
}

4、Driver类

package com.lagou.mr.speak;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class SpeakDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf,"SpeakDuration");
        job.setJarByClass(SpeakDriver.class);

        job.setMapperClass(SpeakMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(SpeakBean.class);

        job.setReducerClass(SpeakReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(SpeakBean.class);

        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        boolean flag = job.waitForCompletion(true);
        System.exit(flag?0:1);
    }
}

自定义分区器Partitioner

案例需求:按照不同的appkey进行分区
数据文件:speak.data
先观察数据结构,appkey = kar 放在同一个分区, appkey = pandora 放在同一个分区 , 其他厂商的放在同一个分区结果一共3个分区文件

日志id	设备id	appkey硬件厂商		网络ip		自有内容时长		第三方内容时长		网络状态码
01		a00df6s		kar				120.196.100.99		384			33					200
11		0sfs01		kar				120.196.100.99		198			86					200
21		adfd00fd5	pandora			120.196.100.99		513			261					200

代码实现:
1、先将数据封装成 Writable 类型的对象

package com.lagou.mr.cp;

import org.apache.hadoop.io.Writable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class CustomBean implements Writable {
    private String logId;
    private String deviceId;
    private String appkey;
    private String ip;
    private Long selfDuration;
    private Long thirdDuration;
    private String status;

    public CustomBean() {}
    public CustomBean(String logId, String deviceId, String appkey, String ip, Long selfDuration, Long thirdDuration, String status) {
        this.logId = logId;
        this.deviceId = deviceId;
        this.appkey = appkey;
        this.ip = ip;
        this.selfDuration = selfDuration;
        this.thirdDuration = thirdDuration;
        this.status = status;
    }

    public String getLogId() {
        return logId;
    }

    public void setLogId(String logId) {
        this.logId = logId;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }

    public String getAppkey() {
        return appkey;
    }

    public void setAppkey(String appkey) {
        this.appkey = appkey;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public Long getSelfDuration() {
        return selfDuration;
    }

    public void setSelfDuration(Long selfDuration) {
        this.selfDuration = selfDuration;
    }

    public Long getThirdDuration() {
        return thirdDuration;
    }

    public void setThirdDuration(Long thirdDuration) {
        this.thirdDuration = thirdDuration;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(logId);
        out.writeUTF(deviceId);
        out.writeUTF(appkey);
        out.writeUTF(ip);
        out.writeLong(selfDuration);
        out.writeLong(thirdDuration);
        out.writeUTF(status);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.logId=in.readUTF();
        this.deviceId=in.readUTF();
        this.appkey=in.readUTF();
        this.ip=in.readUTF();
        this.selfDuration=in.readLong();
        this.thirdDuration=in.readLong();
        this.status=in.readUTF();
    }
}

2、Mapper类

package com.lagou.mr.cp;

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

import java.io.IOException;

public class CustomMapper extends Mapper<LongWritable, Text,Text,CustomBean> {

    private CustomBean custom = new CustomBean();
    private Text appkey = new Text();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] params = line.split("\t");
        custom.setLogId(params[0]);
        custom.setDeviceId(params[1]);
        custom.setAppkey(params[2]);
        custom.setIp(params[3]);
        custom.setSelfDuration(Long.parseLong(params[4]));
        custom.setThirdDuration(Long.parseLong(params[5]));
        custom.setStatus(params[6]);

        appkey.set(custom.getAppkey());
        context.write(appkey,custom);

    }
}

3、自定义分区规则
继承Partitioner 类,泛型是Mapper的输出 key,value 类型

package com.lagou.mr.cp;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;

public class CustomPartitioner extends Partitioner<Text,CustomBean> {
    @Override
    public int getPartition(Text text, CustomBean customBean, int i) {
        int partition = 0;
        if (text.toString().equals("kar")){
            partition = 0;
        }else if (text.toString().equals("pandora")){
            partition = 1;
        }else{
            partition = 2;
        }
        return partition;
    }
}

4、Reducer类

package com.lagou.mr.cp;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class CustomReducer extends Reducer<Text,CustomBean,Text,CustomBean> {
    @Override
    protected void reduce(Text key, Iterable<CustomBean> values, Context context) throws IOException, InterruptedException {
        for (CustomBean value:values){
            context.write(key,value);
        }
    }
}

5、Driver类,使用自定义分区器

package com.lagou.mr.cp;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class CustomDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf,"cp");
        job.setJarByClass(CustomDriver.class);

        job.setMapperClass(CustomMapper.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(CustomBean.class);

        job.setReducerClass(CustomReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(CustomBean.class);

        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        //使用自定义分区器
        job.setPartitionerClass(CustomPartitioner.class);
        job.setNumReduceTasks(3);

        boolean flag = job.waitForCompletion(true);
        System.exit(flag?0:1);
    }
}

排序

局部排序:MR默认的排序方式
二次排序:重写排序方法时,条件1相等的情况下再比较条件2
全排序:reduce个数为1

全排序案例:
需求描述:基于统计的播放时长,进行全局排序
数据:duration.data

00fdaf3	00fdaf3	33180	33420	66600
00wersa4	00wersa4	63869	68611	132480
0a0fe2	0a0fe2	106954	112865	219819
0ad0s7	0ad0s7	138656	142048	280704
0sfs01	0sfs01	170539	171149	341688
a00df6s	a00df6s	203778	208031	411809
adfd00fd5	adfd00fd5	234505	239522	474027

需求分析:将整行数据封装成一个对象作为key,因为 MR 的shuffle 阶段默认会进行排序,可以利用该机制,实现WritableComparable接口

1、封装一个bean类,实现 WritableComparable ,实现排序的方法是compareTo

package com.lagou.mr.sort;

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class SpeakBean implements WritableComparable<SpeakBean> {

    private String deviceId;
    private Long selfDuration;
    private Long thridDuration;
    private Long sumDuration;

    public SpeakBean() {}

    public SpeakBean(String deviceId, Long selfDuration, Long thridDuration,Long sumDuration) {
        this.deviceId = deviceId;
        this.selfDuration = selfDuration;
        this.thridDuration = thridDuration;
        this.sumDuration = sumDuration;
    }

    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }

    public Long getSelfDuration() {
        return selfDuration;
    }

    public void setSelfDuration(Long selfDuration) {
        this.selfDuration = selfDuration;
    }

    public Long getThridDuration() {
        return thridDuration;
    }

    public void setThridDuration(Long thridDuration) {
        this.thridDuration = thridDuration;
    }

    public Long getSumDuration() {
        return sumDuration;
    }

    public void setSumDuration(Long sumDuration) {
        this.sumDuration = sumDuration;
    }

    @Override
    public String toString() {
        return deviceId + "\t" + selfDuration + "\t" + thridDuration + "\t" + sumDuration;
    }

    //排序的关键方法
    @Override
    public int compareTo(SpeakBean o) {
        int flag = 0;
        if(this.sumDuration>o.getSumDuration()){
            flag = -1;  // 倒序
        }else if(this.sumDuration<o.getSumDuration()){
            flag = 1;
        }else{
            flag = 0;
        }
        return flag;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(deviceId);
        out.writeLong(selfDuration);
        out.writeLong(thridDuration);
        out.writeLong(sumDuration);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.deviceId=in.readUTF();
        this.selfDuration=in.readLong();
        this.thridDuration=in.readLong();
        this.sumDuration=in.readLong();
    }
}

2、Mapper类

package com.lagou.mr.sort;

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;

public class SortMapper extends Mapper<LongWritable, Text,SpeakBean, NullWritable> {

    private SpeakBean bean = new SpeakBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] params = line.split("\t");

        bean.setDeviceId(params[1]);
        bean.setSelfDuration(Long.parseLong(params[2]));
        bean.setThridDuration(Long.parseLong(params[3]));
        bean.setSumDuration(Long.parseLong(params[4]));

        context.write(bean,NullWritable.get());
    }
}

3、Reducer类

package com.lagou.mr.sort;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class SortReducer extends Reducer<SpeakBean, NullWritable,SpeakBean,NullWritable> {

    @Override
    protected void reduce(SpeakBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {

        //相同key的value会放在同一个迭代器中,所以在迭代器中每有一个元素,就要输出一次
        for (NullWritable value : values) {
            context.write(key,value);
        }
    }
}

4、Driver类

package com.lagou.mr.sort;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class SortDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf,"sort");
        job.setJarByClass(SortDriver.class);

        job.setMapperClass(SortMapper.class);
        job.setReducerClass(SortReducer.class);
        job.setMapOutputKeyClass(SpeakBean.class);
        job.setMapOutputValueClass(NullWritable.class);
        job.setOutputKeyClass(SpeakBean.class);
        job.setOutputValueClass(NullWritable.class);

        // 全局排序的reduce必须只能设置为1个
        job.setNumReduceTasks(1);

        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        boolean flag = job.waitForCompletion(true);
        System.exit(flag?0:1);
    }
}

分组排序/辅助排序案例:
需求描述:求出相同订单中,成交金额最大的一笔交易
数据:groupingComparator.txt

0000001	Pdt_01	222.8
0000002	Pdt_05	722.4
0000001	Pdt_02	33.8
0000003	Pdt_06	232.8
0000003	Pdt_02	33.8
0000002	Pdt_03	522.8
0000002	Pdt_04	122.4

需求分析:
-》key可以设计为 订单号+商品号+成交金额
-》按照订单号进行排序,订单号相等的情况下,再按照金额进行降序
-》按照订单号进行分区,由 WritableComparator 来实现当订单号相等时则认为 key 相等 ,相同的 key 由同一个 reduce 任务处理
-》分组阶段把相同 key 的 value 放到同一个迭代器中,MR框架默认取的 key 是第一个 key

代码实现:
1、Bean类实现根据订单号和金额的排序

package com.lagou.mr.gc;

import org.apache.hadoop.io.WritableComparable;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class OrderBean implements WritableComparable<OrderBean> {

    private String orderId;
    private String goodsId;
    private Double price;

    public OrderBean() {}

    public OrderBean(String orderId, String goodsId, Double price) {
        this.orderId = orderId;
        this.goodsId = goodsId;
        this.price = price;
    }

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public String getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(String goodsId) {
        this.goodsId = goodsId;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return orderId+"\t"+goodsId+"\t"+price;
    }

    @Override
    public int compareTo(OrderBean o) {
        int flag=0;
        flag = this.orderId.compareTo(o.getOrderId());
        if (flag == 0){
            flag = o.getPrice().compareTo(this.price);
        }
        return flag;
    }

    @Override
    public void write(DataOutput out) throws IOException {
        out.writeUTF(orderId);
        out.writeUTF(goodsId);
        out.writeDouble(price);
    }

    @Override
    public void readFields(DataInput in) throws IOException {
        this.orderId=in.readUTF();
        this.goodsId=in.readUTF();
        this.price=in.readDouble();
    }
}

2、Mapper类

package com.lagou.mr.gc;

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;

public class GroupMapper extends Mapper<LongWritable, Text,OrderBean, NullWritable> {

    private OrderBean bean = new OrderBean();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString();
        String[] fields = line.split("\t");
        bean.setOrderId(fields[0]);
        bean.setGoodsId(fields[1]);
        bean.setPrice(Double.parseDouble(fields[2]));

        context.write(bean,NullWritable.get());
    }
}

3、自定义分区器,订单号相同的会分到同一个 reduce 任务

package com.lagou.mr.gc;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Partitioner;

public class GroupPartitioner extends Partitioner<OrderBean, NullWritable> {
    @Override
    public int getPartition(OrderBean o, NullWritable nullWritable, int i) {
        return (o.getOrderId().hashCode()&Integer.MAX_VALUE) % i;
    }
}

4、相同 key 对应的 value 会进行分组,放入同一个迭代器中
必须在构造器中反射 compare 方法要使用到的 bean 类

package com.lagou.mr.gc;

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;

public class GroupComparator extends WritableComparator {

    //必须要接受OrderBean对象,父类需要初始化一些资源,否则报错
    public GroupComparator() {
        super(OrderBean.class,true);
    }

    //判断相同的key会放在同一个迭代器中
    @Override
    public int compare(WritableComparable a, WritableComparable b) {

        // 需要转型,只比较orderId是否相等
        OrderBean o1 = (OrderBean) a;
        OrderBean o2 = (OrderBean) b;
        return o1.getOrderId().compareTo(o2.getOrderId());
    }
}

5、Reducer类

package com.lagou.mr.gc;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class GroupReducer extends Reducer<OrderBean, NullWritable,OrderBean,NullWritable> {
    @Override
    protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        context.write(key,NullWritable.get());
    }
}

6、Driver类,可设置多个 reduce 数量用来验证结果

package com.lagou.mr.gc;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class GroupDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf,"gc");
        job.setJarByClass(GroupDriver.class);

        job.setMapperClass(GroupMapper.class);
        job.setReducerClass(GroupReducer.class);
        job.setMapOutputKeyClass(OrderBean.class);
        job.setMapOutputValueClass(NullWritable.class);
        job.setOutputKeyClass(OrderBean.class);
        job.setOutputValueClass(NullWritable.class);

        job.setPartitionerClass(GroupPartitioner.class);
        job.setGroupingComparatorClass(GroupComparator.class);
        job.setNumReduceTasks(2);

        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        boolean flag = job.waitForCompletion(true);
        System.exit(flag?0:1);
    }
}

MapReduce的读取和输出数据

InputFormat

常见子类
在这里插入图片描述
CombineTextInputFormat案例:
需求描述:用 WordCount 程序分析多个小文件
数据准备:1.txt 、2.txt 、3.txt 、4.txt
使用 CombineTextInputFormat 关键代码

        //合并小文件CombineTextInputFormat
        job.setInputFormatClass(CombineTextInputFormat.class);
        //虚拟存储切片的最大值设置为4m
        CombineTextInputFormat.setMaxInputSplitSize(job,4194304);

分块规则:

假设 job.setMaxInputSplitSize 设置为 4M
1.txt(2M)		2M<4M			划分为1个2M大小的块
2.txt(7M)		4M<7M<2*4M		将7M文件平均拆分成两个3.5M的块
3.txt(0.3M)	0.3M<4M			划分为1个0.3M大小的块
4.txt(8.2M)	8.2M>2*4M		先切出一个4M大小的块,剩余4.2M
				4M<4.2M<2*4M	将4.2M大小的块平均拆分成两个2.1M的块
此时块信息为:4M,3.5M,3.5M,0.3M,4M,2.1M,2.1M 共7个虚拟块

切片规则:

依次合并每个小块,直到块的大小比 4M 大,就形成一个分片
分片1:( 4M + 3.5M ) > 4M
分片2:( 3.5M + 0.3M + 4M ) > 4M
分片3:( 2.1M +2.1M ) > 4M

自定义InputFormat案例
1、继承 FileInputFormat
2、重写 isSplitable() 设置为不可切分;重写 createRecordReader() ,创建 RecorderReader 对象
3、改变默认读取数据方式,实现一次读取一个完整文件(需要不可切分)作为 kv 输出
4、Driver 指定使用自定义的 InputFormat

需求描述:将多个小文件合并成一个SequenceFile文件

SequenceFile是hadoop用于存储二进制形式的文件格式,数据结构为 key,value
	key 保存的是文件路径+文件名
	value 保存的是文件内容

代码实现:
1、继承 FileInputFormat

package com.lagou.mr.seq;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import java.io.IOException;

//指定InputFormat的输出数据类型,即Mapper阶段的输入数据类型
//SequenceFile的key=文件路径+文件名;value=文件内容
public class SequenceInputFormat extends FileInputFormat<Text, BytesWritable> {

    //RecordReader是用来读取数据的对象
    @Override
    public RecordReader<Text, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {

        //创建recordReader对象,并且调用其初始化方法
        MyRecordReader recordReader = new MyRecordReader();
        recordReader.initialize(split,context);

        return recordReader;
    }

    //重写是否可切分,一整个文件作为一个split,所以不需要进行切分
    @Override
    protected boolean isSplitable(JobContext context, Path filename) {
        return false;
    }
}

2、定义 RecordReader

package com.lagou.mr.seq;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;

import java.io.IOException;

//负责读取数据,一次读取文件的所有内容,封装成kv
public class MyRecordReader extends RecordReader<Text, BytesWritable> {

    //需要两个变量:split分片对象和hadoop配置文件对象
    private FileSplit split;
    private Configuration conf;
    //还需要定义key和value
    private Text key = new Text();
    private BytesWritable value = new BytesWritable();

    //初始化对象
    @Override
    public void initialize(InputSplit inputSplit, TaskAttemptContext context) throws IOException, InterruptedException {
        this.split = (FileSplit) inputSplit;
        this.conf = context.getConfiguration();
    }

    //用来读取数据的方法,如果读取成功,返回true;如果返回false,说明已读完所有文件
    boolean flag = true;
    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {

        if (flag) {
            //获取切片的路径信息
            Path path = split.getPath();
            //根据hdfs路径获取文件系统对象
            FileSystem fs = path.getFileSystem(conf);
            //把数据放到输入流中,有流才能传输数据
            FSDataInputStream fis = fs.open(path);

            //准备一个数组,用于存放读取到的数据,大小为一个完整的文件,split.getLength()是long类型,需要强转
            byte[] content = new byte[(int) split.getLength()];
            //开始读取文件内容,参数:输入流对象、buffer、读取起使位置、读取结束位置
            IOUtils.readFully(fis, content, 0, content.length);

            //封装kv
            key.set(path.toString());
            value.set(content, 0, content.length);

            //关闭流
            IOUtils.closeStream(fis);

            //读到了数据,返回true,一次性读完整个文件不需要再次读取,所以flag设为false
            flag = false;
            return true;
        }
        return false;  //进行下一个文件的读取过程
    }

    //获取key
    @Override
    public Text getCurrentKey() throws IOException, InterruptedException {
        return key;
    }

    //获取value
    @Override
    public BytesWritable getCurrentValue() throws IOException, InterruptedException {
        return value;
    }

    @Override
    public float getProgress() throws IOException, InterruptedException {
        return 0;
    }

    @Override
    public void close() throws IOException {

    }
}

3、Mapper类

package com.lagou.mr.seq;

import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class SequenceMapper extends Mapper<Text, BytesWritable,Text,BytesWritable> {
    @Override
    protected void map(Text key, BytesWritable value, Context context) throws IOException, InterruptedException {
        context.write(key,value);
    }
}

4、Reducer类

package com.lagou.mr.seq;

import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class SequenceReducer extends Reducer<Text, BytesWritable,Text,BytesWritable> {
    @Override
    protected void reduce(Text key, Iterable<BytesWritable> values, Context context) throws IOException, InterruptedException {
        context.write(key,values.iterator().next());
    }
}

5、Driver类

package com.lagou.mr.seq;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class SequenceDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf,"seq");
        job.setJarByClass(SequenceDriver.class);

        job.setMapperClass(SequenceMapper.class);
        job.setReducerClass(SequenceReducer.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(BytesWritable.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(BytesWritable.class);

        //使用自定义的文件输入流
        job.setInputFormatClass(SequenceInputFormat.class);

        FileInputFormat.setInputPaths(job,new Path(args[0]));
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        boolean flag = job.waitForCompletion(true);
        System.exit(flag?0:1);
    }
}

SequenceFile 类型的结果展示:
在这里插入图片描述

OutputFormat

在这里插入图片描述
自定义OutputFormat案例:
需求描述:根据不同的类型,将数据输出到两个目录;将包含 “lagou” 的网址和不包含的输出到两个目录下
数据准备:click_log.data

http://www.baidu.com
http://www.google.com
http://cn.bing.com
http://www.lagou.com
http://www.sohu.com
http://www.sina.com
http://www.taobao.com
http://www.alibaba.com
http://www.tmall.com

代码实现:
1、Mapper类

package com.lagou.mr.output;

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;

public class OutputMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        context.write(value,NullWritable.get());
    }
}

2、Reducer类

package com.lagou.mr.output;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class OutputReducer extends Reducer<Text, NullWritable,Text,NullWritable> {
    @Override
    protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
        context.write(key,NullWritable.get());
    }
}

3、自定义 OutputFormat
这里的 getRecordWriter 方法需要获得自定义对象 RecordWriter 的返回值

package com.lagou.mr.output;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class CustomOutputFormat extends FileOutputFormat<Text, NullWritable> {

    //写出数据需要一个RecordWriter对象
    @Override
    public RecordWriter<Text, NullWritable> getRecordWriter(TaskAttemptContext context) throws IOException, InterruptedException {
        Configuration conf = new Configuration();
        FileSystem fs = FileSystem.get(conf);

        //创建输出流对象
        FSDataOutputStream lagouOut = fs.create(new Path("e:/test/lagou.log"));
        FSDataOutputStream otherOut = fs.create(new Path("e:/test/other.log"));

        CustomWriter customWriter = new CustomWriter(lagouOut,otherOut);
        return customWriter;  //记得返回
    }
}

4、自定义 RecordWriter

package com.lagou.mr.output;

import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;

import java.io.IOException;

public class CustomWriter extends RecordWriter<Text, NullWritable> {

    //因为要往两个地方进行输出,需要定义两个输出流对象
    FSDataOutputStream lagouOut;
    FSDataOutputStream otherOut;

    //输出流对象的创建可以由外部传入构造
    public CustomWriter(FSDataOutputStream lagouOut, FSDataOutputStream otherOut) {
        this.lagouOut = lagouOut;
        this.otherOut = otherOut;
    }

    //写出数据的处理逻辑,需要输出流对象,每行文本执行一次
    @Override
    public void write(Text key, NullWritable value) throws IOException, InterruptedException {
        String line = key.toString();
        if(line.contains("lagou")){
            System.out.println("lagou-------------");
            System.out.println(line);
            lagouOut.write(line.getBytes());
            lagouOut.write("\r\n".getBytes()); //写完内容要换行
        }else {
            System.out.println("other-------------");
            System.out.println(line);
            otherOut.write(line.getBytes());
            otherOut.write("\r\n".getBytes());
        }
    }

    @Override
    public void close(TaskAttemptContext context) throws IOException, InterruptedException {
        IOUtils.closeStream(lagouOut);
        IOUtils.closeStream(otherOut);
    }
}

5、Driver类

package com.lagou.mr.output;

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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

import java.io.IOException;

public class OutputDriver {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf,"output");
        job.setJarByClass(OutputDriver.class);

        job.setMapperClass(OutputMapper.class);
        job.setReducerClass(OutputReducer.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(NullWritable.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(NullWritable.class);

        //指定使用自定义的OutputFormat
        job.setOutputFormatClass(CustomOutputFormat.class);

        FileInputFormat.setInputPaths(job,new Path(args[0]));

        //这里给的输出路径是用来保存SUCCESS文件的,数据的存放位置在自定义OutputFormat里已经写死了
        FileOutputFormat.setOutputPath(job,new Path(args[1]));

        boolean flag = job.waitForCompletion(true);
        System.exit(flag?0:1);
    }
}

常见报错:

hadoop日志路径:hadoop/logs/*
1、启动hadoop集群时发现只有NameNode,没有DataNode
报错内容:NameNode ClusterID=aaa
DataNode ClusterID=aab
报错原因:多次格式化
解决1:清空所有节点的datas目录,再重新格式化
解决2:修改NameNode机器的ClusterID与DataNode的一致
ClusterID位置:datas/dfs/name/current/VERSION

2、启动hadoop后发现DataNode的个数经常发生变化
异常原因:DataNode唯一ID重复了
异常解决:datas/dfs/data/current/VERSION修改重复了的datanodeUuid

3、启动进程后,NameNode或DataNode进程自动消失
异常内容:bindexception/unknownHostException
异常原因:主机名解析异常
解决1:检查ip地址和主机名是否正确
解决2:查看hosts文件的localhost是否被删

4、关闭 hdfs 时无法正常关闭

[root@linux131 sbin]# stop-dfs.sh
Stopping namenodes on [linux131]
linux131: no namenode to stop
linux132: no datanode to stop
linux131: no datanode to stop
linux134: no datanode to stop
Stopping secondary namenodes [linux134]
linux134: no secondarynamenode to stop

解决:https://blog.csdn.net/GYQJN/article/details/50805472

遇到过的面试题

1、数据倾斜
reduce是按照key的hash值取余进行分配的
当某个reduce分配到的数据量过大时,即发生数据倾斜
整个进度卡在99%,是因为有reduce任务迟迟无法结束,可能是数据倾斜

解决:

数据分布不均匀导致的数据倾斜
1)如果数据是不需要的,在map端直接过滤掉
2)如果数据是需要的,在key前拼接一个随机数,reduce阶段再去掉随机数

分区规则不合理导致的数据倾斜
1)开启combiner,让数据在map端先聚合一次,减少reduce的输入量
2)重写分区规则,自定义一个类继承Partitioner

2、分组发生在什么阶段?
reduce端的shuffle

3、搭建过的hadoop版本
1)原生apache版本
hadoop2.7.3 2017/10
2)CDH版本
hadoop2.6 cdh5.7 2017年
-》安装ClouderaManager和元数据库
-》分发到其他所有机器
-》启动服务
-》选择要安装的程序
-》选择节点的分布

4、hadoop集群有哪些进程
NameNode DataNode SeconderyNameNode
ResourceManager NodeManager
JobHistoryServer
JournalNode zkfc Zookeeper

5、解决需求的思路:
1)了解需求
2)观察分析数据的结构,判定脏数据
3)分析需求,提取关键词,确立key和value
相同的key为一组
4)开发顺序时是按正序流程走的,分析需求时是逆推的

6、集群迁移,旧集群数据迁到新集群
hadoop distcp hdfs://nn1:8020/source(旧集群) hdfs://nn2:8020/destination(新集群)
分布式拷贝distributed copy

7、hadoop3.x 新版本特性
不再支持 jdk1.7 及以下版本
MapReduce 将基于 内存 + IO + 磁盘

8、Shuffle端的优化
分为线程优化和数据优化

1)线程优化:combiner
在这里插入图片描述
数据相同的情况下,reduce的输入个数越少,效率越高
combiner其实就是在map阶段提前执行了一次reduce操作
每个map task在任务结束后,调用一次reduce的聚合,减少了reduce的负载量
思路是将reduce端处理数据的个数平摊到map阶段
正常工作中,reduce的个数通常只有一个,map的个数是非常多的

如何开启combiner?
job.setCombinerClass(WordCountReducer.class)

combiner的应用场景
符合结合律或分配律的情况,例如累加求和
也可以通过测试开启/关闭combiner验证结果是否相同来确定是否适用于场景

2)数据压缩:compress
用空间换时间,适用于超大文件的数据处理

不做数据压缩:
map --> 用时100s将10G数据写入磁盘 --> 用时50s读取到2个reduce中
使用数据压缩后:
map --> 用时50s将5G数据写入磁盘 --> 用时25s读取到2个reduce中

以上为理想状态,数据压缩也需要时间,这就涉及压缩比,

hadoop支持的压缩格式
在这里插入图片描述

hadoop查看可用的压缩类型

yum install -y openssl-devel
bin/hadoop checknative

MapReduce可以使用压缩的三个阶段:map的输入阶段、map的输出阶段、reduce的输出阶段

永久配置压缩类型:

vim mapred-default.xml 或 mapred-site.xml
添加:mapreduce.map.output.compress=true
	  mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.Lz4Codec

临时配置压缩类型,在java程序中设置:

Configuration conf = new Configuration();

//设置map阶段的压缩
conf.set("mapreduce.map.output.compress","true");
conf.set("mapreduce.map.output.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");

//设置reduce阶段的压缩
conf.set("mapreduce.output.fileoutputformat.compress","true");
conf.set("mapreduce.output.fileoutputformat.compress.type","RECORD");
conf.set("mapreduce.output.fileoutputformat.compress.codec","org.apache.hadoop.io.compress.SnappyCodec");

shuffle的瓶颈是IO(网络带宽、cpu、内存在现在都不是瓶颈,磁盘读写速度才是,压缩数据大小可提升速度)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值