文章目录
- 一、Hadoop 入门
- 1.1 基础架构
- 1.2 大数据生态圈
- 二、Hadoop 集群部署
- 2.1 准备
- 2.2 配置
- 2.3 启动
- 2.4 监控页面
- 三、HDFS
- 3.1 组成架构
- 3.2 HDFS Shell
- 3.3 HDFS 客户端
- 3.4 HDFS 数据流
- 3.4.1 写数据流程
- 3.4.2 读数据流程
- 3.5 NN 和 2NN
- 3.5.1 工作机制
- 3.5.2 集群安全模式
- 3.6 DN
- 3.6.1 工作机制
- 3.6.2 扩容
- 3.6.3 退役
- 四、MapReduce
- 4.1 概述
- 4.2 WordCount
- 4.3 序列化
- 4.3.1 概述
- 4.3.2 自定义序列化类
- 4.4 工作流程
- 4.4.1 Map 阶段
- 4.4.2 Shuffle 阶段
- 4.4.3 Reduce 阶段
- 4.5 客制化类
- 4.5.1 Map 阶段
- 4.5.2 Shuffle 阶段
- 4.5.2.1 自定义分区 `Partitioner`
- 4.5.2.2 自定义排序 `WritableComparable`
- 4.5.2.3 自定义局部合并 `Combiner`
- 4.5.2.4 自定义分组比较器 `GroupingComparator`
- 4.5.3 Reduce 阶段
- 五、Yarn 资源调度器
- 5.1 基本架构
- 5.2 工作机制
- 5.3 资源调度器
- 5.4 容量调度器多队列提交案例
- 5.5 任务的推测执行
- 六、Hadoop企业优化
- 6.1 MapReduce 优化方法
- 6.1.1 数据输入
- 6.1.2 Map阶段
- 6.1.3 Reduce阶段
- 6.1.4 IO传输
- 6.1.5 数据倾斜问题
- 6.1.6 常用的调优参数
- 6.2 HDFS 小文件优化方法
- 七、HA 高可用
- 7.1 HDFS HA
- 7.1.1 工作要点
- 7.1.2 自动故障转移工作机制
- 7.1.3 集群配置
- 7.1.4 模拟故障自动切换
- 7.2 YARN HA
- 7.3 HDFS Federation
- 7.3.1 NameNode架构的局限性
- 7.3.2 HDFS Federation 架构设计
一、Hadoop 入门
Hadoop是一个分布式系统基础架构,主要解决海量数据的存储及分析计算的问题。
1.1 基础架构
- Hadoop 组成
- 在Hadoop1.x时代,Hadoop中的MapReduce同时处理业务逻辑运算和资源的调度,耦合性较大。
- 在Hadoop2.x时代,增加了Yarn。Yarn只负责资源的调度,MapReduce只负责运算。
- HDFS 架构
- NameNode(nn):存储文件的元数据,如文件名,文件目录结构,文件属性(生成时间、副本数、文件权限),以及每个文件的块列表和块所在的DataNode等。
- DataNode(dn):在本地文件系统存储文件块数据,以及块数据的校验和。
- Secondary NameNode(2nn):每隔一段时间对NameNode元数据备份。
- YARN 架构
- ResourceManager(RM):处理客户端请求、监控NodeManager、启动或监控ApplicationMaster、资源的分配与调度
- NodeManager(NM):管理单个节点上的资源、处理来自ResourceManager的命令、处理来自ApplicationMaster的命令
- ApplicationMaster(AM):负责数据的切分、为应用程序申请资源并分配给内部的任务、任务的监控与容错
- Container:YARN中的资源抽象,它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等。
- MapReduce 架构
- MapReduce将计算过程分为两个阶段:Map和Reduce
- Map阶段并行处理输入数据
- Reduce阶段对Map结果进行汇总
1.2 大数据生态圈
- Sqoop:Sqoop是一款开源的工具,主要用于在Hadoop、Hive与传统的数据库(MySql)间进行数据的传递,可以将一个关系型数据库(例如 :MySQL,Oracle 等)中的数据导进到Hadoop的HDFS中,也可以将HDFS的数据导进到关系型数据库中。
- Flume:Flume是一个高可用的,高可靠的,分布式的海量日志采集、聚合和传输的系统,Flume支持在日志系统中定制各类数据发送方,用于收集数据;
- Kafka:Kafka是一种高吞吐量的分布式发布订阅消息系统;
- Storm:Storm用于“连续计算”,对数据流做连续查询,在计算时就将结果以流的形式输出给用户。
- Spark:Spark是当前最流行的开源大数据内存计算框架。可以基于Hadoop上存储的大数据进行计算。
- Flink:Flink是当前最流行的开源大数据内存计算框架。用于实时计算的场景较多。
- Oozie:Oozie是一个管理Hdoop作业(job)的工作流程调度管理系统。
- Hbase:HBase是一个分布式的、面向列的开源数据库。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。
- Hive:Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的SQL查询功能,可以将SQL语句转换为MapReduce任务进行运行。 其优点是学习成本低,可以通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。
- ZooKeeper:它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护、名字服务、分布式同步、组服务等。
2.1 准备
- 准备软件
- hadoop-3.1.4.tar.gz
- jdk-8u271-linux-x64.tar.gz
- 虚拟机规划
主机名 | IP | NN | DN | RM | NM |
bigdata01 | 192.168.1.101 | Y | Y | Y | |
bigdata02 | 192.168.1.102 | Y | Y | Y | |
bigdata03 | 192.168.1.103 | Y | Y | Y |
- 准备
hosts
# 设置主机名
hostnamectl set-hostname bigdata01
# 设置集群内DNS访问
cat << EOF >> /etc/hosts
192.168.1.101 bigdata01
192.168.1.102 bigdata02
192.168.1.103 bigdata03
EOF
- 关闭安全
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 关闭SELinux
setenforce 0
sed -i 's/enforcing$/disabled/' /etc/selinux/config
- 创建集群安装用户
# 添加用户
useradd omm
passwd omm
# 配置管理员权限
# 在文件 '/etc/sudoers' 行 'root ALL=(ALL) ALL' 下添加
omm ALL=(ALL) NOPASSWD: ALL
- 配置互信
以 omm 用户身份执行
#!/bin/bash
hosts="192.168.1.101 192.168.1.102 192.168.1.103"
passwd="abcd1234.."
yum install sshpass
user=`whoami`
echo n | ssh-keygen -t rsa -P "" -f ~/.ssh/id_rsa > /dev/null
for host in $hosts
do
echo "Spread SSH Pub Key to $host"
{
sshpass -p$passwd ssh-copy-id -i ~/.ssh/id_rsa.pub $user@$host -p 22 -o "StrictHostKeyChecking=no" &> /dev/null
}&
done
wait
- 准备快速分发文件脚本
将此脚本命名为
xsync
并移至$PATH
扫描路径下如/bin
#!/bin/bash
hosts="bigdata02 bigdata03"
stand=`pwd`
p_dir=`cd .. && pwd`
cd $stand
sudo yum install rsync
for host in $hosts
do
rsync -avz --delete $stand $host:$p_dir
done
- 软件包配置(omm 用户)
/opt
├── module
│ ├── hadoop -> hadoop-3.1.4/
│ ├── hadoop-3.1.4
│ ├── jdk -> jdk1.8.0_271/
│ └── jdk1.8.0_271
└── soft
├── hadoop-3.1.4.tar.gz
└── jdk-8u271-linux-x64.tar.gz
# 准备目录并上传软件包
mkdir /opt/module
mkdir /opt/soft
# 解压缩
tar -zxf /opt/soft/jdk-8u271-linux-x64.tar.gz -C /opt/module
tar -zxf /opt/soft/hadoop-3.1.4.tar.gz -C /opt/module
# 软链接
ln -s /opt/module/jdk1.8.0_271 /opt/module/jdk
ln -s /opt/module/hadoop-3.1.4 /opt/module/hadoop-3.1.4
# PATH 路径
cat << EOF >> /etc/profile
# Java
export JAVA_HOME=/opt/module/jdk
export PATH=$PATH:$JAVA_HOME/bin
# Hadoop
export HADOOP_HOME=/opt/module/hadoop
export PATH=$PATH:$HADOOP_HOME/bin
# Hadoop sbin
export PATH=$PATH:$HADOOP_HOME/sbin
EOF
# 验证
[omm@bigdata01 ~]$ java -version
java version "1.8.0_271"
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
[omm@bigdata01 ~]$ h
h2ph hadoop-daemon.sh hardlink hdfs.cmd help hostid httpfs.sh
hadoop hadoop-daemons.sh hash hdsploader hexdump hostname hwclock
hadoop.cmd halt hdfs head history hostnamectl
[omm@bigdata01 ~]$ hadoop version
Hadoop 3.1.4
Source code repository https://github.com/apache/hadoop.git -r 1e877761e8dadd71effef30e592368f7fe66a61b
Compiled by gabota on 2020-07-21T08:05Z
Compiled with protoc 2.5.0
From source with checksum 38405c63945c88fdf7a6fe391494799b
This command was run using /opt/module/hadoop-3.1.4/share/hadoop/common/hadoop-common-3.1.4.jar
[omm@bigdata01 ~]$
- 克隆虚拟机
克隆虚拟机 ==》 修改主机名 ==》 修改IP
2.2 配置
- 准备配置文件
Hadoop 配置文件路径:
/opt/module/hadoop/etc/hadoop
hadoop-env.sh
export JAVA_HOME=/opt/module/jdk
core-site.xml
<configuration>
<property>
<name>fs.defaultFS</name>
<!-- NN 安装节点 -->
<value>hdfs://bigdata01:8020</value>
</property>
<property>
<name>hadoop.data.dir</name>
<!-- 数据存放目录 -->
<value>/opt/module/hadoop/data</value>
</property>
<property>
<!-- 用户 -->
<name>hadoop.proxyuser.omm.hosts</name>
<value>*</value>
</property>
<property>
<!-- 组 -->
<name>hadoop.proxyuser.omm.groups</name>
<value>*</value>
</property>
</configuration>
hdfs-site.xml
<configuration>
<property>
<name>dfs.namenode.name.dir</name>
<value>file://${hadoop.data.dir}/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file://${hadoop.data.dir}/data</value>
</property>
<property>
<name>dfs.namenode.checkpoint.dir</name>
<value>file://${hadoop.data.dir}/namesecondary</value>
</property>
<property>
<name>dfs.client.datanode-restart.timeout</name>
<value>30</value>
</property>
<property>
<!-- 2NN 安装节点 -->
<name>dfs.namenode.secondary.http-address</name>
<value>bigdata03:9868</value>
</property>
</configuration>
mapred-site.xml
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<!-- 历史服务器端地址 -->
<property>
<name>mapreduce.jobhistory.address</name>
<value>bigdata01:10020</value>
</property>
<!-- 历史服务器web端地址 -->
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>bigdata01:19888</value>
</property>
</configuration>
yarn-site.xml
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<!-- RM 安装节点 -->
<name>yarn.resourcemanager.hostname</name>
<value>bigdata02</value>
</property>
<property>
<name>yarn.nodemanager.env-whitelist</name>
<value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value>
</property>
<!-- 开启日志聚合功能 -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<property>
<name>yarn.log.server.url</name>
<value>http://bigdata01:19888/jobhistory/logs</value>
</property>
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>
</configuration>
- 快速分发
[omm@bigdata01 hadoop]$ xsync
sending incremental file list
hadoop/core-site.xml
hadoop/hadoop-env.sh
hadoop/hdfs-site.xml
hadoop/mapred-site.xml
hadoop/yarn-site.xml
sent 1,278 bytes received 305 bytes 3,166.00 bytes/sec
total size is 114,023 speedup is 72.03
sending incremental file list
hadoop/core-site.xml
hadoop/hadoop-env.sh
hadoop/hdfs-site.xml
hadoop/mapred-site.xml
hadoop/yarn-site.xml
sent 1,278 bytes received 305 bytes 3,166.00 bytes/sec
total size is 114,023 speedup is 72.03
[omm@bigdata01 hadoop]$
2.3 启动
- 格式化
[omm@bigdata01 ~]$ hdfs namenode -format
- 启动 hdfs
[omm@bigdata01 hadoop]$ start-dfs.sh
Starting namenodes on [bigdata01]
Starting datanodes
bigdata03: WARNING: /opt/module/hadoop/logs does not exist. Creating.
bigdata02: WARNING: /opt/module/hadoop/logs does not exist. Creating.
Starting secondary namenodes [bigdata03]
[omm@bigdata01 hadoop]$
- 启动 yarn
[omm@bigdata02 ~]$ start-yarn.sh
Starting resourcemanager
Starting nodemanagers
[omm@bigdata02 ~]$
- 查看启动情况
[omm@bigdata01 ~]$ jps
11988 DataNode
12265 NodeManager
11834 NameNode
12380 Jps
[omm@bigdata01 ~]$
[omm@bigdata02 ~]$ jps
1940 NodeManager
1605 DataNode
1772 ResourceManager
2238 Jps
[omm@bigdata02 ~]$
[omm@bigdata03 sbin]$ jps
9425 SecondaryNameNode
9538 NodeManager
9653 Jps
9343 DataNode
[omm@bigdata03 sbin]$
2.4 监控页面
NodeManager
ResourceManager
http://bigdata02:8088/cluster
HDFS
http://bigdata01:9870/dfshealth.html#tab-overview
HistoryServer
[omm@bigdata01 spark]$ mapred --daemon start historyserver
[omm@bigdata01 spark]$ jps
8867 DataNode
8741 NameNode
9861 Jps
9099 NodeManager
9822 JobHistoryServer
[omm@bigdata01 spark]$ ss -tlunp | grep 9822
tcp LISTEN 0 128 192.168.1.101:19888 *:* users:(("java",pid=9822,fd=325))
tcp LISTEN 0 128 *:10033 *:* users:(("java",pid=9822,fd=314))
tcp LISTEN 0 128 192.168.1.101:10020 *:* users:(("java",pid=9822,fd=331))
[omm@bigdata01 spark]$
- HDFS产生背景
- 随着数据量越来越大,在一个操作系统存不下所有的数据,那么就分配到更多的操作系统管理的磁盘中;
- 但这种方式不方便管理和维护,迫切需要一种系统来管理多台机器上的文件,这就是分布式文件管理系统。
- HDFS只是分布式文件管理系统中的一种。
- HDFS定义
- HDFS(Hadoop Distributed File System),它是一个文件系统,用于存储文件,通过目录树来定位文件;
- 其次,它是分布式的,由很多服务器联合起来实现其功能,集群中的服务器有各自的角色。
- HDFS的使用场景:适合一次写入,多次读出的场景,且不支持文件的修改。适合用来做数据分析,并不适合用来做网盘应用。
- HDFS 优点
- 高容错性:数据自动保存多个副本,提高容错性;某一个副本丢失以后,它可以自动恢复。
- 适合处理大数据
2.1 数据规模:能够处理数据规模达到GB、TB、甚至PB级别的数据;
2.2 文件规模:能够处理百万规模以上的文件数量,数量相当之大。 - 可构建在廉价机器上,通过多副本机制,提高可靠性。
- HDFS 缺点
- 不适合低延时数据访问,比如毫秒级的存储数据,是做不到的。
- 无法高效的对大量小文件进行存储。
2.1 存储大量小文件的话,它会占用NameNode大量的内存来存储文件目录和块信息,而NameNode的内存总是有限的;
2.2 小文件存储的寻址时间会超过读取时间,它违反了HDFS的设计目标。 - 不支持并发写入、文件随机修改。
3.1 一个文件只能有一个写,不允许多个线程同时写;
3.2 仅支持数据append(追加),不支持文件的随机修改。
3.1 组成架构
- NameNode(nn):它是一个主管、管理者。
- 管理HDFS的名称空间;
- 配置副本策略;
- 管理数据块(Block)映射信息;
- 处理客户端读写请求。
- DataNode:NameNode下达命令,DataNode执行实际的操作。
- 存储实际的数据块;
- 执行数据块的读/写操作。
- Client:就是客户端。
- 文件切分。文件上传HDFS的时候,Client将文件切分成一个一个的Block,然后进行上传;
- 与NameNode交互,获取文件的位置信息;
- 与DataNode交互,读取或者写入数据;
- Client提供一些命令来管理HDFS,比如NameNode格式化;
- Client可以通过一些命令来访问HDFS,比如对HDFS增删查改操作;
- Secondary NameNode:并非NameNode的热备。当NameNode挂掉的时候,它并不能马上替换NameNode并提供服务。
- 辅助NameNode,分担其工作量,比如定期合并Fsimage和Edits,并推送给NameNode ;
- 在紧急情况下,可辅助恢复NameNode。
3.2 HDFS Shell
- 完整命令
hadoop fs ...
和hdfs dfs ...
命令同价。命令格式:
hadoop fs -command src.. [dest]
[omm@bigdata01 ~]$ hadoop fs
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] [-d] [-t <thread count>] <localsrc> ... <dst>]
[-copyToLocal [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-count [-q] [-h] [-v] [-t [<storage type>]] [-u] [-x] [-e] <path> ...]
[-cp [-f] [-p | -p[topax]] [-d] <src> ... <dst>]
[-createSnapshot <snapshotDir> [<snapshotName>]]
[-deleteSnapshot <snapshotDir> <snapshotName>]
[-df [-h] [<path> ...]]
[-du [-s] [-h] [-v] [-x] <path> ...]
[-expunge [-immediate]]
[-find <path> ... <expression> ...]
[-get [-f] [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-getfacl [-R] <path>]
[-getfattr [-R] {-n name | -d} [-e en] <path>]
[-getmerge [-nl] [-skip-empty-file] <src> <localdst>]
[-head <file>]
[-help [cmd ...]]
[-ls [-C] [-d] [-h] [-q] [-R] [-t] [-S] [-r] [-u] [-e] [<path> ...]]
[-mkdir [-p] <path> ...]
[-moveFromLocal <localsrc> ... <dst>]
[-moveToLocal <src> <localdst>]
[-mv <src> ... <dst>]
[-put [-f] [-p] [-l] [-d] <localsrc> ... <dst>]
[-renameSnapshot <snapshotDir> <oldName> <newName>]
[-rm [-f] [-r|-R] [-skipTrash] [-safely] <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] [-s <sleep interval>] <file>]
[-test -[defsz] <path>]
[-text [-ignoreCrc] <src> ...]
[-touch [-a] [-m] [-t TIMESTAMP ] [-c] <path> ...]
[-touchz <path> ...]
[-truncate [-w] <length> <path> ...]
[-usage [cmd ...]]
Generic options supported are:
-conf <configuration file> specify an application configuration file
-D <property=value> define a value for a given property
-fs <file:///|hdfs://namenode:port> specify default filesystem URL to use, overrides 'fs.defaultFS' property from configurations.
-jt <local|resourcemanager:port> specify a ResourceManager
-files <file1,...> specify a comma-separated list of files to be copied to the map reduce cluster
-libjars <jar1,...> specify a comma-separated list of jar files to be included in the classpath
-archives <archive1,...> specify a comma-separated list of archives to be unarchived on the compute machines
The general command line syntax is:
command [genericOptions] [commandOptions]
[omm@bigdata01 ~]$
- 命令分类
数据流向 | 命令 | 说明 |
Local => HDFS | put | |
copyFromLocal | ||
moveFromLocal | ||
appendToFile | ||
HDFS => HDFS | cp | |
mv | ||
chown | ||
chgrp | ||
chmod | ||
mkdir | ||
du | ||
df | ||
cat | ||
rm | ||
HDFS => Local | get | |
getmerge | ||
copyToLocal |
- 上传
- -moveFromLocal:从本地剪切粘贴到HDFS
[atguigu@hadoop102 hadoop-3.1.3]$ touch kongming.txt
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -moveFromLocal ./kongming.txt /sanguo/shuguo
- -copyFromLocal:从本地文件系统中拷贝文件到HDFS路径去
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -copyFromLocal README.txt /
- -appendToFile:追加一个文件到已经存在的文件末尾
[atguigu@hadoop102 hadoop-3.1.3]$ touch liubei.txt
[atguigu@hadoop102 hadoop-3.1.3]$ vi liubei.txt
输入
san gu mao lu
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -appendToFile liubei.txt /sanguo/shuguo/kongming.txt
- -put:等同于copyFromLocal
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -put ./zaiyiqi.txt /user/atguigu/test/
- 下载
- -copyToLocal:从HDFS拷贝到本地
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -copyToLocal /sanguo/shuguo/kongming.txt ./
- -get:等同于copyToLocal,就是从HDFS下载文件到本地
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -get /sanguo/shuguo/kongming.txt ./
- -getmerge:合并下载多个文件
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -getmerge /user/atguigu/test/* ./zaiyiqi.txt
- 直接操作
- -ls: 显示目录信息
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -ls /
- -mkdir:在HDFS上创建目录
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -mkdir -p /sanguo/shuguo
- -cat:显示文件内容
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -cat /sanguo/shuguo/kongming.txt
- -chgrp 、-chmod、-chown:Linux文件系统中的用法一样,修改文件所属权限
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -chmod 666 /sanguo/shuguo/kongming.txt
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -chown atguigu:atguigu /sanguo/shuguo/kongming.txt
- -cp :从HDFS的一个路径拷贝到HDFS的另一个路径
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -cp /sanguo/shuguo/kongming.txt /zhuge.txt
- -mv:在HDFS目录中移动文件
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -mv /zhuge.txt /sanguo/shuguo/
- -tail:显示一个文件的末尾
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -tail /sanguo/shuguo/kongming.txt
- -rm:删除文件或文件夹
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -rm /user/atguigu/test/jinlian2.txt
- -rmdir:删除空目录
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -mkdir /test
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -rmdir /test
- -du统计文件夹的大小信息
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -du -s -h /user/atguigu/test
2.7 K /user/atguigu/test
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -du -h /user/atguigu/test
1.3 K /user/atguigu/test/README.txt
15 /user/atguigu/test/jinlian.txt
1.4 K /user/atguigu/test/zaiyiqi.txt
- -setrep:设置HDFS中文件的副本数量
这里设置的副本数只是记录在NameNode的元数据中,是否真的会有这么多副本,还得看(<=)DataNode的数量。
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -setrep 10 /sanguo/shuguo/kongming.txt
3.3 HDFS 客户端
- 客户端依赖
- 下载客户端依赖
Github链接:https://github.com/cdarlint/winutils
- 配置环境变量
- 验证
- 新建项目
- 父项目 POM 主要说明
<properties>
<hadoop.version>3.1.3</hadoop.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-api</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-runtime</artifactId>
<version>${hadoop.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
- HDFS 子项目 POM 主要说明
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-runtime</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 测试类
- 源码
package com.simwor.bigdata.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.net.URI;
public class HdfsTest {
private static FileSystem fileSystem;
@BeforeAll
public static void createConnection() throws IOException, InterruptedException {
fileSystem = FileSystem.get(URI.create("hdfs://bigdata01:8020"), new Configuration(), "omm");
}
@Test
public void testCopyFromLocalFile() throws IOException {
fileSystem.copyFromLocalFile(new Path("E:\\SOFT\\hadoop-3.1.4.tar.gz"), new Path("/"));
}
@Test
public void testGet() throws IOException {
fileSystem.copyToLocalFile(new Path("/hadoop-3.1.4.tar.gz"), new Path("C:\\Users\\m1553\\Desktop"));
}
@AfterAll
public static void closeConnection() throws IOException {
fileSystem.close();
}
}
- 运行验证
[omm@bigdata01 ~]$ hadoop fs -ls /
Found 2 items
-rw-r--r-- 3 omm supergroup 147145 2021-01-17 11:39 /LICENSE.txt
-rw-r--r-- 3 omm supergroup 348326890 2021-01-17 13:53 /hadoop-3.1.4.tar.gz
[omm@bigdata01 ~]$
C:\Users\m1553\Desktop>dir
C:\Users\m1553\Desktop 的目录
2021/01/17 16:16 <DIR> .
2021/01/17 16:16 <DIR> ..
2021/01/17 16:16 2,721,312 .hadoop-3.1.4.tar.gz.crc
2021/01/17 16:16 348,326,890 hadoop-3.1.4.tar.gz
2 个文件 351,048,202 字节
2 个目录 30,596,923,392 可用字节
C:\Users\m1553\Desktop>
3.4 HDFS 数据流
3.4.1 写数据流程
- 客户端通过Distributed FileSystem模块向NameNode请求上传文件,NameNode检查目标文件是否已存在,父目录是否存在。
- NameNode返回是否可以上传。
- 客户端请求第一个 Block上传到哪几个DataNode服务器上。
- NameNode返回3个DataNode节点,分别为dn1、dn2、dn3。
- 客户端通过FSDataOutputStream模块请求dn1上传数据,dn1收到请求会继续调用dn2,然后dn2调用dn3,将这个通信管道建立完成。
- dn1、dn2、dn3逐级应答客户端。
- 客户端开始往dn1上传第一个Block(先从磁盘读取数据放到一个本地内存缓存),以Packet为单位,dn1收到一个Packet就会传给dn2,dn2传给dn3;dn1每传一个packet会放入一个应答队列等待应答。
- 当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block的服务器。(重复执行3-7步)。
- 网络拓扑-节点距离计算
在HDFS写数据的过程中,NameNode会选择距离待上传数据最近距离的DataNode接收数据。
节点距离:两个节点到达最近的共同祖先的距离总和。
- 副本节点选择
- 第一个副本在Client所处的节点上。如果客户端在集群外,随机选一个。
- 第二个副本和第一个副本位于相同机架,随机节点。
- 第三个副本位于不同机架,随机节点。
3.4.2 读数据流程
- 客户端通过Distributed FileSystem向NameNode请求下载文件,NameNode通过查询元数据,找到文件块所在的DataNode地址。
- 挑选一台DataNode(就近原则,然后随机)服务器,请求读取数据。
- DataNode开始传输数据给客户端(从磁盘里面读取数据输入流,以Packet为单位来做校验)。
- 客户端以Packet为单位接收,先在本地缓存,然后写入目标文件。
3.5 NN 和 2NN
3.5.1 工作机制
- 流程图
- 元数据存放在内存中,并备份到磁盘命名为FsImage。
- 每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits日志文件中。
- 需要定期进行FsImage和Edits的合并,如果这个操作由SecondaryNamenode节点完成。
- NN 启动流程
- 第一次启动NameNode格式化后,创建Fsimage和Edits文件。如果不是第一次启动,直接加载编辑日志和镜像文件到内存。
- 客户端对元数据进行增删改的请求。
- NameNode记录操作日志,更新滚动日志。
- NameNode在内存中对元数据进行增删改。
- 2NN 工作流程
- Secondary NameNode询问NameNode是否需要CheckPoint。
- Secondary NameNode请求执行CheckPoint。
- NameNode滚动正在写的Edits日志。
- 将滚动前的编辑日志和镜像文件拷贝到Secondary NameNode。
- Secondary NameNode加载编辑日志和镜像文件到内存,并合并。
- 生成新的镜像文件fsimage.chkpoint。
- 拷贝fsimage.chkpoint到NameNode。
- NameNode将fsimage.chkpoint重新命名成fsimage。
- Fsimage和Edits解析
[omm@bigdata01 current]$ pwd
/opt/module/hadoop/data/name/current
[omm@bigdata01 current]$ ll
...
-rw-rw-r-- 1 omm omm 1048576 Feb 21 15:47 edits_0000000000000000068-0000000000000000068
-rw-rw-r-- 1 omm omm 1048576 Feb 22 09:50 edits_inprogress_0000000000000000069
...
-rw-rw-r-- 1 omm omm 398 Feb 22 09:50 fsimage_0000000000000000068
-rw-rw-r-- 1 omm omm 62 Feb 22 09:50 fsimage_0000000000000000068.md5
-rw-rw-r-- 1 omm omm 3 Feb 22 09:50 seen_txid
-rw-rw-r-- 1 omm omm 217 Feb 22 09:50 VERSION
[omm@bigdata01 current]$
- Fsimage文件:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件inode的序列化信息。
- Edits文件:存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到Edits文件中。
- seen_txid文件保存的是一个数字,就是最后一个edits_的数字
- 每次NameNode启动的时候都会将Fsimage文件读入内存,加载Edits里面的更新操作,保证内存中的元数据信息是最新的、同步的,可以看成NameNode启动的时候就将Fsimage和Edits文件进行了合并。
3.5.2 集群安全模式
- NameNode启动
- NameNode启动时,首先将镜像文件(Fsimage)载入内存,并执行编辑日志(Edits)中的各项操作。
- 一旦在内存中成功建立文件系统元数据的映像,则创建一个新的Fsimage文件和一个空的编辑日志。
- 此时,NameNode开始监听DataNode请求。这个过程期间,NameNode一直运行在安全模式,即NameNode的文件系统对于客户端来说是只读的。
- DataNode启动
- 系统中的数据块的位置并不是由NameNode维护的,而是以块列表的形式存储在DataNode中。
- 在系统的正常操作期间,NameNode会在内存中保留所有块位置的映射信息。
- 在安全模式下,各个DataNode会向NameNode发送最新的块列表信息,NameNode了解到足够多的块位置信息之后,即可高效运行文件系统。
- 安全模式退出判断
- 如果满足“最小副本条件”,NameNode会在30秒钟之后就退出安全模式。
- 所谓的最小副本条件指的是在整个文件系统中99.9%的块满足最小副本级别(默认值:dfs.replication.min=1)。
- 在启动一个刚刚格式化的HDFS集群时,因为系统中还没有任何块,所以NameNode不会进入安全模式。
- 基本语法
集群处于安全模式,不能执行重要操作(写操作)。集群启动完成后,自动退出安全模式。
bin/hdfs dfsadmin -safemode get
(功能描述:查看安全模式状态)bin/hdfs dfsadmin -safemode enter
(功能描述:进入安全模式状态)bin/hdfs dfsadmin -safemode leave
(功能描述:离开安全模式状态)bin/hdfs dfsadmin -safemode wait
(功能描述:等待安全模式状态)
3.6 DN
3.6.1 工作机制
- 流程图
- 一个数据块在DataNode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳。
- DataNode启动后向NameNode注册,通过后,周期性(1小时)的向NameNode上报所有的块信息。
- 心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令如复制块数据到另一台机器,或删除某个数据块。如果超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
- 集群运行中可以安全加入和退出一些机器。
- 掉线时限参数设置
需要注意的是hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒。
<property>
<name>dfs.namenode.heartbeat.recheck-interval</name>
<value>300000</value>
</property>
<property>
<name>dfs.heartbeat.interval</name>
<value>3</value>
</property>
3.6.2 扩容
- 准备一台干净的虚拟机
- 拷贝数据
[omm@bigdata01 ~]$ sudo rsync -avz /opt/module/hadoop-3.1.4 192.168.1.104:/opt/module
[omm@bigdata01 ~]$ sudo rsync -avz /opt/module/hadoop 192.168.1.104:/opt/module
[omm@bigdata01 ~]$ sudo rsync -avz /opt/module/jdk1.8.0_271 192.168.1.104:/opt/module
[omm@bigdata01 ~]$ sudo rsync -avz /opt/module/jdk 192.168.1.104:/opt/module
[omm@bigdata01 ~]$ sudo rsync -avz /etc/profile 192.168.1.104:/etc
- 环境准备及启动
[omm@bigdata04 ~]$ source /etc/profile
[omm@bigdata04 ~]$ sudo vi /etc/hosts
[omm@bigdata04 ~]$ cat /etc/hosts # 四台都配一下
...
192.168.1.101 bigdata01
192.168.1.102 bigdata02
192.168.1.103 bigdata03
192.168.1.104 bigdata04
[omm@bigdata04 hadoop]$ pwd
/opt/module/hadoop
[omm@bigdata04 hadoop]$ rm -rf data logs
[omm@bigdata04 hadoop]$ hdfs --daemon start datanode
WARNING: /opt/module/hadoop/logs does not exist. Creating.
[omm@bigdata04 hadoop]$ yarn --daemon start nodemanager
- 前端页面验证
3.6.3 退役
扩容节点并不需要配置主机间互信,但群起(start-dfs.sh/start-yarn.sh)等操作时时需要配置互信。这里已经配置过bigdata04的互信工作。
- 编写配置文件并重启生效(需同步集群配置文件)
[omm@bigdata01 ~]$ cd /opt/module/hadoop/etc/hadoop/
[omm@bigdata01 hadoop]$ vi hdfs-site.xml
[omm@bigdata01 hadoop]$ tail hdfs-site.xml
<property>
<name>dfs.hosts</name>
<value>/opt/module/hadoop/etc/hadoop/dfs.hosts</value>
</property>
<property>
<name>dfs.hosts.exclude</name>
<value>/opt/module/hadoop/etc/hadoop/dfs.hosts.exclude</value>
</property>
</configuration>
[omm@bigdata01 hadoop]$ touch dfs.hosts
[omm@bigdata01 hadoop]$ touch dfs.hosts.exclude
[omm@bigdata01 hadoop]$ cat << EOF > dfs.hosts
> bigdata01
> bigdata02
> bigdata03
> bigdata04
> EOF
> [omm@bigdata01 hadoop]$ # 同步配置文件到集群其它节点
[omm@bigdata01 hadoop]$ stop-dfs.sh
[omm@bigdata01 hadoop]$ start-dfs.sh
- 黑名单退役
[omm@bigdata01 hadoop]$ echo "bigdata04" > dfs.hosts.exclude
[omm@bigdata01 hadoop]$ hdfs dfsadmin -refreshNodes
[omm@bigdata02 hadoop]$ yarn rmadmin -refreshNodes
正在退役
已经退役(观察 bigdata01 数据量的变化)
- 退役节点停业务
[omm@bigdata04 ~]$ hdfs --daemon stop datanode
[omm@bigdata04 ~]$ yarn --daemon stop nodemanager
- 数据重分布
[omm@bigdata01 hadoop]$ start-balancer.sh
4.1 概述
MapReduce是一个分布式运算程序的编程框架,是用户开发“基于Hadoop的数据分析应用”的核心框架。
MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个Hadoop集群上。
- 优点
- MapReduce 易于编程:它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的PC机器上运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得MapReduce编程变得非常流行。
- 良好的扩展性:当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。
- 高容错性:MapReduce设计的初衷就是使程序能够部署在廉价的PC机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop内部完成的。
- 适合PB级以上海量数据的离线处理:可以实现上千台服务器集群并发工作,提供数据处理能力。
- 缺点
- 不擅长实时计算:MapReduce无法像MySQL一样,在毫秒或者秒级内返回结果。
- 不擅长流式计算:流式计算的输入数据是动态的,而MapReduce的输入数据集是静态的,不能动态变化。这是因为MapReduce自身的设计特点决定了数据源必须是静态的。
- 不擅长DAG(有向图)计算:多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce并不是不能做,而是使用后,每个MapReduce作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。
- 核心思想
- 分布式的运算程序往往需要分成至少2个阶段。
- 第一个阶段的MapTask并发实例,完全并行运行,互不相干。
- 第二个阶段的ReduceTask并发实例互不相干,但是他们的数据依赖于上一个阶段的所有MapTask并发实例的输出。
- MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行运行。
- MapReduce 进程
一个完整的MapReduce程序在分布式运行时有三类实例进程。
- MrAppMaster:负责整个程序的过程调度及状态协调。
- MapTask:负责Map阶段的整个数据处理流程。
- ReduceTask:负责Reduce阶段的整个数据处理流程。
4.2 WordCount
- 依赖包
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client-runtime</artifactId>
</dependency>
</dependencies>
- WordCountMapper
package com.simwor.bigdata.mapreduce;
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;
import java.util.StringTokenizer;
//Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
// KEYIN == 行号
// VALUEIN == 行文本
// KEYOUT == 单词
// VALUEOUT == 单词出现次数
public class WordCountMapper extends Mapper<LongWritable,Text,Text, IntWritable> {
private static final IntWritable one = new IntWritable(1);
private Text word = new Text();
/**
* 框架将文件一行行输入进来,此方法将数据变成(单词,1)的形式
*
* @param key 行号
* @param value 行文本
* @param context 任务本身
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while(itr.hasMoreTokens()) {
this.word.set(itr.nextToken());
context.write(this.word, this.one);
}
}
}
- WordCountReducer
package com.simwor.bigdata.mapreduce;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
//Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable result = new IntWritable();
/**
* 框架将KEYIN相同的数据分好组输入,此方法将VALUEIN累加。
* @param key
* @param values
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
for(IntWritable value : values)
sum += value.get();
this.result.set(sum);
context.write(key, this.result);
}
}
- WordCountDriver
相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象。
package com.simwor.bigdata.mapreduce;
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 {
//1. 获取Job实例
Job job = Job.getInstance();
//2. 设置Jar包
job.setJarByClass(WordCountDriver.class);
//3. 设置Mapper和Reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//4. 设置Map和Reduce的输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setMapOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//5. 设置输入输出文件
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//6. 提交Job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
- 打包上传
[omm@bigdata01 hadoop]$ pwd
/opt/module/hadoop
[omm@bigdata01 hadoop]$ mkdir custom-jobs
[omm@bigdata01 hadoop]$ ll custom-jobs/
total 41436
-rw-rw-r-- 1 omm omm 42427660 Feb 22 18:15 mapreduce-1.0-SNAPSHOT.jar
[omm@bigdata01 hadoop]$ hdfs dfs -put ./README.txt /
- 运行
[omm@bigdata01 hadoop]$ yarn jar custom-jobs/mapreduce-1.0-SNAPSHOT.jar com.simwor.bigdata.mapreduce.WordCountDriver /README.txt /wordcount
....
2021-02-22 18:31:55,092 INFO mapreduce.Job: map 0% reduce 0%
2021-02-22 18:31:59,143 INFO mapreduce.Job: map 100% reduce 0%
2021-02-22 18:32:04,271 INFO mapreduce.Job: map 100% reduce 100%
2021-02-22 18:32:04,281 INFO mapreduce.Job: Job job_1613981178835_0001 completed successfully
...
- 验证
4.3 序列化
4.3.1 概述
- 定义
- 序列化:序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。
- 反序列化:反序列化就是将收到字节序列(或其他数据传输协议)或者是磁盘的持久化数据,转换成内存中的对象。
- 为什么要做序列化
- 一般来说,“活的”对象只生存在内存里,关机断电就没有了。
- 而且“活的”对象只能由本地的进程使用,不能被发送到网络上的另外一台计算机。
- 然而序列化可以存储“活的”对象,可以将“活的”对象发送到远程计算机。
- 为什么不用Java的序列化
- Java的序列化是一个重量级序列化框架(Serializable)。
- ,一个对象被序列化后,会附带很多额外的信息(各种校验信息,Header,继承体系等),不便于在网络中高效传输。
- 所以,Hadoop自己开发了一套序列化机制(Writable)。
- Hadoop序列化特点:
- 紧凑 :高效使用存储空间。
- 快速:读写数据的额外开销小。
- 可扩展:随着通信协议的升级而可升级
- 互操作:支持多语言交互
- 常用的数据类型对应的Hadoop数据序列化类型
Java类型 | Hadoop Writable类型 |
Boolean | BooleanWritable |
Byte | ByteWritable |
Int | IntWritable |
Float | FloatWritable |
Long | LongWritable |
Double | DoubleWritable |
String |
|
Map | MapWritable |
Array | ArrayWritable |
4.3.2 自定义序列化类
- FlowBean
package com.simwor.mapreduce.writable;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
@Data
@NoArgsConstructor
public class FlowBean implements Writable {
private long upFlow;
private long downFlow;
private long sumFlow;
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.upFlow = dataInput.readLong();
this.downFlow = dataInput.readLong();
this.sumFlow = dataInput.readLong();
}
public void set(long upFlow, long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
this.sumFlow = upFlow + downFlow;
}
}
- FlowMapper
package com.simwor.mapreduce.writable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class FlowMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
private Text phone = new Text();
private FlowBean flowBean = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] fields = line.split("\t");
phone.set(fields[1]);
flowBean.set(Long.parseLong(fields[fields.length-3]),Long.parseLong(fields[fields.length-2]));
context.write(phone, flowBean);
}
}
- FlowReducer
package com.simwor.mapreduce.writable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class FlowReducer extends Reducer<Text,FlowBean,Text,FlowBean> {
private FlowBean flowBean = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
long sumUpFlow = 0;
long sumDownFlow = 0;
for(FlowBean value : values) {
sumUpFlow += value.getUpFlow();
sumDownFlow += value.getDownFlow();
}
flowBean.set(sumUpFlow, sumDownFlow);
context.write(key, flowBean);
}
}
- FlowDriver
package com.simwor.mapreduce.writable;
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 FlowDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(FlowDriver.class);
job.setMapperClass(FlowMapper.class);
job.setReducerClass(FlowReducer.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(FlowBean.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
FileInputFormat.setInputPaths(job, new Path("D:\\input"));
FileOutputFormat.setOutputPath(job, new Path("D:\\output"));
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
- 运行验证
13470253144 FlowBean(upFlow=180, downFlow=180, sumFlow=360)
13509468723 FlowBean(upFlow=7335, downFlow=110349, sumFlow=117684)
13560439638 FlowBean(upFlow=918, downFlow=4938, sumFlow=5856)
13568436656 FlowBean(upFlow=3597, downFlow=25635, sumFlow=29232)
13590439668 FlowBean(upFlow=1116, downFlow=954, sumFlow=2070)
13630577991 FlowBean(upFlow=6960, downFlow=690, sumFlow=7650)
13682846555 FlowBean(upFlow=1938, downFlow=2910, sumFlow=4848)
13729199489 FlowBean(upFlow=240, downFlow=0, sumFlow=240)
13736230513 FlowBean(upFlow=2481, downFlow=24681, sumFlow=27162)
13768778790 FlowBean(upFlow=120, downFlow=120, sumFlow=240)
13846544121 FlowBean(upFlow=264, downFlow=0, sumFlow=264)
13956435636 FlowBean(upFlow=132, downFlow=1512, sumFlow=1644)
13966251146 FlowBean(upFlow=240, downFlow=0, sumFlow=240)
13975057813 FlowBean(upFlow=11058, downFlow=48243, sumFlow=59301)
13992314666 FlowBean(upFlow=3008, downFlow=3720, sumFlow=6728)
15043685818 FlowBean(upFlow=3659, downFlow=3538, sumFlow=7197)
15910133277 FlowBean(upFlow=3156, downFlow=2936, sumFlow=6092)
15959002129 FlowBean(upFlow=1938, downFlow=180, sumFlow=2118)
18271575951 FlowBean(upFlow=1527, downFlow=2106, sumFlow=3633)
18390173782 FlowBean(upFlow=9531, downFlow=2412, sumFlow=11943)
84188413 FlowBean(upFlow=4116, downFlow=1432, sumFlow=5548)
4.4 工作流程
- 简单流程图
- 完整工作流程
4.4.1 Map 阶段
- InputFormat 阶段
- 将需要处理的文件进行切片;
- 将每个切片的数据拆分成 KV 对作为 Mapper 输入。
- 数据切片
- 一个Job的Map阶段并行度由客户端在提交Job时的切片数决定
- 每一个Split切片分配一个MapTask并行实例处理
- 默认情况下,切片大小=BlockSize
- 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片,即单个文件不足切片大小仍切分成一片
4.4.2 Shuffle 阶段
- Shuffle过程详解
上面的流程是整个MapReduce最全工作流程,但是Shuffle过程只是从第7步开始到第16步结束。
- MapTask收集我们的map()方法输出的kv对,放到内存缓冲区中
- 从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。
缓冲区的大小可以通过参数调整,参数:io.sort.mb默认100M。
- 多个溢出文件会被合并成大的溢出文件
- 在溢出过程及合并的过程中,都要调用Partitioner进行分区和针对key进行排序
- ReduceTask根据自己的分区号,去各个MapTask机器上取相应的结果分区数据
- ReduceTask会取到同一个分区的来自不同MapTask的结果文件,ReduceTask会将这些文件再进行合并(归并排序)
- 合并成大文件后,Shuffle的过程也就结束了,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一个的键值对Group,调用用户自定义的reduce()方法)
- Shuffle 分区机制
- 如 Map 阶段将大文件切片分别生成多个 MapTask,Shuffle阶段也存在分区的概念;会将数据进行分区交由多个Reducer处理;
- 分区的数量是由Reducer的数量决定的,Reducer的数量是手工设置的,有多少个Reducer,Shuffle阶段就会将数据写入到多少个分区;
job.setNumReduceTasks(10);
- Reduce阶段每个Reducer读取的是所有Map的同个分区。
- 局部合并器
Combiner
MR 每个阶段都伴随着数据落盘,假如在落盘前对数据进行局部合并,就可以有效地降低磁盘IO和网络IO。
- Combiner是MR程序中Mapper和Reducer之外的一种组件。
- Combiner组件的父类就是Reducer。
- Combiner和Reducer的区别在于运行的位置
3.1 Combiner是在每一个MapTask所在的节点运行;
3.2 Reducer是接收全局所有Mapper的输出结果; - Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量。
- Combiner能够应用的前提是不能影响最终的业务逻辑,而且,Combiner的输出kv应该跟Reducer的输入kv类型要对应起来。
- 分组比较器
- Reducer 接收到分区数据都是有序的;
- 分区数据会根据 “分区比较器” 对分区数据进行分组;
- 每组数据调用一次 reduce 方法;
- reduce 方法的参数 key 即为分组比较器分组比较的依据,values 即为分组比较器比较出的相同数据集合。
4.4.3 Reduce 阶段
OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了 OutputFormat接口。
- 文本输出TextOutputFormat
- 默认的输出格式是TextOutputFormat,它把每条记录写为文本行。
- 它的键和值可以是任意类型,因为TextOutputFormat调用toString()方法把它们转换为字符串。
- SequenceFileOutputFormat
- 将SequenceFileOutputFormat输出作为后续 MapReduce任务的输入,这便是一种好的输出格式,因为它的格式紧凑,很容易被压缩。
4.5 客制化类
4.5.1 Map 阶段
- 常见的 InputFormat
类型 | 说明 |
FileInputFormat | 【默认】文件方式切片 |
DBInputFormat | 数据库方式切片 |
TextInputFormat | 【默认】按照文件及HDFS Block切片,并将切片拆分成(LongWritable,Text)作为 Mappper 的输入 |
CombineFileInputFormat | 合并小文件,解决大量小文件单独提交一个MapTask的问题 |
NLineInputFormat | 按照文件行数切片 |
- 自定义
InputFormat
- CustomInputFormat
package com.simwor.mapreduce.inputformat;
import org.apache.hadoop.io.BytesWritable;
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.FileInputFormat;
import java.io.IOException;
// CustomInputFormat 做两件事情
// 1. 切片,已由 FileInputFormat 实现
// 2. 将文件映射为 KV 对,这是我们现在要做的
public class CustomInputFormat extends FileInputFormat<Text, BytesWritable> {
@Override
public RecordReader<Text, BytesWritable> createRecordReader(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
return new CustomRecordReader();
}
}
- CustomRecordReader
package com.simwor.mapreduce.inputformat;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
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 对,即一个文件当成 Mapper 的一条记录
public class CustomRecordReader extends RecordReader<Text, BytesWritable> {
private boolean isRead = false;
private Text key = new Text();
private BytesWritable value = new BytesWritable();
private FileSplit fileSplit;
private FSDataInputStream inputStream;
@Override
public void initialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException {
//开流
fileSplit = (FileSplit) inputSplit;
FileSystem fileSystem = FileSystem.get(taskAttemptContext.getConfiguration());
inputStream = fileSystem.open(fileSplit.getPath());
}
/**
* 读取下一组 KV 对
* @return 是否存在
* @throws IOException
*/
@Override
public boolean nextKeyValue() throws IOException {
if(!isRead) {
key.set(fileSplit.getPath().toString());
byte[] buffer = new byte[(int) fileSplit.getLength()];
inputStream.read(buffer);
value.set(buffer, 0, buffer.length);
isRead = true;
return true;
}
return false;
}
@Override
public Text getCurrentKey() {
return key;
}
@Override
public BytesWritable getCurrentValue() {
return value;
}
@Override
public float getProgress() {
return isRead ? 1 : 0;
}
@Override
public void close() {
IOUtils.closeStream(inputStream);
}
}
- CustomDriver
package com.simwor.mapreduce.inputformat;
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 org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import java.io.IOException;
// 无需再经过 Map 和 Reduce,按文件切片,每个文件映射成一个 KV,然后按流输出。
public class CustomDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(CustomDriver.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(BytesWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
job.setInputFormatClass(CustomInputFormat.class);
//直接将所有结果按流输出
job.setOutputFormatClass(SequenceFileOutputFormat.class);
FileInputFormat.setInputPaths(job, new Path("d:/input"));
FileOutputFormat.setOutputPath(job, new Path("d:/output"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
4.5.2 Shuffle 阶段
4.5.2.1 自定义分区 Partitioner
每一条 KV 对要发往哪一个分区,这是这个类目前要做的。
job.setNumReduceTasks(5);
job.setPartitionerClass(MyPartitioner.class);
package com.atguigu.mr.partitioner;
import com.atguigu.mr.flow.FlowBean;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class MyPartitioner extends Partitioner<Text, FlowBean> {
/**
* 对每一条KV对,返回它们对应的分区号
* @param text 手机号
* @param flowBean 流量
* @param numPartitions
* @return
*/
public int getPartition(Text text, FlowBean flowBean, int numPartitions) {
//去手机号前三位
String phone_head = text.toString().substring(0, 3);
switch (phone_head) {
case "136":
return 0;
case "137":
return 1;
case "138":
return 2;
case "139":
return 3;
default:
return 4;
}
}
}
4.5.2.2 自定义排序 WritableComparable
Shuffle 阶段会对 KV 对以 Key 排序,所以只要将想要排序的数据封装成 Bean 实现 WritableComparable 接口并放在 Mapper<LongWritable, Text,
NeedCompareBean
, xxx> 即可。
package com.atguigu.mr.compare;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* 实现WritableComparable接口
*/
public class FlowBean implements WritableComparable<FlowBean> {
private long upFlow;
private long downFlow;
private long sumFlow;
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
public void set(long upFlow, long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
this.sumFlow = upFlow + downFlow;
}
public long getUpFlow() {
return upFlow;
}
public void setUpFlow(long upFlow) {
this.upFlow = upFlow;
}
public long getDownFlow() {
return downFlow;
}
public void setDownFlow(long downFlow) {
this.downFlow = downFlow;
}
public long getSumFlow() {
return sumFlow;
}
public void setSumFlow(long sumFlow) {
this.sumFlow = sumFlow;
}
/**
* 将对象数据写出到框架指定地方
* @param dataOutput 数据的容器
* @throws IOException
*/
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
/**
* 从框架指定地方读取数据填充对象
* @param dataInput 数据的容器
* @throws IOException
*/
public void readFields(DataInput dataInput) throws IOException {
this.upFlow = dataInput.readLong();
this.downFlow = dataInput.readLong();
this.sumFlow = dataInput.readLong();
}
/**
* 比较方法,按照总流量降序排序
* @param o
* @return
*/
@Override
public int compareTo(FlowBean o) {
return Long.compare(o.sumFlow, this.sumFlow);
}
}
数据发送到 Reducer的数据就是以 FlowBean 自定义比较方法有序的。
package com.atguigu.mr.compare;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class CompareMapper extends Mapper<LongWritable, Text, FlowBean, Text> {
private Text phone = new Text();
private FlowBean flow = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//一行数据
String line = value.toString();
//切分
String[] fields = line.split("\t");
//封装
phone.set(fields[0]);
flow.setUpFlow(Long.parseLong(fields[1]));
flow.setDownFlow(Long.parseLong(fields[2]));
flow.setSumFlow(Long.parseLong(fields[3]));
//写出去
context.write(flow, phone);
}
}
4.5.2.3 自定义局部合并 Combiner
job.setCombinerClass(WordcountCombiner.class);
public class WordcountCombiner extends Reducer<Text, IntWritable, Text,IntWritable>{
@Override
protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
// 1 汇总操作
int count = 0;
for(IntWritable v :values){
count += v.get();
}
// 2 写出
context.write(key, new IntWritable(count));
}
}
4.5.2.4 自定义分组比较器 GroupingComparator
- 需求
有有如下订单数据,现在需要求出每一个订单中最贵的商品。
- 期望输入
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
- 期望输出
0000001 Pdt_01 222.8
0000002 Pdt_05 722.4
0000003 Pdt_06 232.8
- 需求分析
- 利用“订单id和成交金额”作为key,可以将Map阶段读取到的所有订单数据按照id升序排序,如果id相同再按照金额降序排序,发送到Reduce。
- 在Reduce端利用groupingComparator将订单id相同的kv聚合成组,然后取第一个即是该订单中最贵商品,如图4-18所示。
- 代码实现
- OrderBean
package com.atguigu.mr.grouping;
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 productId;
private double price;
@Override
public String toString() {
return orderId + "\t" + productId + "\t" + price;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
/**
* 排序逻辑:先按照订单排序,订单相同按照价格降序排列
* @param o
* @return
*/
@Override
public int compareTo(OrderBean o) {
int compare = this.orderId.compareTo(o.orderId);
if (compare != 0) {
return compare;
} else {
return Double.compare(o.price, this.price);
}
}
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(orderId);
out.writeUTF(productId);
out.writeDouble(price);
}
@Override
public void readFields(DataInput in) throws IOException {
this.orderId = in.readUTF();
this.productId = in.readUTF();
this.price = in.readDouble();
}
}
- OrderMapper
package com.atguigu.mr.grouping;
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;
/**
* 封装OrderBean
*/
public class OrderMapper extends Mapper<LongWritable, Text, OrderBean, NullWritable> {
private OrderBean order = new OrderBean();
/**
* Map用来封装OrderBean
* @param key
* @param value
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//拆分
String[] fields = value.toString().split("\t");
//封装
order.setOrderId(fields[0]);
order.setProductId(fields[1]);
order.setPrice(Double.parseDouble(fields[2]));
context.write(order, NullWritable.get());
}
}
- OrderComparator
package com.atguigu.mr.grouping;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
/**
* 按照订单编号对数据进行分组
*/
public class OrderComparator extends WritableComparator {
protected OrderComparator() {
super(OrderBean.class, true);
}
/**
* 分组比较方法,按照相同订单进入一组进行比较
* @param a
* @param b
* @return
*/
@Override
public int compare(WritableComparable a, WritableComparable b) {
OrderBean oa = (OrderBean) a;
OrderBean ob = (OrderBean) b;
return oa.getOrderId().compareTo(ob.getOrderId());
}
}
- OrderReducer
package com.atguigu.mr.grouping;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.Iterator;
/**
* 取每个订单的最高价格(扩展:取前二高的价格)
*/
public class OrderReducer extends Reducer<OrderBean, NullWritable, OrderBean, NullWritable> {
/**
* 取每个订单的前二高价格
* @param key 订单信息
* @param values 啥也没有
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
Iterator<NullWritable> iterator = values.iterator();
for (int i = 0; i < 2; i++) {
if (iterator.hasNext()) {
// ****** 当 iterator.next() 方法调用时 ******
// ****** 当 key 的值就会被重置 ******
NullWritable value = iterator.next();
context.write(key, value);
}
}
}
}
- OrderDriver
package com.atguigu.mr.grouping;
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 OrderDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(OrderDriver.class);
job.setMapperClass(OrderMapper.class);
job.setReducerClass(OrderReducer.class);
job.setMapOutputKeyClass(OrderBean.class);
job.setMapOutputValueClass(NullWritable.class);
//设置分组比较器
job.setGroupingComparatorClass(OrderComparator.class);
job.setOutputKeyClass(OrderBean.class);
job.setOutputValueClass(NullWritable.class);
FileInputFormat.setInputPaths(job, new Path("d:/input"));
FileOutputFormat.setOutputPath(job, new Path("d:/output"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
4.5.3 Reduce 阶段
- 自定义
OutputFormat
- MyOutputFormat
package com.atguigu.mr.outputformat;
import org.apache.hadoop.io.LongWritable;
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 MyOutputFormat extends FileOutputFormat<LongWritable, Text> {
/**
* 返回一个处理数据的Record Writer
* @param job
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public RecordWriter<LongWritable, Text> getRecordWriter(TaskAttemptContext job) throws IOException, InterruptedException {
return new MyRecordWriter(job);
}
}
- MyRecordWriter
package com.atguigu.mr.outputformat;
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.IOUtils;
import org.apache.hadoop.io.LongWritable;
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.FileOutputStream;
import java.io.IOException;
/**
* 将数据按照包不包含atguigu,分别输出到两个文件
*/
public class MyRecordWriter extends RecordWriter<LongWritable, Text> {
FSDataOutputStream atguigu = null;
FSDataOutputStream other = null;
public MyRecordWriter(TaskAttemptContext job) throws IOException {
Configuration configuration = job.getConfiguration();
String outDir = configuration.get(FileOutputFormat.OUTDIR);
FileSystem fileSystem = FileSystem.get(configuration);
atguigu = fileSystem.create(new Path(outDir + "/atguigu.log"));
other = fileSystem.create(new Path(outDir + "/other.log"));
}
/**
* 接收key value对,并按照值的不同写出到不通文件
* @param key 读取的一行的偏移量
* @param value 这一行内容
* @throws IOException
* @throws InterruptedException
*/
@Override
public void write(LongWritable key, Text value) throws IOException, InterruptedException {
//获取一行内容
String line = value.toString() + "\n";
//判断包不包含atguigu
if (line.contains("atguigu")) {
//往atguigu文件写数据
atguigu.write(line.getBytes());
} else {
//往other文件写数据
other.write(line.getBytes());
}
}
/**
* 关闭资源
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
public void close(TaskAttemptContext context) throws IOException, InterruptedException {
IOUtils.closeStream(atguigu);
IOUtils.closeStream(other);
}
}
- OutputDriver
package com.atguigu.mr.outputformat;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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 {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(OutputDriver.class);
job.setOutputFormatClass(MyOutputFormat.class);
FileInputFormat.setInputPaths(job, new Path("d:/input"));
FileOutputFormat.setOutputPath(job, new Path("d:/output2"));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
5.1 基本架构
5.2 工作机制
- MR程序提交到客户端所在的节点。
- YarnRunner向ResourceManager申请一个Application。
- RM将该应用程序的资源路径返回给YarnRunner。
- 该程序将运行所需资源提交到HDFS上。
- 程序资源提交完毕后,申请运行mrAppMaster。
- RM将用户的请求初始化成一个Task。
- 其中一个NodeManager领取到Task任务。
- 该NodeManager创建容器Container,并产生MRAppmaster。
- Container从HDFS上拷贝资源到本地。
- MRAppmaster向RM 申请运行MapTask资源。
- RM将运行MapTask任务分配给另外两个NodeManager,另两个NodeManager分别领取任务并创建容器。
- MR向两个接收到任务的NodeManager发送程序启动脚本,这两个NodeManager分别启动MapTask,MapTask对数据分区排序。
- MrAppMaster等待所有MapTask运行完毕后,向RM申请容器,运行ReduceTask。
- ReduceTask向MapTask获取相应分区的数据。
- 程序运行完毕后,MR会向RM申请注销自己。
5.3 资源调度器
目前,Hadoop作业调度器主要有三种:FIFO、Capacity Scheduler和Fair Scheduler。Hadoop3.1.3默认的资源调度器是Capacity Scheduler。
<property>
<description>The class to use as the resource scheduler.</description>
<name>yarn.resourcemanager.scheduler.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</value>
</property>
- 先进先出调度器(FIFO)
- 容量调度器(Capacity Scheduler)
- 支持多个队列,每个队列可配置一定的资源量,每个队列采用FIFO调度策略。
- 为了防止同一个用户的作业独占队列中的资源,该调度器会对同一用户提交的作业所占资源量进行限定。
- 首先,计算每个队列中正在运行的任务数与其应该分得的计算资源之间的比值,选择一个该比值最小的队列——最闲的。
- 其次,按照作业优先级和提交时间顺序,同时考虑用户资源量限制和内存限制对队列内任务排序。
- 三个队列同时按照任务的先后顺序依次执行,比如,job11、job21和job31分别排在队列最前面,先运行,也是并行运行。
- 公平调度器(Fair Scheduler)
- 同队列所有任务共享资源,在时间尺度上获得公平的资源
- 支持多队列多作业,每个队列可以单独配置
- 同一队列的作业按照其优先级分享整个队列的资源,并发执行
- 每个作业可以设置最小资源值,调度器会保证作业获得其以上的资源
- 公平调度器设计目标是:在时间尺度上,所有作业获得公平的资源。某一时刻一个作业应获资源和实际获取资源的差距叫“缺额”
- 调度器会优先为缺额大的作业分配资源
5.4 容量调度器多队列提交案例
- 需求
- Yarn默认的容量调度器是一条单队列的调度器,在实际使用中会出现单个任务阻塞整个队列的情况。
- 同时,随着业务的增长,公司需要分业务限制集群使用率。
- 这就需要我们按照业务种类配置多条任务队列。
- 配置多队列的容量调度器
- 默认Yarn的配置下,容量调度器只有一条Default队列。在capacity-scheduler.xml中可以配置多条队列,并降低default队列资源占比。
<property>
<name>yarn.scheduler.capacity.root.queues</name>
<value>default,hive</value>
<description>
The queues at the this level (root is the root queue).
</description>
</property>
<property>
<name>yarn.scheduler.capacity.root.default.capacity</name>
<value>40</value>
</property>
- 同时为新加队列添加必要属性
<!--队列目标资源百分比,所有队列相加必须等于100-->
<property>
<name>yarn.scheduler.capacity.root.hive.capacity</name>
<value>60</value>
</property>
<!--队列最大资源百分比-->
<property>
<name>yarn.scheduler.capacity.root.hive.maximum-capacity</name>
<value>100</value>
</property>
<!--单用户可用队列资源占比-->
<property>
<name>yarn.scheduler.capacity.root.hive.user-limit-factor</name>
<value>1</value>
</property>
<!--队列状态(RUNNING或STOPPING)-->
<property>
<name>yarn.scheduler.capacity.root.hive.state</name>
<value>RUNNING</value>
</property>
<!--队列允许哪些用户提交-->
<property>
<name>yarn.scheduler.capacity.root.hive.acl_submit_applications</name>
<value>*</value>
</property>
<!--队列允许哪些用户管理-->
<property>
<name>yarn.scheduler.capacity.root.hive.acl_administer_queue</name>
<value>*</value>
</property>
- 在配置完成后,重启Yarn,就可以看到两条队列。
- 向Hive队列提交任务
默认的任务提交都是提交到default队列的。如果希望向其他队列提交任务,需要在Driver中声明。
public class WcDrvier {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration configuration = new Configuration();
configuration.set("mapred.job.queue.name", "hive");
//1. 获取一个Job实例
Job job = Job.getInstance(configuration);
//2. 设置类路径
job.setJarByClass(WcDrvier.class);
//3. 设置Mapper和Reducer
job.setMapperClass(WcMapper.class);
job.setReducerClass(WcReducer.class);
//4. 设置Mapper和Reducer的输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
job.setCombinerClass(WcReducer.class);
//5. 设置输入输出文件
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
//6. 提交Job
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
- 这样,这个任务在集群提交时,就会提交到hive队列。
5.5 任务的推测执行
- 作业完成时间取决于最慢的任务完成时间
- 一个作业由若干个Map任务和Reduce任务构成。因硬件老化、软件Bug等,某些任务可能运行非常慢。
- 思考:系统中有99%的Map任务都完成了,只有少数几个Map老是进度很慢,完不成,怎么办?
- 推测执行机制
- 发现拖后腿的任务,比如某个任务运行速度远慢于任务平均速度。
- 为拖后腿任务启动一个备份任务,同时运行。
- 谁先运行完,则采用谁的结果。
- 执行推测任务的前提条件
- 每个Task只能有一个备份任务
- 当前Job已完成的Task必须不小于0.05(5%)
- 开启推测执行参数设置。mapred-site.xml文件中默认是打开的。
<property>
<name>mapreduce.map.speculative</name>
<value>true</value>
<description>If true, then multiple instances of some map tasks may be executed in parallel.</description>
</property>
<property>
<name>mapreduce.reduce.speculative</name>
<value>true</value>
<description>If true, then multiple instances of some reduce tasks may be executed in parallel.</description>
</property>
- 不能启用推测执行机制情况
- 任务间存在严重的负载倾斜;
- 特殊任务,比如任务向数据库中写数据。
- 推测执行算法原理
- 假设某一时刻,任务T的执行进度为progress,则可通过一定的算法推测出该任务的最终完成时刻estimateEndTime。另一方面,如果此刻为该任务启动一个备份任务,则可推断出它可能的完成时刻estimateEndTime`,于是可得出以下几个公式:
- MR总是选择(estimateEndTime- estimateEndTime)差值最大的任务,并为之启动备份任务。
- 为了防止大量任务同时启动备份任务造成的资源浪费,MR为每个作业设置了同时启动的备份任务数目上限。
- 造成 MapReduce 缓慢的可能原因
- 数据倾斜
- Map和Reduce数设置不合理
- Map运行时间太长,导致Reduce等待过久
- 小文件过多
- 大量的不可分块的超大文件
- Spill次数过多
- Merge次数过多等。
6.1 MapReduce 优化方法
MapReduce优化方法主要从六个方面考虑:数据输入、Map阶段、Reduce阶段、IO传输、数据倾斜问题和常用的调优参数。
6.1.1 数据输入
- 合并小文件:在执行MR任务前将小文件进行合并,大量的小文件会产生大量的Map任务,增大Map任务装载次数,而任务的装载比较耗时,从而导致MR运行较慢。
- 采用CombineTextInputFormat来作为输入,解决输入端大量小文件场景。
6.1.2 Map阶段
- 减少溢写(Spill)次数:通过调整io.sort.mb及sort.spill.percent参数值,增大触发Spill的内存上限,减少Spill次数,从而减少磁盘IO。
- 减少合并(Merge)次数:通过调整io.sort.factor参数,增大Merge的文件数目,减少Merge的次数,从而缩短MR处理时间。
- 在Map之后,不影响业务逻辑前提下,先进行Combine处理,减少 I/O。
6.1.3 Reduce阶段
- 合理设置Map和Reduce数:两个都不能设置太少,也不能设置太多。太少,会导致Task等待,延长处理时间;太多,会导致Map、Reduce任务间竞争资源,造成处理超时等错误。
- 设置Map、Reduce共存:调整slowstart.completedmaps参数,使Map运行到一定程度后,Reduce也开始运行,减少Reduce的等待时间。
- 规避使用Reduce:因为Reduce在用于连接数据集的时候将会产生大量的网络消耗。
- 合理设置Reduce端的Buffer:默认情况下,数据达到一个阈值的时候,Buffer中的数据就会写入磁盘,然后Reduce会从磁盘中获得所有的数据。也就是说,Buffer和Reduce是没有直接关联的,中间多次写磁盘->读磁盘的过程,既然有这个弊端,那么就可以通过参数来配置,使得Buffer中的一部分数据可以直接输送到Reduce,从而减少IO开销:mapreduce.reduce.input.buffer.percent,默认为0.0。当值大于0的时候,会保留指定比例的内存读Buffer中的数据直接拿给Reduce使用。这样一来,设置Buffer需要内存,读取数据需要内存,Reduce计算也要内存,所以要根据作业的运行情况进行调整。
6.1.4 IO传输
- 采用数据压缩的方式,减少网络IO的的时间。安装Snappy和LZO压缩编码器。
- 使用SequenceFile二进制文件。
6.1.5 数据倾斜问题
- 数据倾斜现象
- 数据频率倾斜——某一个区域的数据量要远远大于其他区域。
- 数据大小倾斜——部分记录的大小远远大于平均值。
- 减少数据倾斜的方法
- 抽样和范围分区:可以通过对原始数据进行抽样得到的结果集来预设分区边界值。
- 自定义分区:基于输出键的背景知识进行自定义分区。例如,如果Map输出键的单词来源于一本书。且其中某几个专业词汇较多。那么就可以自定义分区将这这些专业词汇发送给固定的一部分Reduce实例。而将其他的都发送给剩余的Reduce实例。
- Combine:使用Combine可以大量地减小数据倾斜。在可能的情况下,Combine的目的就是聚合并精简数据。
- 采用Map Join,尽量避免Reduce Join。
6.1.6 常用的调优参数
- 资源相关参数
以下参数是在用户自己的MR应用程序中配置就可以生效(mapred-default.xml)
配置参数 | 参数说明 |
mapreduce.map.memory.mb | 一个MapTask可使用的资源上限(单位:MB),默认为1024。如果MapTask实际使用的资源量超过该值,则会被强制杀死。 |
mapreduce.reduce.memory.mb | 一个ReduceTask可使用的资源上限(单位:MB),默认为1024。如果ReduceTask实际使用的资源量超过该值,则会被强制杀死。 |
mapreduce.map.cpu.vcores | 每个MapTask可使用的最多cpu core数目,默认值: 1 |
mapreduce.reduce.cpu.vcores | 每个ReduceTask可使用的最多cpu core数目,默认值: 1 |
mapreduce.reduce.shuffle.parallelcopies | 每个Reduce去Map中取数据的并行数。默认值是5 |
mapreduce.reduce.shuffle.merge.percent | Buffer中的数据达到多少比例开始写入磁盘。默认值0.66 |
mapreduce.reduce.shuffle.input.buffer.percent | Buffer大小占Reduce可用内存的比例。默认值0.7 |
mapreduce.reduce.input.buffer.percent | 指定多少比例的内存用来存放Buffer中的数据,默认值是0.0 |
- 应该在YARN启动之前就配置在服务器的配置文件中才能生效(yarn-default.xml)
配置参数 | 参数说明 |
yarn.scheduler.minimum-allocation-mb | 给应用程序Container分配的最小内存,默认值:1024 |
yarn.scheduler.maximum-allocation-mb | 给应用程序Container分配的最大内存,默认值:8192 |
yarn.scheduler.minimum-allocation-vcores | 每个Container申请的最小CPU核数,默认值:1 |
yarn.scheduler.maximum-allocation-vcores | 每个Container申请的最大CPU核数,默认值:32 |
yarn.nodemanager.resource.memory-mb | 给Containers分配的最大物理内存,默认值:8192 |
- Shuffle性能优化的关键参数,应在YARN启动之前就配置好(mapred-default.xml)
配置参数 | 参数说明 |
mapreduce.task.io.sort.mb | Shuffle的环形缓冲区大小,默认100m |
mapreduce.map.sort.spill.percent | 环形缓冲区溢出的阈值,默认80% |
- 容错相关参数(MapReduce性能优化)
配置参数 | 参数说明 |
mapreduce.map.maxattempts | 每个Map Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4。 |
mapreduce.reduce.maxattempts | 每个Reduce Task最大重试次数,一旦重试参数超过该值,则认为Map Task运行失败,默认值:4。 |
mapreduce.task.timeout | Task超时时间,经常需要设置的一个参数,该参数表达的意思为:如果一个Task在一定时间内没有任何进入,即不会读取新的数据,也没有输出数据,则认为该Task处于Block状态,可能是卡住了,也许永远会卡住,为了防止因为用户程序永远Block住不退出,则强制设置了一个该超时时间(单位毫秒),默认是600000。如果你的程序对每条输入数据的处理时间过长(比如会访问数据库,通过网络拉取数据等),建议将该参数调大,该参数过小常出现的错误提示是“AttemptID:attempt_14267829456721_123456_m_000224_0 Timed out after 300 secsContainer killed by the ApplicationMaster.”。 |
6.2 HDFS 小文件优化方法
- HDFS小文件弊端
- HDFS上每个文件都要在NameNode上建立一个索引,这个索引的大小约为150byte;
- 这样当小文件比较多的时候,就会产生很多的索引文件,一方面会大量占用NameNode的内存空间,另一方面就是索引文件过大使得索引速度变慢。
- HDFS小文件解决方案
- 在数据采集的时候,就将小文件或小批数据合成大文件再上传HDFS。
- 在业务处理之前,在HDFS上使用MapReduce程序对小文件进行合并。
- 在MapReduce处理时,可采用CombineTextInputFormat提高效率。
- 概述
- 所谓HA(High Availablity),即高可用(7*24小时不中断服务)。
- 实现高可用最关键的策略是消除单点故障。HA严格来说应该分成各个组件的HA机制:HDFS的HA和YARN的HA。
- Hadoop2.0之前,在HDFS集群中NameNode存在单点故障(SPOF)。
- HDFS HA功能通过配置Active/Standby两个NameNodes实现在集群中对NameNode的热备来解决上述问题。
7.1 HDFS HA
7.1.1 工作要点
- 元数据管理方式需要改变
- 内存中各自保存一份元数据;
- Edits日志只有Active状态的NameNode节点可以做写操作;
- 两个NameNode都可以读取Edits;
- 共享的Edits放在一个共享存储中管理(qjournal和NFS两个主流实现);
- 需要一个状态管理功能模块
- 实现了一个zkfailover,常驻在每一个namenode所在的节点;
- 每一个zkfailover负责监控自己所在NameNode节点,利用zk进行状态标识;
- 当需要进行状态切换时,由zkfailover来负责切换,切换时需要防止brain split现象的发生。
必须保证两个NameNode之间能够ssh无密码登录
隔离(Fence),即同一时刻仅仅有一个NameNode对外提供服务
7.1.2 自动故障转移工作机制
实现自动故障转移需要两个新的组件:ZooKeeper和ZKFailoverController(ZKFC)进程。
- 故障检测
- 集群中的每个NameNode在ZooKeeper中维护了一个持久会话;
- 如果机器崩溃,ZooKeeper中的会话将终止,ZooKeeper通知另一个NameNode需要触发故障转移。
- 现役NameNode选择
- ZooKeeper提供了一个简单的机制用于唯一的选择一个节点为active状态。
- 如果目前现役NameNode崩溃,另一个节点可能从ZooKeeper获得特殊的排外锁以表明它应该成为现役NameNode。
- ZKFC是自动故障转移中的另一个新组件,是ZooKeeper的客户端,也监视和管理NameNode的状态。
每个运行NameNode的主机也运行了一个ZKFC进程,ZKFC负责:
- 健康监测
- ZKFC使用一个健康检查命令定期地ping与之在相同主机的NameNode;
- 只要该NameNode及时地回复健康状态,ZKFC认为该节点是健康的。
- 如果该节点崩溃,冻结或进入不健康状态,健康监测器标识该节点为非健康的。
- ZooKeeper会话管理
- 当本地NameNode是健康的,ZKFC保持一个在ZooKeeper中打开的会话。
- 如果本地NameNode处于active状态,ZKFC也保持一个特殊的znode锁,该锁使用了ZooKeeper对短暂节点的支持,如果会话终止,锁节点将自动删除。
- 基于ZooKeeper的选择
- 如果本地NameNode是健康的,且ZKFC发现没有其它的节点当前持有znode锁,它将为自己获取该锁。
- 如果成功,则它已经赢得了选择,并负责运行故障转移进程以使它的本地NameNode为Active。
7.1.3 集群配置
- 集群规划
hadoop102 | hadoop103 | hadoop104 |
NameNode | NameNode | NameNode |
ZKFC | ZKFC | ZKFC |
JournalNode | JournalNode | JournalNode |
DataNode | DataNode | DataNode |
ZK | ZK | ZK |
ResourceManager | ||
NodeManager | NodeManager | NodeManager |
- 准备干净的 Hadoop 环境
[omm@bigdata01 ~]$ sudo mkdir /opt/ha
[omm@bigdata01 ~]$ sudo chown omm.wheel /opt/ha
[omm@bigdata01 ~]$ cp -r /opt/module/hadoop-3.1.4 /opt/ha/
[omm@bigdata01 ~]$ cd /opt/ha/
[omm@bigdata01 ha]$ ln -s hadoop-3.1.4 hadoop
[omm@bigdata01 ha]$ cd hadoop
[omm@bigdata01 hadoop]$ rm -rf data logs
- 修改配置文件
core-site.xml
[omm@bigdata01 hadoop]$ cd etc/hadoop/
[omm@bigdata01 hadoop]$ vim core-site.xml
[omm@bigdata01 hadoop]$ cat core-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://mycluster</value>
</property>
<property>
<name>hadoop.data.dir</name>
<value>/opt/ha/hadoop/data</value>
</property>
<property>
<name>hadoop.proxyuser.omm.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.omm.groups</name>
<value>*</value>
</property>
</configuration>
[omm@bigdata01 hadoop]$
- 修改并同步配置文件
hdfs-site.xml
[omm@bigdata01 hadoop]$ vim hdfs-site.xml
[omm@bigdata01 hadoop]$ cat hdfs-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>dfs.namenode.name.dir</name>
<value>file://${hadoop.data.dir}/name</value>
</property>
<property>
<name>dfs.datanode.data.dir</name>
<value>file://${hadoop.data.dir}/data</value>
</property>
<property>
<name>dfs.nameservices</name>
<value>mycluster</value>
</property>
<property>
<name>dfs.ha.namenodes.mycluster</name>
<value>nn1,nn2, nn3</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn1</name>
<value>bigdata01:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn2</name>
<value>bigdata02:8020</value>
</property>
<property>
<name>dfs.namenode.rpc-address.mycluster.nn3</name>
<value>bigdata03:8020</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn1</name>
<value>bigdata01:9870</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn2</name>
<value>bigdata02:9870</value>
</property>
<property>
<name>dfs.namenode.http-address.mycluster.nn3</name>
<value>bigdata03:9870</value>
</property>
<property>
<name>dfs.namenode.shared.edits.dir</name>
<value>qjournal://bigdata01:8485;bigdata02:8485;bigdata03:8485/mycluster</value>
</property>
<property>
<name>dfs.client.failover.proxy.provider.mycluster</name>
<value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
</property>
<property>
<name>dfs.ha.fencing.methods</name>
<value>sshfence</value>
</property>
<property>
<name>dfs.ha.fencing.ssh.private-key-files</name>
<value>/home/atguigu/.ssh/id_rsa</value>
</property>
<property>
<name>dfs.journalnode.edits.dir</name>
<value>${hadoop.data.dir}/jn</value>
</property>
<property>
<name>dfs.ha.automatic-failover.enabled</name>
<value>true</value>
</property>
<property>
<name>ha.zookeeper.quorum</name>
<value>bigdata01:2181,bigdata02:2181,bigdata03:2181</value>
</property>
</configuration>
[omm@bigdata01 ~]$ cd /opt
[omm@bigdata01 opt]$ sudo xsync ha
- 修改环境变量
[omm@bigdata01 ~]$ # 三台都做一下
[omm@bigdata01 ~]$ sudo vi /etc/profile
[omm@bigdata01 ~]$ tail /etc/profile
# Java
export JAVA_HOME=/opt/module/jdk
export PATH=$PATH:$JAVA_HOME/bin
# Hadoop
export HADOOP_HOME=/opt/ha/hadoop
export PATH=$PATH:$HADOOP_HOME/bin
# Hadoop sbin
export PATH=$PATH:$HADOOP_HOME/sbin
[omm@bigdata01 ~]$ source /etc/profile
[omm@bigdata01 ~]$ which hdfs
/opt/ha/hadoop/bin/hdfs
- 启动
[omm@bigdata01 ~]$ hdfs --workers --daemon start journalnode # 存放 NN 操作日志的 journal 集群
bigdata01: WARNING: /opt/ha/hadoop-3.1.4/logs does not exist. Creating.
bigdata02: WARNING: /opt/ha/hadoop-3.1.4/logs does not exist. Creating.
bigdata03: WARNING: /opt/ha/hadoop-3.1.4/logs does not exist. Creating.
[omm@bigdata01 ~]$ hdfs namenode -format # 格式化
[omm@bigdata01 ~]$ hdfs --daemon start namenode # 启动主 NN
[omm@bigdata02 ~]$ hdfs namenode -bootstrapStandby # 同步备NN
[omm@bigdata02 ~]$ hdfs --daemon start namenode
[omm@bigdata03 ~]$ hdfs namenode -bootstrapStandby # 同步备NN
[omm@bigdata03 ~]$ hdfs --daemon start namenode
[omm@bigdata01 ~]$ hdfs zkfc -formatZK # 格式化 ZK
[omm@bigdata01 ~]$ start-dfs.sh
- 验证
正确现象:一个 active 两个 standby
7.1.4 模拟故障自动切换
- 查看主NN进程号并将其杀掉
[omm@bigdata03 ~]$ jps
1505 QuorumPeerMain
4118 Jps
3991 NodeManager
3560 NameNode
3740 JournalNode
3629 DataNode
3869 DFSZKFailoverController
[omm@bigdata03 ~]$ kill -9 3560
- 解决报错:
fuser: command not found
[omm@bigdata01 ~]$ yum install psmisc
- 备 NN 自动升主
7.2 YARN HA
参考文档:https://hadoop.apache.org/docs/r3.2.2/hadoop-yarn/hadoop-yarn-site/ResourceManagerHA.html
- 集群规划
hadoop102 | hadoop103 | hadoop104 |
NameNode | NameNode | NameNode |
ZKFC | ZKFC | ZKFC |
JournalNode | JournalNode | JournalNode |
DataNode | DataNode | DataNode |
ZK | ZK | ZK |
ResourceManager | ResourceManager | |
NodeManager | NodeManager | NodeManager |
- 修改并同步配置文件
[omm@bigdata01 hadoop]$ pwd
/opt/ha/hadoop/etc/hadoop
[omm@bigdata01 hadoop]$ cat yarn-site.xml
<?xml version="1.0"?>
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>bigdata02</value>
</property>
<property>
<name>yarn.nodemanager.env-whitelist</name>
<value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOME</value>
</property>
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<property>
<name>yarn.log.server.url</name>
<value>http://bigdata01:19888/jobhistory/logs</value>
</property>
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>604800</value>
</property>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<!--启用resourcemanager ha-->
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<!--声明两台resourcemanager的地址-->
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>cluster-yarn1</value>
</property>
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm1</name>
<value>bigdata01</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name>
<value>bigdata02</value>
</property>
<!--指定zookeeper集群的地址-->
<property>
<name>yarn.resourcemanager.zk-address</name>
<value>bigdata01:2181,bigdata02:2181,bigdata03:2181</value>
</property>
<!--启用自动恢复-->
<property>
<name>yarn.resourcemanager.recovery.enabled</name>
<value>true</value>
</property>
<!--指定resourcemanager的状态信息存储在zookeeper集群-->
<property>
<name>yarn.resourcemanager.store.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
</property>
</configuration>
[omm@bigdata01 hadoop]$ cd ..
[omm@bigdata01 etc]$ xsync hadoop
- 启动并验证
[omm@bigdata02 ~]$ start-yarn.sh
Starting resourcemanagers on [ bigdata01 bigdata02]
Starting nodemanagers
bigdata03: nodemanager is running as process 2067. Stop it first.
bigdata02: nodemanager is running as process 2118. Stop it first.
bigdata01: nodemanager is running as process 2579. Stop it first.
[omm@bigdata01 ~]$ yarn rmadmin -getServiceState rm1
active
[omm@bigdata01 ~]$ yarn rmadmin -getServiceState rm2
standby
[omm@bigdata01 ~]$
7.3 HDFS Federation
7.3.1 NameNode架构的局限性
- Namespace(命名空间)的限制
- 由于NameNode在内存中存储所有的元数据(metadata),因此单个NameNode所能存储的对象(文件+块)数目受到NameNode所在JVM的heap size的限制。
- 50G的heap能够存储20亿(200million)个对象,这20亿个对象支持4000个DataNode,12PB的存储(假设文件平均大小为40MB)。
- 随着数据的飞速增长,存储的需求也随之增长。单个DataNode从4T增长到36T,集群的尺寸增长到8000个DataNode。存储的需求从12PB增长到大于100PB。
- 隔离问题
- 由于HDFS仅有一个NameNode,无法隔离各个程序,因此HDFS上的一个实验程序就很有可能影响整个HDFS上运行的程序。
- 性能的瓶颈
- 由于是单个NameNode的HDFS架构,因此整个HDFS文件系统的吞吐量受限于单个NameNode的吞吐量。
7.3.2 HDFS Federation 架构设计
不同应用可以使用不同NameNode进行数据管理图片业务、爬虫业务、日志审计业务。
Hadoop生态系统中,不同的框架使用不同的NameNode进行管理NameSpace。(隔离性)