Hadoop学习
根据B站尚硅谷的视频所作的一些笔记。视频链接如下:
https://www.bilibili.com/video/BV1cW411r7c5
一、前期准备
1.1 安装JDK
设置JAVA_HOME的环境变量和HADOOP_HOME环境变量
sudo vim /etc/profile, 在文件最后加入以下代码:
然后重新编译该文件
source /etc/profile
1.2 安装hadoop
将hadoop解压到相关文件夹(/opt/module/)下,配置HADOOP_HOME变量,如上述。
1.3 hadoop文件目录结构
bin目录:存放hadoop相关服务
etc目录:存放hadoop各种配置文件
include目录:存放代码的头文件,多以.h结尾
lib目录:本地目录
libexec目录:同lib
sbin目录:存放大量hadoop的相关命令
share目录:分享文档,有大量的说明文档以及官方案例
二、hadoop运行模式
2.1 本地模式
2.1.1 官方Grep案例
创建输入目录
mkdir input
拷贝文件
cp etc/hadoop/*.xml input
运行本地模式
bin/hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.2.1.jar grep input output ‘dfs[a-z.]+’
查看运行结果
cat output/*
注意:如果运行不成功,基本上是没有关闭防火墙(请关闭防火墙)
1、安装
yum install iptables-services #安装iptables
2、systemctl使用
systemctl unmask firewalld #执行命令,即可实现取消服务的锁定
systemctl mask firewalld # 下次需要锁定该服务时执行
systemctl start firewalld.service #启动防火墙
systemctl stop firewalld.service #停止防火墙
systemctl reloadt firewalld.service #重载配置
systemctl restart firewalld.service #重启服务
systemctl status firewalld.service #显示服务的状态
systemctl enable firewalld.service #在开机时启用服务
systemctl disable firewalld.service #在开机时禁用服务
systemctl is-enabled firewalld.service #查看服务是否开机启动
systemctl list-unit-files|grep enabled #查看已启动的服务列表
systemctl --failed #查看启动失败的服务列表
2.1.2 运行WordCount官方案例
此案例演示的是统计一个文件中个词语出现的次数,具体操作如下:
a.在hadoop文件夹下创建一个wcinput目录
b.在wcinput下创建一个wc.input文件并编辑
c.执行程序
d.查看执行结果
2.2 伪分布模式
2.2.1 HDFS并运行MapReduce
1. 配置hadoop-env.sh
a. linux下获取java JDK安装路径
b. 修改JAVA_HOME路径
export JAVA_HOME=/opt/module/jdk1.8.0_144
2. 配置core-site.xml:
路径:etc/hadoop/core-site.xml
<configuration>
<!-- 指定HDFS的NameNode的地址-->
<property>
<name>fs.defaultFS</name>
<value>hdfs://hadoop001:9000</value>
</property>
<!-- 指定hadoop运行中产生的文件的临时储存位置 -->
<property>
<name>hadoop.tmp.dir</name>.
<value>/opt/module/hadoop-2.7.2/data/tmp</value>
</property>
</configuration>
3. 配置hdfs-site.xml:
路径:etc/hadoop/hdfs-site.xml:
<configuration>
<!-- 指定HDFS副本的数量-->
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
</configuration>
4. 启动集群
a. 格式化NameNode(第一次启动格式化,以后就不要总是格式化了!)![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/8fce636d47467576e89906b70cd76fa1.png)
b. 启动NameNode
c. 启动DataNode
d. 查看集群
注意:jps是JDK的命令,没有安装JDK无法使用该命令!
e. web端查看HDFS系统
浏览器输入虚拟机的ip地址加上50070端口号
创建文件目录
查看目录
上传文件
注意:如果重启hadoop datanode无法重新启动,去core-site.xml中修改hdfs的地址,将主机名改为ip地址
2.2.2 启动YARN并运行MapReduce
1. 配置集群
a. 配置yarn-env.sh
配置一下JAVA_HOME
export JAVA_HOME=/opt/module/jdk1.8.0_144
b. 配置yarn-site.xml
<property>
<!-- 指定yarn ResourceManager的获取地址-->
<name>yarn.resourcemanager.hostname</name>
<value>hadoop001</value>
</property>
<property>
<!-- Reducer 获取数据的方式-->
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
c. 配置mapred-env.sh
配置一下JAVA_HOME
export JAVA_HOME=/opt/module/jdk1.8.0_144
d. 配置mapred-site.xml.template(将之改名为mapred-site.xml)
<property>
<!-- 指定MR 运行在yarn上-->
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
2. 启动集群
a. 启动ResourceManager
b. 启动NodeManager
c. 查看集群启动情况
d. web端查看yarn
网页输入虚拟机ip地址加端口号8088
试运行一下
运行结果如下:
2.2.3 配置历史服务器
1. 配置mapred-site.xml
<property>
<!-- 历史服务器地址-->
<name>mapreduce.jobhistory.address</name>
<value>hadoop001:10020</value>
</property>
<property>
<!-- 历史服务器web端地址-->
<name>mapreduce.jobhistory.webapp.address</name>
<value>hadoop001:19888</value>
</property>
2. 启动历史服务器
查看启动是否成功
web端查看结果
2.2.4 配置日志的聚集
1. 配置yarn-site.xml
注意,开启日志聚集功能需要重新启动NodeManager, ResourceManager, HistoryManager功能
<!-- 开启日志聚合 -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
</property>
<!-- 日志时长 设置日志保留七天-->
<property>
<name>yarn.log-aggregation.retain-seconds</name>
<value>640800</value>
</property>
2. 关闭NodeManager,ResourceManager,HistoryManager
3. 开启NodeManager,ResourceManager,HistoryManager
4. 删除HDFS上已存在的输出文件
5. 再次运行案例
查看日志
2.3 完全分布式模式(重点)
克隆虚拟机
vim /etc/udev/rules.d/70-persistent-net.rules
改IP地址(centos8已经自动分配,不需要改)
改主机名称
2.3.1 编写集群分发脚本xsync
1. scp
(1)scp定义(安全拷贝)
scp可以实现服务器与服务器之间的数据拷贝
(2)基本语法
scp -r 源文件路径 目的用户@主机:目的路径
例如:把hadoop001 /opt/module 拷到hadoop002的root用户 /opt/module 下
scp -r /opt/module root@hadoop002:opt/module
在例如:从hadoop003机器上,把hadoop001的 /opt/module 下的数据拷到本机的 /opt/module 下来
scp -r xsl@hadoop001:/opt/module /opt/
如果不是root账号的话,请在最前面加上sudo。
2. rsync 远程同步工具
rsync主要用与备份和镜像。具有速度快、避免复制相同内容和支持符号链接的有点。
rsync和scp区别:用rsync做文件的复制要比scp的速度快,rsync只对差异文件做更新。scp是把所有的文件都复制过去。
(1)基本语法
rsync -rvl 源文件路径 目的用户@主机名:目的文件路径
选项 | 功能 |
---|---|
-r | 递归 |
-l | 显示复制过程 |
-v | 拷贝符号连接 |
(2)案例实操
把hadoop001机器上的/opt/software 目录同步到 hadoop002服务器的root用户下的/opt/softwate 目录
rsync -rvl /opt/software/ root@hadoop002:/opt/software
3. xsync集群分发脚本
需求:循环复制文件到所有节点目录下
#!/bin/bash
#1 获取输入参数个数,如果没有参数,直接退出
pcount=$#
if((pcount==0)); then
echo no args;
exit;
fi
#2 获取文件名称
p1=$1
fname=`basename $p1`
echo fname=$fname
#3 获取上级目录到绝对路径
pdir=`cd -P $(dirname $p1); pwd`
echo pdir=$pdir
#4 获取当前用户名称
user=`whoami`
#5 循环
for((host=2; host<4; host++)); do
echo ------------------- hadoop$host --------------
rsync -rvl $pdir/$fname $user@hadoop00$host:$pdir
done
修改脚本 xsync,使他具有执行权限
chmod 777 xsync
调用脚本形式:
xsync 文件名称
试运行
运行成功之后,发现Hadoop002和Hadoop003的家目录都有bin目录以及目录下的xsync文件。
注意,如果脚本无法实现,可以将xsync移动到/usr/local/bin目录下。
2.3.2 集群配置
1. 集群配置规划
hadoop001 | hadoop002 | hadoop003 | |
---|---|---|---|
HDFS | NameNode DataNode | DataNode | SecondaryNameNode DataNode |
YARN | NodeManager | ResourceManager NodeManager | NodeManager |
2. 配置集群
(1)核心配置文件
配置hadoop001主机的core-site.xml:
hadoop001主机中的配置 vim etc/hadoop/core-site.xml,在文件中配置如下信息:
<configuration>
<!-- 指定HDFS的NameNode的地址-->
<property>
<name>fs.defaultFS</name>
<value>hdfs://hadoop001:9000</value>
</property>
<!-- 指定hadoop运行中产生的文件的临时储存位置 -->
<property>
<name>hadoop.tmp.dir</name>.
<value>/opt/module/hadoop-2.7.2/data/tmp</value>
</property>
</configuration>
(2)HDFS配置文件
配置hadoop001主机的hadoop-env.sh文件
export JAVA_HOME=/opt/module/jdk1.8.0_144
配置hadoop001主机的hdfs-site.xml
<!--设置副本数-->
<property>
<name>dfs.replication</name>
<value>3</value>
</property>
<!-- 指定hadoop辅助节点主机配置 -->
<property>
<name>dfs.namenode.secondary.http-address</name>.
<value>hadoop003:50090</value>
</property>
(3)YARN配置文件
配置hadoop001主机的yarn-env.sh文件
export JAVA_HOME=/opt/module/jdk1.8.0_144
配置hadoop001主机中的yarn-site.xml文件,将resourcemanager改为hadoop002
<property>
<!-- 指定yarn ResourceManager的获取地址-->
<name>yarn.resourcemanager.hostname</name>
<value>hadoop002</value>
</property>
<property>
<!-- Reducer 获取数据的方式-->
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
(4)MapReduce配置文件
配置hadoop001主机的mapred-env.sh文件
export JAVA_HOME=/opt/module/jdk1.8.0_144
配置mapred-site.xml.template(将之改名为mapred-site.xml),并在文件里面添加yarn的配置信息
<property>
<!-- 指定MR 运行在yarn上-->
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
3. 在集群上分发配置好的Hadoop配置文件
在 /opt/module/hadoop-2.7.2/etc 目录下运行脚本分发程序,将配置同步到hadoop002和hadoo003节点上去
xsync hadoop/
4. 查看文件分发情况
我们去看一下hadoo002机器的core-site.xml,发现确实分发过来了,没有问题。同样去看看hadoop003的core-site.xml配置文件,发现也没有问题。
hadoop002:core-site.xml
hadoop003:core-site.xml
2.3.3 集群单节点启动
注意,在启动集群之前,先去确认每个节点的hadoop下的data目录和logs目录是否删除。如果没有删除,务必先删除再格式化namenode!
(1)如果集群是第一次启动,需要格式化NameNode。在hadoop001的机器上对namenode节点进行格式化
bin/hdfs namenode -format
(2)在hadoop001上启动namonode。
sbin/hadoop-daemon.sh start namenode
jps查看一下启动成功没有。
(3)在hadoop001、hadoop002、hadoop003上启动datanode。
sbin/hadoop-daemon.sh start datanode
查看一下启动成功没有,发现集群已经启动成功了。
上述操作虽然成功启动了集群,但是这样一个一个启动真的太慢了。如果集群上千或者上万个,那得启动到什么时候?
2.3.4 SSH 无密登录设置
(1)原理:
(2)生成公钥和私钥
回到家目录,使用ls -al
,找到.ssh,进入。
在.ssh目录下运行ssh-keygen -t rsa
命令,连续三次回车生成密钥,之后ll
查看一下。
文件名称 | 文件作用 |
---|---|
known_hosts | 记录ssh访问过计算机的公钥(public key) |
id_rsa | 生成的私钥 |
id_rsa.pub | 生成的公钥 |
authorized_keys | 存放授权过得无密登录服务器公钥 |
(3)将公钥拷贝到要免密登录的目标机器上
如:将hadoop002上的公钥拷贝到hadoop003上
ssh-copy-id hadoop102
在hadoop003的机器上查看.ssh目录下,我们发现多了一个authorized_keys文件。
打开看一下,里面存的就是hadoop002的公钥。
之后我们在hadoop002上就可以无密的登陆到hadoop003上了。
总结:我们想要hadoop001可以免密的访问hadoop002、hadoop003
首先,在hadoop001的家目录下的.ssh目录下,先生成公私钥,然后将公钥发送给自己、hadoop002和hadoop003,之后就可以实现免密登录了。
那么为什么hadoop001需要免密登录呢?因为hadoop001上有个namenode,他是管理节点,而其他节点都是数据节点。它需要频繁的和其他数据节点通信来进行管理,所以需要设置免密登录。同样的,在hadoop002上有个resourcemanager节点,他是实现资源调度的,也需要频繁的和其他数据节点通信,所有也要设置免密登录。
注意:还需要在hadoop001上采用root账号,配置一下无密登录到hadoop001、hadoop002、hadoop003。
2.3.5 群起集群
1. 配置slaves
vim /etc/hadoop/slaves
在文件中添加如下信息:
hadoop101
hadoop102
hadoop103
注意:该文件中添加的内容结尾不允许有空格,文件中不允许有空行。
同步所有节点配置文件:
2. 启动集群
(1)第一次启动集群,格式化namenode。但是在格式化之前一定要确保namenode、datanode节点进程都关闭了,而且删除data和logs目录,最后在进行格式化。
(2)启动HDFS
sbin/start-dfs.sh
hadoop001:
hadoop002:
hadoop003:
(3)启动YARN
注意:NameNode和ResourceManger如果不是同一台机器,不能在NameNode上启动 YARN,应该在ResouceManager所在的机器上启动YARN。在我的测试中,我就hadoop002上启动YARN。
sbin/start-yarn.sh
hadoop001:
hadoop002:
hadoop003:
(4)集群测试
上传一个文件到HDFS上,查看显示上传成功!
bin/hdfs dfs -put wcinput/ /
我们再上传一个大的文件,可以看到我们传到HDFS上的文件已经实现了分布式存储。至此,简单的分布式环境已经搭好。
(4)集群启动/停止方式总结
1.单节点启动
(1)启动/停止HDFS
hadoop-daemon.sh start namenode / datanode / secondarynamenode
hadoop-daemon.sh stop namenode / datanode / secondarynamenode
(2)启动/停止YARN
yarn-daemon.sh start resourcemanager / nodemanager
yarn-daemon.sh stop resourcemanager / nodemanager
2.集群群起
(1)启动/停止HDFS
start-dfs.sh
stop-dfs.sh
(2)启动/停止YARN
start-yarn.sh
stop-yarn.sh
3. 集群时间同步(cenos8)
其中一台机器节点作为时钟节点,例如hadoop001为时间节点,其他节点每隔一段时间要和hadoop001同步一次。
配置hadoop001为时间同步节点
Centos Linux 8.1默认安装了chronyd服务。我们只需要配置即可。
vim /etc/chrony.conf
配置hadoop001为本机时间节点
将pool 2.centos.pool.ntp.org.iburst注释掉,不使用网络上的时间。添加下列内容:
server 192.168.23.101 iburst
同时允许192.168.23.0网段上的节点访问该节点时间并与之同步。
allow 192.168.23.0/24
将loacl stratum 10的注释解掉,字面翻译,提供时间即使没有同步一个时间源。
启动chronyd,并设置开机自启
systemctl start chronyd
systemctl enable chronyd
查看chronyd状态
systemctl status chronyd
查看同步时间源
chronyc sources –v
*表示正常,如果是?表示时间服务不可达,可能是防火墙没有关闭。防火墙要能放行此服务,关闭防火墙或是放行相应的端口及服务。关闭防火墙,并设置开机不启动。
systemctl stop firewalld
systemctl disable firewalld
或者设置防火墙放行规则
netstat -antup | grep chrony
设置放行upd 123端口
firewall-cmd --permanent --add-port=123/udp
firewall-cmd --reload
查看硬件时间
hwclock --show
将硬件时钟调整为与系统时钟一致
timedatectl set-local-rtc 1
或者
hwclock --systohc --localtime
hadoop002和hadoop003配置和hadoop001时间同步
配置vim /etc/chrony.conf
配置开机自启
systemctl restart chronyd
systemctl enable chronyd
查看同步状态及信息
timedatectl
查看时间同步源
chronyc sources -v
三、 HDFS客户端操作(重点)
3.1 HDFS客户端环境准备
1. 将win10编译过的jar包解压到不包含中文的路径下,例如:D:\Softwares\hadoop-2.7.2
2. 配置HADOOP_HOME环境变量
3. 配置Path环境变量
4. 创建一个Maven工程HdfsClientDemo
5. 导入相应的依赖和日志添加
依赖:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
</dependencies>
注意:我的jdk用的1.8版本,所以注意项目的jdk版本(项目右键 -> open Module setting -> project,选择jdk1.8 )。同时注意JAVA_HOME是不是1.8,不是的话,请修改\${JAVA_HOME}/lib/tools.jar
这一句。将\${JAVA_HOME}
改为jdk1.8的路径,或者修改你的JAVA_HOME为jdk1.8的路径。同时需要在项目的src/main/resources目录下,新建一个文件,命名为“log4j.properties”,在文件中填入如下信息:
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
6.创建包名:com.xsl.hdfs
在包下创建一个 HDFSClient 类
试着写一下代码:
public class HDFSClient {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration();
// 设置配置信息
conf.set("fs.defaultFS", "hdfs://hadoop001:9000"); //连接的集群地址
// 1.获取hdfs客户端对象
FileSystem fs = FileSystem.get(conf);
// 2.在hdfs上创建路径
fs.mkdirs(new Path("/user/xsl"));
// 3.关闭资源
fs.close();
System.out.println("========结束======");
}
}
但是我们发现报错误了,连不上集群。因为我们是要操纵linux下的xsl这个用户下的集群,而在windows下我们是administrator用户,所以我们先来修改一下用户。右键 -> Edit
在VM options中填写 -DHADOOP_USER_NAME=Hadoop集群用户名
比如我填写如下:
再次运行,发现成功。且hdfs成功创建了/user/xsl目录。
但是这样显得太麻烦。我们可以直接在获取客户端对象的时候将我们的信息加进去,如下:
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
3.2 HDFS的API操作
Hadoop封装好的I/O流操作。
3.2.1 HDFS的文件上传
// 1、HDFS文件上传
@Test
public void copyFileFromLocal() throws URISyntaxException, IOException, InterruptedException {
// 1、获取HDFS客户端对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、执行上传API操作
fs.copyFromLocalFile(new Path("E:/Code/banzhang.txt"), new Path("/user/xsl/test/banzhang.txt"));
// 3、关闭资源
fs.close();
}
上传成功。
3.2.2 HDFS的文件下载
// 2、HDFS文件下载
@Test
public void copyFileFromHDFS() throws URISyntaxException, IOException, InterruptedException {
// 1、获取HDFS客户端对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、执行下载API操作
fs.copyToLocalFile(new Path("/user/xsl/test/banzhang.txt"), new Path("E:/Code/banhua.txt"));
// 3、关闭资源
fs.close();
}
public void copyToLocalFile(boolean delSrc, Path src, Path dst, boolean useRawLocalFileSystem)
// 参数说明
delSrc:是否覆盖已经存在的文件。填true则会覆盖原来已经存在的文件。
src:hdfs文件路径
dst:下载到的本地路径
useRawLocalFileSystem:是否使用本地文件系统。填true则不会生成.crc检验文件。
3.2.3 HDFS的文件夹删除
// 3、HDFS文件夹删除
@Test
public void deleteDirFromHDFS() throws URISyntaxException, IOException, InterruptedException {
// 1、获取HDFS客户端对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、执行文件夹删除API操作
fs.delete(new Path("/user/xsl/test"), true);
// 3、关闭资源
fs.close();
}
public abstract boolean delete(Path var1, boolean var2) throws IOException;
// 参数说明
var1:待删除文件目录
var2:是否递归删除
3.2.4 HDFS的文件名更改
// 4、HDFS文件更名
@Test
public void fileRename() throws URISyntaxException, IOException, InterruptedException {
// 1、获取HDFS客户端对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、执行文件更名API操作
fs.rename(new Path("/user/xsl/test/banzhang.txt"), new Path("/user/xsl/test/fubanzhang.txt"));
// 3、关闭资源
fs.close();
}
3.2.5 HDFS的文件详情查看
// 5、HDFS文件详情查看
@Test
public void catFileList() throws URISyntaxException, IOException, InterruptedException {
// 1、获取HDFS客户端对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、执行文件详情查看API操作
RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);
// 3、遍历
while (listFiles.hasNext()){
LocatedFileStatus fileStatus = listFiles.next();
//查看文件名称、权限、长度、块信息
System.out.println(fileStatus.getPath().getName()); // 文件名称
System.out.println(fileStatus.getPermission()); // 文件权限
System.out.println(fileStatus.getLen()); // 文件长度
BlockLocation[] blockLocations = fileStatus.getBlockLocations(); // 块信息数组
for (BlockLocation blockLocation: blockLocations) {
String[] hosts = blockLocation.getHosts(); // 块节点地址信息数组
for (String host: hosts) {
System.out.println(host); // 打印块节点地址信息
}
}
System.out.println("--------------分割线------------------");
}
// 4、关闭资源
fs.close();
}
3.2.6 HDFS的文件和文件夹判断
// 5、HDFS文件和文件夹判断
@Test
public void fileTypeJudge() throws URISyntaxException, IOException, InterruptedException {
// 1、获取HDFS客户端对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、执行文件判断API操作
FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
for (FileStatus fileStatu: fileStatuses) {
if (fileStatu.isFile()){
System.out.println(fileStatu.getPath().getName()+" 是文件");
}else {
System.out.println(fileStatu.getPath().getName() + " 是文件夹");
}
}
// 3、关闭资源
fs.close();
}
3.3 HDFS的I/O流操作
自己实现I/O操作。
3.3.1 HDFS的文件上传
需求:把 E:/Code/banhua.txt 上传到HDFS的 /user/xsl/test/banhua.txt 上。
代码实现:
// 把 E:/Code/banhua.txt 上传到HDFS的 /user/xsl/test/banhua.txt 上。
@Test
public void putFileToHDFS() throws URISyntaxException, IOException, InterruptedException {
// 1、获取对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、获取输入流
FileInputStream fsIn = new FileInputStream(new File("E:/Code/banhua.txt"));
// 3、获取输出流
FSDataOutputStream fsOut = fs.create(new Path("/user/xsl/test/banhua.txt"));
// 4、流的对接拷贝
IOUtils.copyBytes(fsIn, fsOut, conf);
// 5、关闭资源
IOUtils.closeStream(fsOut);
IOUtils.closeStream(fsIn);
fs.close();
}
3.3.2 HDFS的文件下载
需求:把 HDFS上的 /user/xsl/test/banhua.txt 下载到本地 E:/Code/xiaocao.txt 上。
代码实现:
// 把 HDFS 上的 /user/xsl/test/banhua.txt 下载到本地 E:/Code/xiaocao.txt 上。
@Test
public void getFileFromHDFS() throws URISyntaxException, IOException, InterruptedException {
// 1、获取对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、获取输入流
FSDataInputStream fsIn = fs.open(new Path("/user/xsl/test/banhua.txt"));
// 3、获取输出流
BufferedOutputStream bfsOut = new BufferedOutputStream(new FileOutputStream(new File("E:/Code/xiaocao.txt")));
// 4、流的对接拷贝
IOUtils.copyBytes(fsIn, bfsOut, conf);
// 5、关闭资源
IOUtils.closeStream(bfsOut);
IOUtils.closeStream(fsIn);
fs.close();
}
3.3.3 定位文件读取
例如:分块下载HDFS下的 /hadoop-2.7.2.tar.gz 文件(文件被分为两块)。
下载第一块:
// 下载第一块
@Test
public void downloadFirstBlock() throws URISyntaxException, IOException, InterruptedException {
// 1、获取对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、获取输入流
FSDataInputStream fsIn = fs.open(new Path("/hadoop-2.7.2.tar.gz"));
// 3、获取输出流
BufferedOutputStream bfsOut = new BufferedOutputStream(new FileOutputStream(new File("E:/Code/hadoop-2.7.2.tar.gz.block1")));
// 4、流的对接拷贝(只拷贝第一块128M)
byte []buff = new byte[1024];
for (int i = 0; i < 1024 * 128; i++) {
fsIn.read();
bfsOut.write(buff);
}
// 5、关闭资源
IOUtils.closeStream(bfsOut);
IOUtils.closeStream(fsIn);
fs.close()
}
下载第二块:
// 下载第二块
@Test
public void downloadSecondBlock() throws URISyntaxException, IOException, InterruptedException {
// 1、获取对象
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop001:9000"), conf, "xsl");
// 2、获取输入流
FSDataInputStream fsIn = fs.open(new Path("/hadoop-2.7.2.tar.gz"));
// 3、获取指定读取的起点
fsIn.seek(1024*1024*128);
// 4、获取输出流
BufferedOutputStream bfsOut = new BufferedOutputStream(new FileOutputStream(new File("E:/Code/hadoop-2.7.2.tar.gz.block2")));
// 5、流的对接拷贝(拷剩下的文件)
IOUtils.copyBytes(fsIn, bfsOut, conf);
// 6、关闭资源
IOUtils.closeStream(bfsOut);
IOUtils.closeStream(fsIn);
fs.close();
}
我们可以将这两块合并起来看一下是不是原来的文件。输入cmd来到终端控制台。
将block2 追加到 block1,发现最终确实是原来的文件。
四、NameNode与SecondaryNameNode设置
4.1 ChekPoint时间设置
(1)一般情况下,SecondaryNameNode每隔一小时执行一次。
hdfs-default.xml
<property>
<name>dfs.namenode.checkpoint.period</name>
<!-- 单位是秒 -->
<value>3600</value>
</property>
(2)NameNode每一分钟检查一次,达到一百万次,SecondaryNameNode必须执行一次。
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
<description>操作次数的上限值</description>
</property>
<property>
<name>dfs.namenode.checkpoint.check.period</name>
<value>60</value>
<description> 设置一分钟检查一次</description>
</property >
4.2 NameNode故障处理
(1)可以通过将 SecondaryNameNode 中的数据拷贝到 NameNode 中来恢复数据
下面我们来模拟一下。
-
终止NameNode进程:kill -9 NameNode
kill -9 NameNode
-
删除NameNode存储的数据(/opt/module/hadoop-2.7.2/data/tmp/dfs/name)
rm -rf /opt/module/hadoop-2.7.2/data/tmp/dfs/name/*
-
将SecondaryNameNode中数据拷贝到NameNode存储数据的目录下
scp -r xsl@hadoop103:/opt/module/hadoop-2.7.2/data/tmp/dfs/namesecondary/* ./
-
重新启动NameNode
sbin/hadoop-daemon.sh start namenode
单节点启动
(2)通过采用 -importCheckpoint 选项来启动 NameNode 的守护进程,实现将SecondaryNameNode 中的数据拷贝到 NameNode 的数据存储目录中去。
- 先修改hdfs-site.xml中的配置:
vim etc/hadoop/hdfs-site.xml
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>120</value>
</property>
<property>
<name>dfs.namenode.name.dir</name>
<value>/opt/module/hadoop-2.7.2/data/tmp/dfs/name</value>
</property>
2. 终止NameNode进程:kill -9 NameNode
kill -9 3868
3.删除NameNode存储的数据(/opt/module/hadoop-2.7.2/data/tmp/dfs/name)
rm -rf /opt/module/hadoop-2.7.2/data/tmp/dfs/name/*
4.如果SecondaryNameNode不和NameNode在一个主机节点上,需要将SecondaryNameNode存储数据的目录拷贝到NameNode存储数据的平级目录,并删除in_use.lock文件
scp -r xsl@hadoop003:/opt/module/hadoop-2.7.2/data/tmp/dfs/namesecondary ./
rm -rf in_use.lock
5.导入检查点数据
bin/hdfs namenode -importCheckpoint
等待一会儿,就可以ctrl+c关闭。
6.启动NameNode节点
sbin/hadoop-daemon.sh start namenode
4.3 集群安全模式
当集群处于安全模式时,为只读模式。也就是说不能执行相应的写操作、增加操作、删除操作等。当集群完全启动之后会自动退出安全模式。
(1)bin/hdfs dfsadmin -safemode get :查看安全模式
(2)bin/hdfs dfsadmin -safemode enter :进入安全模式
(3)bin/hdfs dfsadmin -safemode leave :离开安全模式
(4)bin/hdfs dfsadmin -safemode wait :等待安全模式
- (1)
bin/hdfs dfsadmin -safemode get
:查看安全模式
即当前属于离开安全模式。 - (2)
bin/hdfs dfsadmin -safemode enter
:进入安全模式
即现在已经进入了安全模式。如果现在向集群上传文件我们会发现这是不允许的。
- (3)
bin/hdfs dfsadmin -safemode leave
:离开安全模式
即现集群现在已经离开了安全模式,可以对集群进行相关的操作。此时我们再上传文件到HDFS上则可以成功。
上传成功! - (4)
bin/hdfs dfsadmin -safemode wait
:等待安全模式,即等待集群退出安全模式再进行后续的操作。下面我们来模拟一下等待安全模式。
1.先查看当前安全模式状态
2.进入安全模式
3.编写一个shell脚本
touch wait.sh
vim wait.sh
在脚本里面写上下面的内容:
#!/bin/bash
hdfs dfsadmin -safemode wait
hdfs dfs -put /opt/module/hadoop-2.7.2/README.txt /
意思是,集群进入安全等待模式,一旦集群离开安全等待模式立马执行下面的语句,将README.txt文件上传到集群根目录。
4.执行脚本
bash wait.sh
现在为阻塞状态。
5.新开一个窗口执行bin/hdfs dfsadmin -safemode leave
,看看文件上传成功没有。
发现成功上传了README.txt文件。
4.4 NameNode多目录配置
为增加集群的可靠性,我们可已经NameNode的本地目录配置成多个,且每个目录都存放相同的内容。
配置如下:
1.首先关闭集群
sbin/stop-dfs.sh
sbin/stop-yarn.sh
[xsl@hadoop001 hadoop-2.7.2]$ sbin/stop-dfs.sh
[xsl@hadoop002 hadoop-2.7.2]$ sbin/stop-yarn.sh
2.删除 data 和 logs 目录,将集群清理干净
[xsl@hadoop001 hadoop-2.7.2]$ rm -rf data/ logs/
[xsl@hadoop002 hadoop-2.7.2]$ rm -rf data/ logs/
[xsl@hadoop003 hadoop-2.7.2]$ rm -rf data/ logs/
3.在hdfs-site.xml文件中修改内容为:
<property>
<name>dfs.namenode.name.dir</name>
<value>file:///${hadoop.tmp.dir}/dfs/name1,file:///${hadoop.tmp.dir}/dfs/name2</value>
</property>
4.分发脚本
xsync etc/hadoop/
[xsl@hadoop001 hadoop-2.7.2]$ xsync etc/hadoop/
5.格式化集群并启动
bin/hdfs namenode –format
[xsl@hadoop001 hadoop-2.7.2]$ bin/hdfs namenode -format
sbin/start-dfs.sh
[xsl@hadoop001 hadoop-2.7.2]$ sbin/start-dfs.sh
6.查看结果
我们发现两个目录里面的内容都一样。我们可以上传文件看看到底是不是一样的。
bin/hdfs dfs -put README.txt /
[xsl@hadoop001 hadoop-2.7.2]$ bin/hdfs dfs -put README.txt /
name1中的内容:
name2中的内容:
经过验证,确实这两个目录中的内容是一样的。
五、DataNode
5.1 掉线时限参数设置
DataNode要时常与NameNode进行通信,经过10分30秒,NameNode也没有收到DataNode的信息,NameNode就会认为DataNode已经挂掉。我们可以在 hdfs-site.xml 中设置这个时间限制。需要注意的是 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>
超时时间 = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval(即10分30秒)
5.2 服役新数据节点
5.2.1 环境准备
(1)在hadoop003上克隆一台hadoop004主机
(2)修改IP地址为 192.168.23.104 和主机名称 hadoop004
(3)删除原来HDFS文件系统留存的文件,即 hadoop 下的 data 和 logs 目录
(4)source一下配置文件source /etc/profile
5.2.2 服役新节点的具体步骤
(1)在hadoop004上直接启动DataNode,即可关联到集群
sbin/hadoop-daemon.sh start datanode
[xsl@hadoop004 hadoop-2.7.2]$ sbin/hadoop-daemon.sh start datanode
sbin/yarn-daemons.sh start nodemanager
[xsl@hadoop004 hadoop-2.7.2]$ sbin/yarn-daemons.sh start nodemanager
(2)在hadoop104上上传文件试一下
bin/hdfs dfs -put wcinput/ /
[xsl@hadoop004 hadoop-2.7.2]$ bin/hdfs dfs -put wcinput/ /
可以看到我们成功上传了文件,而且hadoop004节点上也存储了数据。但是这样很不安全,例如有人知道了namenode节点的ip地址,那么他可以将自己的节点接到集群上,这是很危险的。
5.3 退役旧数据节点
5.3.1 添加白名单
只有添加到白名单的节点主机才能够访问NameNode,不在白名单中的主机节点则会被退出。
具体步骤:
(1)在NameNode即hadoop001节点下的/opt/module/hadoop-2.7.2/etc/hadoop目录下创建dfs.hosts文件
[xsl@hadoop001 hadoop]$ pwd
/opt/module/hadoop-2.7.2/etc/hadoop
[xsl@hadoop001 hadoop]$ touch dfs.hosts
[xsl@hadoop001 hadoop]$ vim dfs.hosts
在dfs.hosts文件中添加主机名称,在这里我们不添加hadoop004,看看等会会怎么样。
hadoop101
hadoop102
hadoop103
(2)在NameNode即hadoop001主机节点的hdfs-site.xml配置文件中增加dfs.hosts属性
<property>
<name>dfs.hosts</name>
<value>/opt/module/hadoop-2.7.2/etc/hadoop/dfs.hosts</value>
</property>
(3)将配置文件分发到集群
xsync hdfs-site.xml
[xsl@hadoop001 hadoop-2.7.2]$ xsync etc/hadoop/hdfs-site.xml
(4)刷新NameNode
bin/hdfs dfsadmin -refreshNodes
[xsl@hadoop001 hadoop-2.7.2]$ bin/hdfs dfsadmin -refreshNodes
Refresh nodes successful
(5)更新ResourceManager节点
yarn rmadmin -refreshNodes
[xsl@hadoop001 hadoop-2.7.2]$ yarn rmadmin -refreshNodes
20/11/14 10:47:38 INFO client.RMProxy: Connecting to ResourceManager at hadoop002/192.168.23.102:8033
(6)在浏览器端查看
我们发现,刚才的hadoop004不在了。当一个节点退役之后,为了实现节点的负载均衡,我们可以使用下面的命令:
sbin/start-balancer.sh
5.3.2 黑名单退役
凡是在黑名单上的主机节点都会被勒令退出。
1.在NameNode即hadoop001主机节点的/opt/module/hadoop-2.7.2/etc/hadoop目录下创建dfs.hosts.exclude文件
[xsl@hadoop001 hadoop]$ pwd
/opt/module/hadoop-2.7.2/etc/hadoop
[xsl@hadoop001 hadoop]$ touch dfs.hosts.exclude
[xsl@hadoop001 hadoop]$ vim dfs.hosts.exclude
在文件中添加要退役的节点 hadoop
2.在NameNode即hadoop001的hdfs-site.xml配置文件中增加dfs.hosts.exclude属性
<property>
<name>dfs.hosts.exclude</name>
<value>/opt/module/hadoop-2.7.2/etc/hadoop/dfs.hosts.exclude</value>
</property>
3.分发配置文件到集群
[xsl@hadoop001 hadoop-2.7.2]$ xsync etc/hadoop/hdfs-site.xml
4.刷新NameNode、刷新ResourceManager
[xsl@hadoop001 hadoop-2.7.2]$ hdfs dfsadmin -refreshNodes
Refresh nodes successful
[xsl@hadoop001 hadoop-2.7.2]$ yarn rmadmin -refreshNodes
5. 在web端查看一下,会发现hadoop004的状态变为Decommissoned in progress,即正在退役中。它会将数据拷贝到其他节点。拷贝完成变成Decommissioned状态,即退役状态。
注意,白名单和黑名单中的节点只能出现一个!!!
5.4 DataNode多级目录设置
可以将DataNode配置成多级目录,每个目录保存的数据都不一样。也就是说数据不是副本。
具体配置步骤:
1.首先关闭集群
sbin/stop-dfs.sh
sbin/stop-yarn.sh
[xsl@hadoop001 hadoop-2.7.2]$ sbin/stop-dfs.sh
[xsl@hadoop002 hadoop-2.7.2]$ sbin/stop-yarn.sh
2.删除 data 和 logs 目录,将集群清理干净
[xsl@hadoop001 hadoop-2.7.2]$ rm -rf data/ logs/
[xsl@hadoop002 hadoop-2.7.2]$ rm -rf data/ logs/
[xsl@hadoop003 hadoop-2.7.2]$ rm -rf data/ logs/
3.在hdfs-site.xml文件中修改内容为:
<property>
<name>dfs.datanode.data.dir</name>
<value>file:///${hadoop.tmp.dir}/dfs/data1,file:///${hadoop.tmp.dir}/dfs/data2</value>
</property>
4.分发脚本
xsync etc/hadoop/hdfs-site.xml
[xsl@hadoop001 hadoop-2.7.2]$ xsync etc/hadoop/hdfs-site.xml
5.格式化集群并启动
bin/hdfs namenode –format
[xsl@hadoop001 hadoop-2.7.2]$ bin/hdfs namenode -format
sbin/start-dfs.sh
[xsl@hadoop001 hadoop-2.7.2]$ sbin/start-dfs.sh
6.查看结果
六、HDFS新特性
6.1 集群间的数据拷贝
1.通过scp实现集群间节点的拷贝
2.采用distcp命令实现两个集群间的数据递归复制
6.2 小文件归档
每个小文件都会在NameNode中产生一个小文件的元数据即相关数据索引,大概150字节。这样大量的小文件就会占据NameNode的大部分空间。这个时候就要使用小文档归档。
1.启动yarn
sbin/start-yarn.sh
2.归档文件
把/user/xsl/input目录里面的所有文件归档成一个叫input.har的归档文件,并把归档后文件存储到/user/xsl/output路径下。
命令如下:
bin/hadoop archive -archiveName input.har –p /user/xsl/input /user/xsl/output
3.查看归档
那么文件在哪呢?
4.解归档文件
[xsl@hadoop001 hadoop-2.7.2]$hadoop fs -ls -R har:///user/xsl/output/input.har
6.3 回收站
当数据删除的时候,首先会到回收站里,当超过一定时间才会真正的从回收站删除,在这之前数据都可以从回收站恢复以实现数据的误删导致集群崩溃的问题。HDFS集群回收站默认是关闭的。
开启回收站:
1.修改core-site.xml,配置垃圾回收时间为1分钟。单位为分钟。fs.trash.interval 默认值为0,表示回收站关闭。
<property>
<name>fs.trash.interval</name>
<value>1</value>
</property>
2.查看回收站
回收站在集群中的路径:/user/xsl/.Trash/….
3.修改能够访问回收站的用户,默认是dr.who,我们将之修改为xsl
vim etc/hadoop/core-site.xml
<property>
<name>hadoop.http.staticuser.user</name>
<value>xsl</value>
</property>
分发配置文件:
[xsl@hadoop001 hadoop-2.7.2]$ xsync etc/hadoop/core-site.xml
4.如果是通过程序删除数据的话是不会进入回收站的,必须手动调用moveToTrash()才会进入回收站。
代码如下:
Trash trash = New Trash(conf);
trash.moveToTrash(path);
5.试着删除 /user/xsl/input/file1.txt 文件
[xsl@hadoop001 hadoop-2.7.2]$ bin/hdfs dfs -rm -f /user/xsl/input/file1.txt
在HDFS的回收站中我们可以看到刚才删除的数据。
我们设置的一分钟,一分钟之后会自动删除。
6.恢复回收站数据
hadoop fs -mv /user/xsl/.Trash/Current/user/xsl/input /user/xslinput
7.清空回收站
hadoop fs -expunge
6.4 快照管理
快照,即对目录做一个状态记录,并不会复制所有文件。
相关命令:
1.hdfs dfsadmin -allowSnapshot 路径
:开启指定目录的快照
2.hdfs dfsadmin -disallowSnapshot 路径
:禁用指定目录的快照,集群默认都是禁用
3.hdfs dfs -createSnapshot 路径
:对目录创建快照
4.hdfs dfs -createSnapshot 路径 名称
:对指定目录创建指定名称的快照
5.hdfs dfs -renameSnapshot 路径 旧名称 新名称
:快照重命名
6.hdfs lsSnapshottableDir
:列出当前用户所有可以创建快照的目录
7.hdfs snapshotDiff 路径1 路径2
:比较两个快照目录的不同之处
8.hdfs dfs -deleteSnapshot path snapshotName
:删除快照
实操:
(1)开启指定目录的快照功能,如对 /user/xsl/input 目录开启快照。
(2)对 /user/xsl/input 创建快照
可以看到有快照了。
(3)对 /user/xsl/input 指定名称为 input_snapshot 创建快照
(4)列出当前用户所有可以创建快照的目录
(5)比较两个快照目录的不同之处
在 /user/xsl/input 下上传一个文件再进行比较
(6)删除快照 input_snapshot
七、MapReduce
7.1 WordCount案例实操
7.1.1 需求:将文本文件hello.txt中的单词统计它们出现的次数。![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b430e8485187d5bdbf7f76ccc6781946.png)
7.1.2 预期输出:
hello 2
word 2
zhangsan 2
lisi 2
wangwu 2
7.1.3 实操
1. 创建mapreduce的maven项目
2. 导入依赖包和日志文件
3. 创建xsl.com.mr.wordcount包,在之下创建WordCountMapper类
/**
* **** map阶段,继承Mapper *****
* KEYIN:输入数据的key,行的偏移量
* VALUEIN:输入数据的内容
* KEYOUT:输出数据的key hello 2
* VALUEOUT:输出数据的内容
*/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
Text k = new Text(); // 输出数据的key为Text类型
IntWritable v = new IntWritable(1); // 输出数据的value为IntWritable类
// 重写map方法
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// hello word(一行数据)
// 1.获取一行
String line = value.toString();
// 2.按照空格切割单词
String[] words = line.split(" ");
// 3.循环写出数据
for (String word: words) {
k.set(word); // 设置输出key
//v.set(1); // 设置输出value
context.write(k, v);
}
}
}
4. 创建WordCountReducer类
/**
* ****** reduce阶段 *************
* KEYIN:map阶段输出数据的key,行的偏移量
* VALUEIN:map阶段输出数据的内容
* KEYOUT:最终输出数据的key hello 2
* VALUEOUT:最终输出数据的内容
*/
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
IntWritable v = new IntWritable(); // 输出数据的value为IntWritable类
//重写reducer方法
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//hello,1
//word,1
// 1.累加求和
int sum = 0;
for (IntWritable value: values) {
sum += value.get();
}
// 2.写出,最终输出
v.set(sum); // 设置最终数据输出value
context.write(key, v);
}
}
5. 创建WordCountDriver类
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
// 1.获取Job对象
Job job = Job.getInstance(conf);
// 2.设置jar存储的位置
job.setJarByClass(WordCountDriver.class);
// 3.关联Map和Reduce类
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4.设置Mapper阶段输出数据的key和value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5.设置最终数据的输出的key和value
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6.设置输入路径和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0])); // 输入路径
FileOutputFormat.setOutputPath(job, new Path(args[1])); // 输出路径
// 7.提交Job
// job.submit();
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1); // 失败为1,成功为0
}
}
6. 运行WordCountDriver
右键 -> Edit
在 Program arguments 一栏中填写 hellol…txt 文件所在的目录以及输出的目录(注意输出目录不能已经存在)。然后点击确定,最后运行。
我们去 E:\Code\hadoop_test 目录下可以看到生成了output 目录,里面有我们程序最终生成的文件。
我们看一下 part-r-00000 文件中的内容。可以看到最终生成了我们想要的结果。
7.1.4 WordCount案例在集群上测试
将我们自己实现的代码打包,需要在 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>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<!-- 需要填写自己的类名路径 -->
<mainClass>xsl.com.mr.wordcount.WordCountDriver</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
或者使用 IDEA 自带的打包工具。
然后先 clean 再 package。
之后项目上就会生成下面的 jar 包,我们将他改成 wc.jar,然后上传到集群上的 hadoop 目录下面。
将我们的 hello.txt 文件上传到集群的 HDFS 下的 /input 下面。
然后使用我们的jar包运行下面的命令。注意要带上WordCountDriver类的全类名
hadoop jar wc.jar xsl.com.mr.wordcount.WordCountDriver /input/ /output
运行成功,我们去HDFS上看一下,确实有输出文件。
我们查看一下 part-r-00000 文件中内容。
hdfs dfs -cat /output/part-r-00000
这是集群HDFS上 /input/hello.txt 文件的原内容。
hadoop-2.7.2]$ hdfs dfs -cat /input/hello.txt
我们发现使用我们自己写的WordCount案例成功实现了单词的统计!!!
7.2 MapReduce序列化
序列化就是把内存中的对象,转化成字节序列(或其他数据传输协议)以便于存储到磁盘(持久化)和网络传输。反序列化就是将收到的字节序列(或其他数据传输协议)或者磁盘持久化的数据转换成内存中的对象。
7.2.1 序列化案例
1. 需求:统计每个手机号耗费的总上行流量、下行流浪、总流量
2. 数据:phone_data.txt
数据文件,对应字段分别为:
id 手机号码 ip地址 上行流量 下行流量 网络状态码
文件内容如下:
1 15561654675 192.168.100.1 www.sdfs.com 2481 56515 200
2 15352545656 192.168.100.2 www.gaadfa.com 2515 51522 200
3 16526505466 192.168.100.3 123 56448 404
4 63156156156 192.168.100.4 www.wewfsd.com 245 0 500
输入数据格式:
期望输出的数据格式:
3. 编写MapReduce程序
以手机号为 key,bean 对象为 value 输出,即 context.write(手机号, bean);
(1)创建一个包 xsl.com.mr.flowsum
(2)在包下创建类 FlowBean
public class FlowBean implements Writable {
private long upFlow; // 上行流量
private long downFlow; // 下行流量
private long sumFlow; // 总的流量
// 空参构造方法,为了后续反射用
public FlowBean(){
super();
}
// 带参数构造方法
public FlowBean(long upFlow, long downFlow, long sumFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
this.sumFlow = 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 {
upFlow = dataInput.readLong();
downFlow =dataInput.readLong();
sumFlow = dataInput.readLong();
}
public long getUpFlow() {
return upFlow;
}
public long getDownFlow() {
return downFlow;
}
public long getSumFlow() {
return sumFlow;
}
public void setUpFlow(long upFlow) {
this.upFlow = upFlow;
}
public void setDownFlow(long downFlow) {
this.downFlow = downFlow;
}
public void setSumFlow(long sumFlow) {
this.sumFlow = sumFlow;
}
// toString方法,方便输出查看
@Override
public String toString() {
return upFlow + "\t" + downFlow + "\t" + sumFlow;
}
}
(3)在包下创建类 FlowCountMapper,执行 map 阶段
/**
* **** map阶段,继承Mapper *****
* KEYIN:输入数据的key,行的偏移量
* VALUEIN:输入数据的内容
* KEYOUT:输出数据的key(手机号)
* VALUEOUT:输出数据的内容(FlowBean)
*/
public class FlowCountMapper extends Mapper<LongWritable, Text, Text, FlowBean> {
Text k = new Text();
FlowBean v = new FlowBean();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 15561654675 192.168.100.1 www.sdfs.com 2481 56515 200
// 1、获取一行
String line = value.toString();
// 2、以\t进行切割
String[] fields = line.split("\t");
// 3、封装数据
k.set(fields[1]); // 封装手机号
long upFlow = Long.parseLong(fields[fields.length - 3]); // 上行流量
long downFlow = Long.parseLong(fields[fields.length - 2]); // 下行流量
v.setUpFlow(upFlow); // 封装上行流量
v.setDownFlow(downFlow);// 封装下行流量
// 4、写出
context.write(k, v);
}
}
(4)在包下创建类 FlowCountReducer,执行 reduce 阶段
/**
* ****** reduce阶段 *************
* KEYIN:map阶段输出数据的key,行的偏移量
* VALUEIN:map阶段输出数据的内容
* KEYOUT:最终输出数据的key(手机号)
* VALUEOUT:最终输出数据的内容(FlowBean)
*/
public class FlowCountReducer extends Reducer<Text, FlowBean, Text, FlowBean> {
FlowBean v = new FlowBean();
@Override
protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException {
// 15561654675 2481 56515
// 15352545656 2515 51522
// 1、累加求和
long sum_upFlow = 0;
long sum_downFlow = 0;
for (FlowBean flowBean: values) {
sum_upFlow += flowBean.getUpFlow();
sum_downFlow += flowBean.getDownFlow();
}
v.setUpFlow(sum_upFlow);
v.setDownFlow(sum_downFlow);
v.setSumFlow(sum_upFlow + sum_downFlow);
// 2、写出
context.write(key, v);
}
}
(5)在包下创建类 FlowCountDriver
public class FlowCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
args = new String[]{"E:/Code/hadoop_test/input2", "E:/code/hadoop_test/output2"};
Configuration conf = new Configuration();
// 1、获取job对象
Job job = Job.getInstance(conf);
// 2、设置jar的路径
job.setJarByClass(FlowCountDriver.class);
// 3、关联mapper和reducer
job.setMapperClass(FlowCountMapper.class);
job.setReducerClass(FlowCountReducer.class);
// 4、设置mapper输出的key和value类型
job.setMapOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 5、设置最终输出的key和value类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FlowBean.class);
// 6、设置输入输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7、提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
(6)运行
输入:
输出:
part-r-00000:文件内容
嗯,是我们所期望的结果!!!
7.2.2 CombineTextInputFormat案例实操
1. 需求:将输入的大量小文件合并成一个切片
(1)输入数据:4个小文件。
(2)期望输出:希望将4个小文件合成一个切片处理。
2. 实现过程
(1)运行之前写的 WordCount 案例,看看处理的小文件的个数。(输入数据为上面的四个小文件)
(2)在 WordCount 案例的驱动类中添加下列代码,运行程序,观察切片的个数。
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class);
// 设置虚拟存储切片最大值为4m
CombineTextInputFormat.setMaxInputSplitSize(job, 1024);
发现切片个数变为一个,说明四个小文件合并成一个切片进行处理了。
7.2.3 KeyValueTextInputFormat 案例
1. 需求:统计输入文件中每一行的第一个单词相同的行数
(1)输入数据:
(2)预期结果:
zhangsan 2
lisi 2
zhouliu 1
2. 实现
(1)建立 xsl.com.mr.keyValueTextInputFormat 包
(2)创建 KVTextMapper 类
public class KVTextMapper extends Mapper<Text, Text, Text, IntWritable> {
// 1、封装对象
IntWritable v = new IntWritable(1);
@Override
protected void map(Text key, Text value, Context context) throws IOException, InterruptedException {
// 2、写出
context.write(key, v);
}
}
(3)创建KVTextReducer 类
public class KVTextReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
IntWritable v = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// 1、累加求和
int sum = 0;
for (IntWritable value: values) {
sum += value.get();
}
v.set(sum);
// 2、写出
context.write(key, v);
}
}
(4)创建KVTextDriver 类
public class KVTextDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
args = new String[]{"E:\\Code\\hadoop_test\\input4", "E:\\Code\\hadoop_test\\output4"};
Configuration conf = new Configuration();
// 设置切割符,即设置空格为切割符
conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, " ");
// 1.获取Job对象
Job job = Job.getInstance(conf);
// 2.设置jar存储的位置
job.setJarByClass(KVTextDriver.class);
// 3.关联Map和Reduce类
job.setMapperClass(KVTextMapper.class);
job.setReducerClass(KVTextReducer.class);
// 4.设置Mapper阶段输出数据的key和value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5.设置最终数据的输出的key和value
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 设置 文件切割类型为 KeyValueInputFormat
job.setInputFormatClass(KeyValueTextInputFormat.class);
// 6.设置输入路径和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0])); // 输入路径
FileOutputFormat.setOutputPath(job, new Path(args[1])); // 输出路径
// 7.提交Job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1); // 失败为1,成功为0
}
}
查看生成的结果: