通过Docker部署hadoop集群实现数据重删
集群规划
- 1个NameNode节点
- 1个SecondaryNameNode节点
- 1个ResourceManager节点
- 1个JobHistory节点
- 5个Slave节点
- 1个Client节点
其中Slave节点包含DataNode和NodeManager两种角色。Client节点是用来操作的节点,所有操作都尽量在这个节点上进行。以上共10个节点,7种角色。
由于官方apache/hadoop容器过于精简缺少一些hadoop运行所需的工具,所有先构建一个可以正常运行hadoop的模板镜像,再通过该模板镜像构建以上10个容器
实验环境
- windows11
- Docker version 27.3.1
- hadoop 3.4.1
提前下载的工具
- Docker Desktop Installer.exe (官网下载安装Docker)
- jdk-8u431-linux-x64(jdk1.8.0_431)Java Downloads | Oracle
一、配置模板镜像
使用官方apache/hadoop:3.4.1构建模板容器,配置java环境变量、安装ssh,用来创建后续容器。
1. 拉取官方apache/hadoop:3.4.1镜像
docker run -itd --name=hadoop_node apache/hadoop:3.4.1 bash -c "tail -f /dev/null"

使用官方apache/hadoop:3.4.1镜像运行容器
在主机运行
docker run -itd --name=hadoop_node apache/hadoop:3.4.1 bash -c "tail -f /dev/null"
-itd:表示交互式且后台运行,少了 -it 容器会自动停止
–name:容器名
bash:启动容器内的 Bash Shell
-c:告诉 Bash 执行一个字符串命令
tail -f /dev/null :容器不会执行任何有意义的任务,只是保持运行状态。tail -f /dev/null是一种常见的方式,用于让容器处于空闲但活动状态,避免它因为没有前台进程而自动退出。

从本地上传java jdk文件到容器并解压:
docker cp D:\xurui\Docker\hadoop-cluster\java\jdk-8u431-linux-x64.tar.gz hadoop_node:/root
docker cp <jdk路径> <容器id/容器名字>:<容器路径>

进入容器并解压
#进入容器指令
docker exec -it hadoop_node bash
#解压
tar -zxvf /root/jdk-8u431-linux-x64.tar.gz
yum换源与更新操作:
cd /etc/yum.repos.d/
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
yum clean all && yum makecache
yum update -y
2. 安装SSh
官方hadoop容器可以看做是一个非常精简的系统,很多功能没有,需要自己安装。Hadoop需要SSH,但容器没有自带,需要我们安装。
##首先需要设置密码
passwd root
##安装SSH
yum -y install openssh-clients openssh-server
##生成key文件
ssh-keygen -A
##启动sshd
/usr/sbin/sshd
查看运行进程,确认ssh启动

然后检查配置文件,查看以下两个配置是否开启,没有则在文件末尾补充
vim /etc/ssh/sshd_config
PermitRootLogin yes
PasswordAuthentication yes
#这两个配置允许外网通过 ssh 连接该服务器(容器)
运行hadoop需要which命令,同样容器没有自带,需要安装。
yum -y install which
3. 配置环境变量并在容器启动时启动sshd
在/etc/profile.d中新建一个run.sh文件
在run.sh文件中写入下面6行内容:
##运行容器时配置环境变量
export JAVA_HOME=/root/jdk1.8.0_431
export HADOOP_HOME=/opt/hadoop
export PATH=$PATH:$JAVA_HOME/bin
export PATH=$PATH:$HADOOP_HOME/bin
export PATH=$PATH:$HADOOP_HOME/sbin
##设置用户为root
export HDFS_NAMENODE_USER=root
export HDFS_DATANODE_USER=root
export HDFS_SECONDARYNAMENODE_USER=root
export YARN_RESOURCEMANAGER_USER=root
export YARN_NODEMANAGER_USER=root
启动sshd
/usr/sbin/sshd
用exit命令退出容器,重新启动容器并进入容器,上面配置的环境变量生效,sshd启动。
配置hadoop-env.sh
##/opt/hadoop/etc/hadoop/hadoop-env.sh
export JAVA_HOME=/root/jdk1.8.0_431
配置core-site.xml
<!-- /opt/hadoop/etc/hadoop/core-site.xml -->
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://localhost:9000</value>
</property>
</configuration>
配置hdfs-site.xml
<configuration>
<property>
<name>dfs.replication</name>
<value>1</value>
</property>
</configuration>
配置mapred-site.xml
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>yarn.app.mapreduce.am.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>
<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>
<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>
</configuration>
配置yarn-site.xml
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
</configuration>
4. 单节点hadoop运行测试
启动伪分布集群并运行wordcount示例程序
准备测试数据
在/root目录底下新建一个input文件夹,在这个文件夹中新建一个test.txt文件,里面随便写点单词,然后将该文件多复制几份,这里复制了5份。

#test.txt
2012-6-9 a
2012-6-10 b
2012-6-11 c
2012-6-12 d
2012-6-13 a
2012-6-14 b
2012-6-15 c
2012-6-11 c
格式化namenode
hdfs namenode -format
集群测试时,多次格式化namenode会导致namenode与datanode之间的不一致导致,每次格式化前要删除datanode数据
rm -rf /tmp/hadoop-root/dfs/data/*(datanode存储默认路径:/tmp/hadoop-root/dfs/data)
启动HDFS
start-dfs.sh
启动YARN
start-yarn.sh
查看相关进程是否都启动
jps

有以下5个进程,说明启动成功。
将测试数据复制到HDFS中
hdfs dfs -put /root/input /
运行wordcount示例程序
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.4.1.jar wordcount /input /output
查看输出结果
hdfs dfs -cat /output/part-r-00000

从截图可以看出输出正确。该容器配置正确。
二、利用模板镜像搭建集群
集群规划
1个NameNode节点
1个SecondaryNameNode节点
1个ResourceManager节点
1个JobHistory节点
5个Slave节点
1个Client节点
Client节点是用来操作的节点,所有操作都尽量在这个节点上进行。
将上面的模板容器打包成镜像
将上面的模板容器复制10份,修改配置文件来实现7种节点。但是Docker容器不能直接复制,需要先打包成镜像,然后用这个镜像生成10个新的容器。
命令如下:
docker commit -a "xr" -m "配置ssh以及相关环境变量" hadoop_node hadoop_node:v1
1. 创建集群
创建网络
docker network create hadoop_nw
新建一个hadoop_nw的网络,后面将10个Hadoop节点容器都加入到该网络,就能相互间通信了。而且不需要配置hosts文件
用模板镜像创建10个容器
docker run -itd --network hadoop_nw -h namenode --name namenode -p 9870:9870 hadoop_node:v1
docker run -itd --network hadoop_nw -h secondarynamenode --name secondarynamenode -p 9868:9868 hadoop_node:v1
docker run -itd --network hadoop_nw -h resourcemanager --name resourcemanager -p 8088:8088 hadoop_node:v1
docker run -itd --network hadoop_nw -h jobhistory --name jobhistory -p 19888:19888 hadoop_node:v1
docker run -itd --network hadoop_nw -h slave1 --name slave1 hadoop_node:v1
docker run -itd --network hadoop_nw -h slave2 --name slave2 hadoop_node:v1
docker run -itd --network hadoop_nw -h slave3 --name slave3 hadoop_node:v1
docker run -itd --network hadoop_nw -h slave4 --name slave4 hadoop_node:v1
docker run -itd --network hadoop_nw -h slave5 --name slave5 hadoop_node:v1
docker run -itd --network hadoop_nw -h client --name client hadoop_node:v1
打开docker桌面客户端查看容器

namenode、resourcemanager和jobhistory这3个节点做了端口映射。端口映射的作用是将Docker宿主机的某个端口映射到容器的某个端口上,这样通过访问Docker宿主机的这个端口就能间接访问到相应的容器端口了。就像从外网访问内网中的某台机器一样。在后面通过浏览器查看集群信息的时候会用到。
2. 在每个容器内执行以下操作
docker exec -it 节点容器 bash
sudo su -
#每个节点重置密码ssh才能连接成功
passwd root
#因为在前面模板镜像容器中伪分布式部署格式化过namenode,所以所有节点都要执行一次`rm -rf /tmp/hadoop-root/dfs/data/*`
rm -rf /tmp/hadoop-root/dfs/data/*
3. 修改Hadoop配置文件
在client节点修改以下配置文件,然后复制到其它节点。
配置core-site.xml
<!-- vim /opt/hadoop/etc/hadoop/core-site.xml -->
<configuration>
<property>
<name>fs.defaultFS</name>
<value>hdfs://namenode:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/opt/hadoop/data</value>
</property>
</configuration>
配置hdfs-site.xml
<!-- vim /opt/hadoop/etc/hadoop/hdfs-site.xml -->
<configuration>
<!-- nn web 端访问地址 -->
<property>
<name>dfs.namenode.http-address</name>
<value>namenode:9870</value>
</property>
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>secondarynamenode:9868</value>
</property>
<property>
<name>dfs.webhdfs.enabled</name>
<value>true</value>
</property>
</configuration>
配置mapred-site.xml
<!-- vim /opt/hadoop/etc/hadoop/mapred-site.xml -->
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<property>
<name>mapreduce.jobhistory.address</name>
<value>jobhistory:10020</value>
</property>
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>jobhistory:19888</value>
</property>
<property>
<name>yarn.app.mapreduce.am.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>
<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>
<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
</property>
</configuration>
配置yarn-site.xml
<!-- vim /opt/hadoop/etc/hadoop/yarn-site.xml -->
<configuration>
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>resourcemanager</value>
</property>
</configuration>
配置 workers
修改文件 /opt/hadoop/etc/hadoop/workers
将里边的内容去掉(把 localhost 删掉)然后换成5个从节点:
注意:行末不能有空格,以及不能有空行的出现
slave1
slave2
slave3
slave4
slave5
4. 配置节点间SSH免密登录
注意所有节点要重新设置一次密码passwd root,否则ssh无法连通
ssh要在root用户下才能成功,使用sudo su -切换root用户
务必测试所有节点都能通过ssh 主机名(容器名)连接后,hadoop集群才能连通
在client节点执行:
ssh-keygen
ssh-copy-id namenode
ssh-copy-id secondarynamenode
ssh-copy-id resourcemanager
ssh-copy-id jobhistory
ssh-copy-id slave1
ssh-copy-id slave2
ssh-copy-id slave3
ssh-copy-id slave4
ssh-copy-id slave5

在resourcemanager节点执行:
ssh-keygen
ssh-copy-id slave1
ssh-copy-id slave2
ssh-copy-id slave3
ssh-copy-id slave4
ssh-copy-id slave5
5. 复制配置文件到所有节点
在client节点执行:
scp -r $HADOOP_HOME/etc/hadoop namenode:$HADOOP_HOME/etc
scp -r $HADOOP_HOME/etc/hadoop secondarynamenode:$HADOOP_HOME/etc
scp -r $HADOOP_HOME/etc/hadoop resourcemanager:$HADOOP_HOME/etc
scp -r $HADOOP_HOME/etc/hadoop jobhistory:$HADOOP_HOME/etc
scp -r $HADOOP_HOME/etc/hadoop slave1:$HADOOP_HOME/etc
scp -r $HADOOP_HOME/etc/hadoop slave2:$HADOOP_HOME/etc
scp -r $HADOOP_HOME/etc/hadoop slave3:$HADOOP_HOME/etc
scp -r $HADOOP_HOME/etc/hadoop slave4:$HADOOP_HOME/etc
scp -r $HADOOP_HOME/etc/hadoop slave5:$HADOOP_HOME/etc
6. 启动Hadoop集群
在client节点执行:
1、格式化namenode
ssh namenode
hdfs namenode -format
多次格式化namenode会导致namenode与datanode之间的不一致导致,每次格式化前要删除/tmp/hadoop-root/dfs/data
rm -rf /tmp/hadoop-root/dfs/data/*
2、启动HDFS集群
ssh namenode
start-dfs.sh
#stop-dfs.sh
3、启动YARN集群
ssh resourcemanager
start-yarn.sh
#stop-yarn.sh
4、启动JobHistory
ssh jobhistory
/opt/hadoop/bin/mapred --daemon start historyserver
#/opt/hadoop/bin/mapred --daemon stop historyserver
HDFS http://localhost:50070/

5个slave节点作为datanode

YARN http://localhost:8088/

jobhistory http://localhost:19888/

7. 运行wordcount示例程序
将测试数据复制到HDFS中
hdfs dfs -put /root/input /
运行wordcount示例程序
hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.4.1.jar wordcount /input /output

查看输出结果
hdfs dfs -cat /output/part-r-00000

三、使用hadoop mapreduce进行数据去重
思路:
- Mapper 阶段:
每一行的数据(如 “2012-6-9 a”)作为键输出。
key 是日期和字母的组合,value 可以是一个固定值(如空字符串或常量)。 - Reducer 阶段:
Reducer 接收所有来自 Mapper 的键值对,利用 HashSet 或类似的数据结构去重。由于每行数据作为唯一的 key,相同的日期-字母组合会合并为一条数据。 - 输出:
输出去重后的每一行数据。
1. 代码实现
Mapper 类
在 Mapper 阶段,我们将输入的每一行(日期和字母)作为 key,值部分可以为空字符串或任意常量,因为我们不需要在 Reducer 阶段使用 value。最终我们通过 key 来去重。
//DedupMapper.java
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class DedupMapper extends Mapper<Object, Text, Text, Text> {
// Mapper方法
@Override
public void map(Object key, Text value, Context context) throws IOException, InterruptedException {
// 每一行数据格式:2012-6-9 a
// 输出键值对,键是每行数据,值是一个空字符串
context.write(value, new Text(""));
}
}
Reducer 类
Reducer 会接收 Mapper 输出的每一行数据,由于 Reducer 每个键只会收到一次,直接输出键即可实现去重。
//DedupReducer.java
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class DedupReducer extends Reducer<Text, Text, Text, Text> {
// Reducer方法
@Override
public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
// 直接输出key,它已经保证了唯一性
context.write(key, new Text(""));
}
}
Driver 类
//DedupDriver.java
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;
public class DedupDriver {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: DedupDriver <input path> <output path>");
System.exit(-1);
}
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "Data Deduplication");
job.setJarByClass(DedupDriver.class);
// 设置 Mapper 类和 Reducer 类
job.setMapperClass(DedupMapper.class);
job.setReducerClass(DedupReducer.class);
// 设置 Map 输出的类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
// 设置 Reduce 输出的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
// 设置输入和输出路径
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 提交作业并等待完成,如果成功返回 0,否则返回 1
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
2. 代码编译和文件上传
在client容器中编辑代码

编译java文件
javac -classpath $(hadoop classpath): *.java
打包java文件
jar -cvf DedupDriver.jar *.class
编辑去重文件file1、file2
#vim /root/input/file1
2012-6-9 a
2012-6-10 b
2012-6-11 c
2012-6-12 d
2012-6-13 a
2012-6-14 b
2012-6-15 c
2012-6-11 c
#vim /root/input/file2
2012-6-9 b
2012-6-10 a
2012-6-11 b
2012-6-12 d
2012-6-13 a
2012-6-14 c
2012-6-15 d
2012-6-11 c
将file1、file2上传到hdfs中
hdfs dfs -put /root/input /

3. 运行去重程序
进行数据去重
hadoop jar DedupDriver.jar DedupDriver /input /dedup/output
查看输出结果
hdfs dfs -cat /dedup/output/part-r-00000

结果正确



1993

被折叠的 条评论
为什么被折叠?



