本文档仅用于流程参考。
如有错误请联系我进行修改。
行文比较粗糙,还请见谅。
请善用AI进行辅助。
简述大概流程:
首先创建第一台虚拟机,然后搭建单机伪分布式Hadoop,并使用自带的wordcount进行验证。
然后修改为1主2从分布式集群,逐步引入zookeeper、HBase、Kafka,最后使用自动化脚本进行快速启动集群、停止集群、重启集群、查看集群状态。
软件包(点击下载-百度网盘)

安装VMware并创建第一台Linux虚拟机
前置软件:
VMware-Workstation-Full-25H2u1-25219725.exe(用于按照VMware虚拟机软件)CentOS-7-x86_64-DVD-2009.iso(用于创建第一台Linux虚拟机)
注意:VMware的安装自行搜索,安装完成后自行询问AI或者CSDN找教程配置虚拟网络编辑器。
创建Linux虚拟机





注意:磁盘容量最少20GB左右,实在不行,询问AI看分配多少GB。





点击开启虚拟机后,进入下图页面,鼠标左键点击任意黑色区域,鼠标消失后直接回车即可。(按键盘ctrl+alt可唤回鼠标,后续在虚拟机与本机电脑中的鼠标切换逻辑同理,后续不在复述)

回车后等待即可。

根据个人喜好选择英文还是中文或者其他语言,选择好后点击继续。

选择好后进入一些配置界面,具体配置如图。

安装位置配置如下图。

网络和主机名配置。
注意:这一步主机名取名为master。
注意:这一步主机名取名为master。
注意:这一步主机名取名为master。

配置完成后点击开始安装。


自行设置一个root密码和设置管理员用户。

安装完成后点击重启,点击重启按钮后啥都不用管。

输入刚刚设置的用户名和该用户的密码。

登录完成后输入whoami并回车,能够打印出你的用户名即可。
在这一步需要输入ip -4 addr,查看其中的ens33中的ipv4地址。

至此,Linux虚拟机已经创建完成。
使用Xshell和Xftp连接linux虚拟机
前置软件:
Xshell-8.0.0119p.exeXftp-8.0.0119p.exe
安装好后正常打开即可。
验证本机与虚拟机的网络是否连通
win+r后输入cmd并回车。

在黑窗口中输入ping ip地址,查看是否有类似下图的输出,如果有则证明连通。
如果没有或超时,检查虚拟机是否正常运行,或者询问AI、浏览器搜索相关教程解决办法。

使用Xshell连接虚拟机



点击确定后如果没有自动连接,那么点击左上角文件->打开->找到刚刚新建好的名字,打开。然后点击接受并保存。

接着连接成功后输入whoami和ip -4 addr查看是否和之前在虚拟机的输出一致。

至此,Xshell已经成功连接虚拟机。
使用Xftp连接虚拟机


至此Xshell和Xftp都已经连接好虚拟机,关于这两个软件的更多功能可以自行查阅资料或者询问AI。
前置环境准备
关闭防火墙
shell
# 立即停止防火墙服务
sudo systemctl stop firewalld
# 禁止防火墙开机自启
sudo systemctl disable firewalld
修改虚拟机IP与主机名映射
在xshell窗口中输入:
shell
# 进入根目录
cd ~
# 使用vi编辑hosts文件中的IP与主机名映射
sudo vi /etc/hosts
打开后界面如下图,在此界面按键盘上的i,确认左下角出现-- INSERT --提示。


接着使用键盘操作光标上下左右移动(键盘的上下左右)到可移动的最后一行末尾,在上图中就是第二行末尾,然后回车。
在回车后的新行键入你的虚拟机ip地址 空格 主机名(如果听从前面的建议取名为master那么这里就写master),举例:192.168.20.129 master
输入完成后按键盘上的esc按键,按下之后输入:wq然后回车。
如果还不理解自行搜索或询问AI在Linux centos7.9中关于vi的操作详解。

上传文件到虚拟机
上传jdk-8u202-linux-x64.tar.gz到虚拟机中。
直接拖进去就好 。

安装jdk
依次执行以下命令:
shell
# 确保 /opt 目录存在(已存在也不报错,-p 的作用)。用 sudo 是因为 /opt 属 root。
sudo mkdir -p /opt
# 把 JDK 压缩包解压到 /opt。-x解压、-zgzip、-f指定文件、-C目标目录。解压后得到 /opt/jdk1.8.0_202。
sudo tar -xzf ~/jdk-8u202-linux-x64.tar.gz -C /opt
# 把解压出来的长名字目录重命名为 /opt/jdk1.8,方便后续路径引用(和 Hadoop 配置里写的 JAVA_HOME=/opt/jdk1.8 对应)。
sudo mv /opt/jdk1.8.0_202 /opt/jdk1.8
# 写入一个全局环境变量脚本到 /etc/profile.d/java8.sh(该目录下的 .sh 会被所有用户登录时自动加载)
sudo tee /etc/profile.d/java8.sh >/dev/null <<'EOF'
# 紧接着输入以下三条命令,注意输完一条回车换行在输入下一条,输入完EOF后直接回车,一定不要打错字母,错一个后续全部报错
export JAVA_HOME=/opt/jdk1.8
export PATH=$JAVA_HOME/bin:$PATH
EOF
# 让刚写的脚本在当前 shell 立即生效,不用重新登录。
source /etc/profile.d/java8.sh
# 验证 JDK 安装:打印 Java 版本,能正常输出版本号(8u202)说明配置成功。
java -version
结果类似下图:

实践任务一:搭建一台伪分布式Hadoop系统
目标成果: 一台虚拟机里分别运行 NameNode、DataNode、SecondaryNameNode、ResourceManager、NodeManager服务;并且能够在浏览器打开 http://<这台虚拟机的IP>:9870查看管理页面。
上传hadoop-3.3.6.tar.gz
上传到/home/你的用户名目录下。
安装hadoop-3.3.6.tar.gz
在Xshell中依次输入以下命令:
shell
# 进入当前用户的目录
cd ~
# 解压 Hadoop 压缩包到 /opt 目录。-x解压、-zgzip、-f指定文件、-C指定目标目录。用 sudo 是因为 /opt 通常需要 root 权限。
sudo tar -xzf ./hadoop-3.3.6.tar.gz -C /opt/
# 创建两个目录:data(存 HDFS 数据)和 logs(存日志)。-p表示父目录不存在就一起建、已存在不报错。
mkdir -p /opt/hadoop-3.3.6/data /opt/hadoop-3.3.6/logs
# 向 ~/.bashrc 追加环境变量配置(>>追加不覆盖)
cat >> ~/.bashrc <<'EOF'
# 紧接着输入以下命令直到EOF,具体注意事项同配置jdk中一样。
# 指定 JDK 路径(Hadoop 依赖 Java)
export JAVA_HOME=/opt/jdk1.8
# Hadoop 安装根目录
export HADOOP_HOME=/opt/hadoop-3.3.6
# 配置文件目录
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
# 把 Hadoop 的 bin(命令)和 sbin(启停脚本)加入 PATH,方便直接敲 hadoop、start-dfs.sh
export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH
EOF
# 让刚改的 .bashrc 立即生效(不用重开终端)。
source ~/.bashrc
# 验证安装:打印 Hadoop 版本信息,能正常输出版本号说明环境配置成功。
hadoop version
最后的结果类似下图:

配置免密登录
在Xshell中依次输入以下命令:
shell
# 进入根目录
cd ~
# 生成一对 SSH 密钥(公钥+私钥),-t ed25519 指定密钥类型为 Ed25519(现代推荐算法,比 RSA 更短更安全)。交互式会询问保存路径(默认 ~/.ssh/id_ed25519)和密码短语(可直接回车留空)。生成私钥 id_ed25519 和公钥 id_ed25519.pub。
# 简单来说输入完命令后回车4次
ssh-keygen -t ed25519
# 把刚生成的公钥追加到 authorized_keys 文件。这个文件是 SSH 服务端校验的"允许免密登录的公钥列表"------把自己的公钥放进去,本机就能免密 SSH 登录自己(常用于 Hadoop 单机/伪分布式,让节点能免密 ssh 到自身)。
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
# 设置 ~/.ssh 目录权限为 700(仅属主可读/写/进入)。SSH 对权限要求严格,目录权限过宽会拒绝使用密钥。
chmod 700 ~/.ssh
# 设置 authorized_keys 权限为 600(仅属主可读写)。同样,权限过宽(如组或其他用户可写)SSH 会拒绝该文件。
chmod 600 ~/.ssh/authorized_keys
# 测试免密登录:以 SSH 方式连接到主机 master(需要在 /etc/hosts 或 DNS 里能解析到,通常指向本机),执行命令 hostname 并返回结果。如果不用输密码就输出主机名,说明免密配置成功。master 是 Hadoop 集群里常见的 master 节点主机名。
# 回车后如果询问输入yes或no,输入yes
ssh master 'hostname'
最后的结果类似下图:

配置hadoop
注意,接下来的一些配置文件编辑,都会使用到
vi,如果不熟练或者不太清楚请先自行搜索或者询问AI关于vi的相关操作教程,本文不在此大量叙述vi的教程。
配置hadoop-env.sh
输入命令:
shell
# 进入目录
cd /opt/hadoop-3.3.6/etc/hadoop/
# 编辑hadoop-env.sh
vi hadoop-env.sh
# 输入内容,如下图,输入完成后按esc,:wq保存并退出
export JAVA_HOME=/opt/jdk1.8

配置core-site.xml
输入命令:
shell
# 编辑core-site.xml
vi core-site.xml
# 输入内容(一个字母都不能错),如下图,输入完成后按esc,:wq保存并退出
<property>
<name>fs.defaultFS</name>
<value>hdfs://master:9000</value>
</property>
<property>
<name>hadoop.tmp.dir</name>
<value>/opt/hadoop-3.3.6/data/tmp</value>
</property>

配置hdfs-site.xml
输入命令:
shell
# 编辑hdfs-site.xml
vi hdfs-site.xml
# 输入内容(一个字母都不能错),如下图,输入完成后按esc,:wq保存并退出
<property>
<name>dfs.replication</name>
<value>1</value>
</property>

配置mapred-site.xml
输入命令:
shell
# 编辑mapred-site.xml
vi mapred-site.xml
# 输入内容(一个字母都不能错),如下图,输入完成后按esc,:wq保存并退出
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>

配置yarn-site.xml
输入命令:
shell
# 编辑yarn-site.xml
vi yarn-site.xml
# 输入内容(一个字母都不能错),如下图,输入完成后按esc,:wq保存并退出
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</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>

配置workers
输入命令:
shell
# 编辑workers
vi /opt/hadoop-3.3.6/etc/hadoop/workers
# 删除原本存在得localhost,输入以下内容(一个字母都不能错),如下图,输入完成后按esc,:wq保存并退出
master

检查配置
SSH 免密登录
ssh master 'hostname'
预期:输出 master,不需要账户密码
Java 环境
java -version
预期:显示 Java 1.8.0_202
Hadoop 安装
hadoop version
预期:显示 Hadoop 3.3.6
主机名解析
getent hosts master
预期:输出 master 对应的实际虚拟机 IP
HDFS 默认地址
hdfs getconf -confKey fs.defaultFS
预期:输出 hdfs://master:9000
HDFS 副本数
hdfs getconf -confKey dfs.replication
预期:输出 1
工作节点
cat /opt/hadoop-3.3.6/etc/hadoop/workers
预期:输出 master
初始化hadoop与启动hadoop
输入命令:
shell
# 进入 Hadoop 安装根目录。
cd /opt/hadoop-3.3.6
# 格式化 HDFS 的 NameNode。这是首次启动前必须执行一次的操作------它在 NameNode 的数据目录(dfs.namenode.name.dir,前面建过 /opt/hadoop-3.3.6/data)里初始化元数据(如 fsimage),相当于"格式化磁盘"。
# 重要:只在初次部署时执行。重复格式化会清空 NameNode 元数据,导致 DataNode 数据与元数据不匹配、集群起不来。若想重新格式化,需先清空所有节点的 data 目录。
hdfs namenode -format
# 启动 HDFS 的所有守护进程:在 master 上起 NameNode 和 SecondaryNameNode,在各节点起 DataNode。脚本内部通过 SSH 免密登录各节点拉起进程(这正是前面配 SSH 免密的原因)。
start-dfs.sh
# 启动 YARN 的所有守护进程:在 master 上起 ResourceManager,在节点上起 NodeManager。YARN 负责资源调度和作业运行。
start-yarn.sh
# (Java Virtual Machine Process Status Tool)列出当前用户运行的 Java 进程。用来验证 Hadoop 是否启动成功------正常应看到类似:
# NameNode
# SecondaryNameNode
# DataNode
# ResourceManager
# NodeManager
# Jps ← 这个是你刚执行 jps 命令本身的进程
jps
在本机(非虚拟机)电脑上打开浏览器输入如下内容:
shell
# 打开后应该能看到NameNode首页与1个LiveDataNode
http://<虚拟机的实际IP>:9870
http://<虚拟机的实际IP>:8088
注意:
如果本机打不开网页显示无法访问等,在xshell中输入以下命令:
shell
# 立即停止防火墙服务
sudo systemctl stop firewalld
# 禁止防火墙开机自启
sudo systemctl disable firewalld
正确查看结果如下图:


实践任务二:WordCount
**目标成果:**利用hadoop自带的wordcount统计txt文件中的单词出现次数。
在Xshell中输入以下命令:
shell
# 进入根目录
cd ~
# 创建有一些数据的txt文件,具体内容你可以自行修改。
printf 'Hello World\nHello Hadoop\nHello MapReduce\n' > ~/words.txt
# 在HDFS中创建input文件夹
hdfs dfs -mkdir -p /practice/input
# 上传txt文件到HDFS文件系统中
hdfs dfs -put ~/words.txt /practice/input/
# 检查是否上传成功
hdfs dfs -ls /practice/input
# 进入hadoop安装目录
cd /opt/hadoop-3.3.6
# hadoop jar
↓
# 运行 Hadoop 自带的 Java 示例程序
# wordcount
↓
# 选择词频统计功能
# /practice/input
↓
# 输入目录(HDFS)
# /practice/wordcount-run1
↓
# 输出目录(HDFS)
hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar wordcount /practice/input /practice/wordcount-run1
# 查看词频统计结果AW
hdfs dfs -cat /practice/wordcount-run1/part-r-00000
最后输出结果类似下图:

实践任务三:搭建分布式集群系统
目标成果: 三台 VM 分工运行,master 管理元数据和调度,slave01/slave02 保存数据并执行任务;9870 页面显示两个 Live DataNode;在集群上重复 G2 的小样本测试。
从本任务开始,后续基本上都是三台虚拟机需要一起运行。
前置准备
需要在新建一台slave01虚拟机,并且在这台虚拟机上安装jdk。
步骤和前面相同,在此不再复述。
创建完成并且安装好jdk后,关闭该虚拟机。
点击关闭客户机。

创建快照。

克隆虚拟机。





接下来启动slave02,并对slave02做一些配置修改。
首先使用xshell连接slave02(查看ip地址:ip -4 addr)。
输入以下命令:
shell
sudo hostnamectl set-hostname slave02
hostname
结果类似于下图:

确保主机名修改为slave02后,重启该虚拟机。
完成后,启动三台虚拟机:master、slave01、slave02,使用xshell连接上后,每台虚拟机输入以下命令:
shell
ip -r addr
记录三台虚拟机的ip地址,然后输入以下命令:
shell
sudo vi /etc/hosts
最后三台虚拟机类似于下图:

验证是否配置成功:
shell
ping -c 2 slave01
ping -c 2 slave02
结果类似于下图:

配置免密登录
在master上输入以下命令:
shell
ssh-copy-id user@slave01
ssh-copy-id user@slave02
验证是否成功:
shell
ssh -o BatchMode=yes user@slave01 hostname
ssh -o BatchMode=yes user@slave02 hostname
预期结果类似于下图:

配置一些便捷操作脚本
将以下脚本创建在master主机的/home/user/cluster-scripts目录下。
| 脚本 | 功能 |
|---|---|
01-distribute.sh |
向 slave01、slave02 分发并安装 Hadoop |
02-configure-sync.sh |
配置 master 并同步配置到两台 slave |
start-all.sh |
在 master 上启动集群并汇总 jps |
stop-all.sh |
在 master 上停止集群并汇总 jps |
03-decommission-master.sh |
将原有 master DataNode 安全退出 HDFS 集群 |
distribute.sh |
通用分发脚本。 |
01-distribute.sh:
shell
#!/usr/bin/env bash
set -Eeuo pipefail
NODES=(slave01 slave02)
HADOOP_TAR="$HOME/hadoop-3.3.6.tar.gz"
REMOTE_SCRIPT="/tmp/hadoop-g3-install-remote.sh"
[[ -r "$HADOOP_TAR" ]] || { echo "[失败] master 找不到 $HADOOP_TAR"; exit 1; }
# This helper is run by sudo on the remote machine; no heredoc is fed to a TTY.
TMP_HELPER=$(mktemp)
trap 'rm -f "$TMP_HELPER"' EXIT
cat > "$TMP_HELPER" <<'REMOTE'
#!/usr/bin/env bash
set -Eeuo pipefail
TARGET=/opt/hadoop-3.3.6
PKG="/home/${SUDO_USER}/hadoop-3.3.6.tar.gz"
[[ -f "$PKG" ]] || { echo '[失败] 从节点安装包不存在'; exit 1; }
[[ -x /opt/jdk1.8/bin/java ]] || { echo '[失败] /opt/jdk1.8/bin/java 不存在'; exit 1; }
if [[ -e "$TARGET" ]]; then
[[ -x "$TARGET/bin/hadoop" ]] || { echo '[失败] Hadoop 目录已存在但不完整,请人工检查'; exit 1; }
echo '[跳过] Hadoop 已安装,保留原目录'
else
echo '[进行中] 解压 Hadoop'
tar -xzf "$PKG" -C /opt
chown -R "$SUDO_USER:$(id -gn "$SUDO_USER")" "$TARGET"
mkdir -p "$TARGET/data" "$TARGET/logs"
echo '[成功] Hadoop 安装完成'
fi
printf '[Java] '
/opt/jdk1.8/bin/java -version 2>&1 | head -n 1
printf '[Hadoop] '
JAVA_HOME=/opt/jdk1.8 "$TARGET/bin/hadoop" version | head -n 1
REMOTE
for node in "${NODES[@]}"; do
echo "========== $node =========="
ssh -o BatchMode=yes -o ConnectTimeout=8 "$node" true || { echo "[失败] $node SSH 免密不可用"; exit 1; }
scp -q "$HADOOP_TAR" "$node:~/hadoop-3.3.6.tar.gz"
scp -q "$TMP_HELPER" "$node:$REMOTE_SCRIPT"
# sudo's password prompt is expected unless this account has cached sudo authorization.
ssh -tt "$node" "sudo bash '$REMOTE_SCRIPT'; result=\$?; rm -f '$REMOTE_SCRIPT'; exit \$result"
echo "[完成] $node"
done
echo '[完成] 两台从节点已检查'
02-configure-sync.sh:
she
#!/usr/bin/env bash
set -Eeuo pipefail
H=/opt/hadoop-3.3.6
C="$H/etc/hadoop"
[[ -d "$C" ]] || { echo '[错误] master 上没有 Hadoop 配置目录'; exit 1; }
BACKUP="$HOME/hadoop-config-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP"
cp -a "$C"/{core-site.xml,hdfs-site.xml,mapred-site.xml,yarn-site.xml,hadoop-env.sh,workers} "$BACKUP/"
echo "[备份] $BACKUP"
cat > "$C/core-site.xml" <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property><name>fs.defaultFS</name><value>hdfs://master:9000</value></property>
<property><name>hadoop.tmp.dir</name><value>/opt/hadoop-3.3.6/data/tmp</value></property>
</configuration>
XML
cat > "$C/hdfs-site.xml" <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property><name>dfs.replication</name><value>2</value></property>
<property><name>dfs.hosts.exclude</name><value>/opt/hadoop-3.3.6/etc/hadoop/dfs-exclude</value></property>
</configuration>
XML
cat > "$C/mapred-site.xml" <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property><name>mapreduce.framework.name</name><value>yarn</value></property>
</configuration>
XML
cat > "$C/yarn-site.xml" <<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property><name>yarn.nodemanager.aux-services</name><value>mapreduce_shuffle</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.resourcemanager.hostname</name><value>master</value></property>
</configuration>
XML
# Keep hadoop-env.sh's existing settings; append verified Java path only if needed.
grep -q '^export JAVA_HOME=/opt/jdk1.8$' "$C/hadoop-env.sh" || printf '\nexport JAVA_HOME=/opt/jdk1.8\n' >> "$C/hadoop-env.sh"
printf 'slave01\nslave02\n' > "$C/workers"
# Brand-new exclude list; do not accidentally keep master excluded on a rerun.
if [[ -s "$C/dfs-exclude" ]]; then
echo '[错误] dfs-exclude 非空。可能已开始退役;禁止覆盖。'
exit 1
fi
touch "$C/dfs-exclude"
for node in slave01 slave02; do
scp -q "$C"/{core-site.xml,hdfs-site.xml,mapred-site.xml,yarn-site.xml,hadoop-env.sh,workers,dfs-exclude} "$node:$C/"
echo "[成功] $node 配置同步"
done
printf '[配置] fs.defaultFS: '; "$H/bin/hdfs" getconf -confKey fs.defaultFS
printf '[配置] dfs.replication: '; "$H/bin/hdfs" getconf -confKey dfs.replication
printf '[配置] workers: '; paste -sd, "$C/workers"
echo '[提醒] 不要格式化 NameNode;旧 master DataNode 需要临时运行到退役完成'
03-decommission-master.sh:
shell
#!/usr/bin/env bash
set -Eeuo pipefail
H=/opt/hadoop-3.3.6
export JAVA_HOME=/opt/jdk1.8
EXCLUDE="$H/etc/hadoop/dfs-exclude"
[[ -f "$EXCLUDE" ]] || { echo '[错误] 先执行 02-configure-sync.sh'; exit 1; }
if [[ -s "$EXCLUDE" ]]; then echo '[提示] 已存在排除配置:'; cat "$EXCLUDE"; else
echo '[检查] 当前三个 DataNode 的状态(必须确认两个 slave 都已正常注册)'
"$H/bin/hdfs" dfsadmin -report | grep -E 'Live datanodes|Hostname:|Name:|Decommission Status:' || true
count=$("$H/bin/hdfs" dfsadmin -report | sed -n 's/^Live datanodes ([0-9]*):.*/\1/p' | head -1)
if [[ "$count" != "3" ]]; then
echo "[错误] 当前 Live DataNode 数不是 3(实际:${count:-未知}),不能开始退役"
exit 1
fi
echo '[确认] 9870 页面应有三个健康的 Live DataNode。'
read -r -p '输入 DECOMMISSION 才开始退役 master: ' ans
[[ "$ans" == 'DECOMMISSION' ]] || { echo '[取消]'; exit 1; }
echo master > "$EXCLUDE"
"$H/bin/hdfs" dfsadmin -refreshNodes
echo '[提交] master DataNode 退役请求;等待数据块复制完成'
fi
"$H/bin/hdfs" dfsadmin -report | grep -E 'Live datanodes|Dead datanodes|Hostname:|Name:|Decommission Status:' || true
echo '[重要] 仅当 master 显示 Decommissioned,且 fsck 不存在丢失块后,才手动停止 master DataNode。'
echo "[检查] $H/bin/hdfs fsck / -files -blocks"
echo "[停止] $H/bin/hdfs --daemon stop datanode"
start-all.sh:
shell
#!/usr/bin/env bash
H=/opt/hadoop-3.3.6
J=/opt/jdk1.8/bin/jps
SSH="ssh -o BatchMode=yes -o ConnectTimeout=5"
FAILED=0
start() {
local host="$1" role="$2" type="$3"
local output
output=$($SSH "$host" "$H/bin/$type --daemon start $role" 2>&1)
if [ $? -ne 0 ]; then
printf "[FAIL] %-8s %-22s %s\n" "$host" "$role" "$output"
FAILED=1
fi
}
check() {
local host="$1" expected="$2"
local result
result=$($SSH "$host" "$J" 2>/dev/null)
if [ $? -ne 0 ]; then
printf "[FAIL] %-8s SSH 连接失败\n" "$host"
FAILED=1
return
fi
local names
names=$(echo "$result" | awk '$2 != "Jps" {print $2}' | sort | paste -sd ',' -)
local ok=1
local role
for role in $expected; do
if ! echo "$result" | awk '{print $2}' | grep -qx "$role"; then
ok=0
fi
done
for role in $(echo "$result" | awk '$2 != "Jps" {print $2}'); do
case " $expected " in
*" $role "*) ;;
*) ok=0 ;;
esac
done
if [ "$ok" -eq 1 ]; then
printf "[OK] %-8s %s\n" "$host" "$names"
else
printf "[FAIL] %-8s %s\n" "$host" "${names:-无进程}"
FAILED=1
fi
}
echo "========== 启动 HDFS =========="
start master namenode hdfs
start master secondarynamenode hdfs
start slave01 datanode hdfs
start slave02 datanode hdfs
echo "========== 启动 YARN =========="
start master resourcemanager yarn
start slave01 nodemanager yarn
start slave02 nodemanager yarn
sleep 5
echo "========== JPS 进程检查 =========="
check master "NameNode SecondaryNameNode ResourceManager"
check slave01 "DataNode NodeManager"
check slave02 "DataNode NodeManager"
echo "========== 集群节点 =========="
DFS=$($H/bin/hdfs dfsadmin -report 2>/dev/null |
sed -n 's/^Live datanodes (\([0-9]*\)):/\1/p')
YARN=$($H/bin/yarn node -list 2>/dev/null |
sed -n 's/^Total Nodes:[[:space:]]*\([0-9]*\).*/\1/p')
echo "HDFS DataNode : ${DFS:-未知}/2"
echo "YARN NodeManager : ${YARN:-未知}/2"
if [ "$DFS" != "2" ] || [ "$YARN" != "2" ]; then
FAILED=1
fi
if [ "$FAILED" -eq 0 ]; then
echo "[SUCCESS] Hadoop 集群启动成功"
else
echo "[FAIL] 集群未达到预期状态,请检查节点日志"
exit 1
fi
stop-all.sh:
shell
#!/usr/bin/env bash
H=/opt/hadoop-3.3.6
J=/opt/jdk1.8/bin/jps
SSH="ssh -o BatchMode=yes -o ConnectTimeout=5"
FAILED=0
stop() {
local host="$1" role="$2" type="$3"
local output
output=$($SSH "$host" "$H/bin/$type --daemon stop $role" 2>&1)
if [ $? -ne 0 ]; then
printf "[WARN] %-8s %-22s %s\n" "$host" "$role" "$output"
fi
}
echo "========== 停止 YARN =========="
stop slave01 nodemanager yarn
stop slave02 nodemanager yarn
stop master resourcemanager yarn
echo "========== 停止 HDFS =========="
stop slave01 datanode hdfs
stop slave02 datanode hdfs
stop master secondarynamenode hdfs
stop master namenode hdfs
sleep 3
echo "========== JPS 进程检查 =========="
for node in master slave01 slave02; do
result=$($SSH "$node" "$J" 2>/dev/null)
if [ $? -ne 0 ]; then
printf "[FAIL] %-8s SSH 连接失败\n" "$node"
FAILED=1
continue
fi
names=$(echo "$result" |
awk '$2 != "Jps" {print $2}' |
sort | paste -sd ',' -)
if [ -z "$names" ]; then
printf "[OK] %-8s 已停止\n" "$node"
else
printf "[FAIL] %-8s 仍运行: %s\n" "$node" "$names"
FAILED=1
fi
done
if [ "$FAILED" -eq 0 ]; then
echo "[SUCCESS] Hadoop 集群已全部停止"
else
echo "[FAIL] 部分进程未停止"
exit 1
fi
创建完成后,在master上输入以下命令:
shell
cd ~/cluster-scripts
chmod +x *.sh
ls -1
使用脚本分发hadoop以及安装
在master主机上输入以下命令(注意必须是配置了免密的情况下):
shell
cd ~/cluster-scripts
./01-distribute.sh
脚本执行完成后输入以下命令检查:
shell
ssh slave01 '/opt/hadoop-3.3.6/bin/hadoop version | head -1'
ssh slave02 '/opt/hadoop-3.3.6/bin/hadoop version | head -1'
预期输出结果类似于下图:

配置分布式集群为一主两从配置
首先在master输入以下命令:
shell
stop-yarn.sh
stop-dfs.sh
jps
输出结果应该类似于下图:

接下来在master上使用脚本进行自动配置:
shell
cd ~/cluster-scripts
./02-configure-sync.sh
cat /opt/hadoop-3.3.6/etc/hadoop/workers
输出结果类似于下图:

启动集群
在master上输入以下命令:
shell
cd ~/cluster-scripts
./start-all.sh
预期输出类似于下图:

停止集群
在master上输入以下命令:
shell
cd ~/cluster-scripts
./stop-all.sh
预期输出类似于下图:

wordcount测试
启动集群后,输入以下命令:
shell
printf 'Hello World\nHello Hadoop\nHello MapReduce\n' > ~/words.txt
hdfs dfs -mkdir -p /practice/input
hdfs dfs -put ~/words.txt /practice/input/
hadoop jar /opt/hadoop-3.3.6/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.3.6.jar wordcount /practice/input /practice/wordcount-g3
hdfs dfs -cat /practice/wordcount-g3/part-r-00000
预期结果类似于下图:

用三台机器做出 ZooKeeper 仲裁服务
目标成果: 三台 ZooKeeper 里选出 1 个 Leader、2 个 Follower;客户端创建、读取、修改、删除一个 znode。
上传zookeeper
首先上传zookeeper到master主机的/home/user目录下,然后使用分发脚本:
shell
cd ~/cluster-scripts
./distribute.sh ~/apache-zookeeper-3.8.2-bin.tar.gz ~
安装zookeeper
在master主机上输入以下命令:
shell
sudo tar -xzf ~/apache-zookeeper-3.8.2-bin.tar.gz -C /opt
sudo mv /opt/apache-zookeeper-3.8.2-bin /opt/zookeeper-3.8.2
sudo chown -R user:user /opt/zookeeper-3.8.2
接着分别在slave01和slave02上执行以下命令:
shell
for node in slave01 slave02; do
ssh -t "$node" '
sudo tar -xzf ~/apache-zookeeper-3.8.2-bin.tar.gz -C /opt &&
sudo mv /opt/apache-zookeeper-3.8.2-bin /opt/zookeeper-3.8.2 &&
sudo chown -R user:user /opt/zookeeper-3.8.2
'
done
在master主机上验证是否安装成功:
shell
for node in master slave01 slave02; do
if ssh "$node" 'test -x /opt/zookeeper-3.8.2/bin/zkServer.sh'; then
echo "[OK] $node ZooKeeper 已安装"
else
echo "[FAIL] $node ZooKeeper 未安装"
fi
done
预期输出结果类似于下图:

配置zookeeper
在master上执行:
shell
cd /opt/zookeeper-3.8.2
cp conf/zoo_sample.cfg conf/zoo.cfg
vi conf/zoo.cfg
修改文件内容为如下内容(注意先删除全部内容,快速删除:esc模式下,:%d然后回车):
tex
tickTime=2000
initLimit=5
syncLimit=2
dataDir=/opt/zookeeper-3.8.2/data
dataLogDir=/opt/zookeeper-3.8.2/logs
clientPort=2181
server.1=master:2888:3888
server.2=slave01:2888:3888
server.3=slave02:2888:3888
分发配置文件:
在master上输入以下命令:
shell
cd ~/cluster-scripts
./distribute.sh /opt/zookeeper-3.8.2/conf/zoo.cfg ~/zk-config
shell
for node in slave01 slave02; do
ssh "$node" 'cp ~/zk-config/zoo.cfg /opt/zookeeper-3.8.2/conf/zoo.cfg' \
&& echo "[OK] $node 配置完成" \
|| echo "[FAIL] $node 配置失败"
done
预期输出结果类似于下图:

创建数据目录和 myid
在master上输入以下命令:
shell
for entry in master:1 slave01:2 slave02:3; do
node="${entry%%:*}"
id="${entry##*:}"
ssh "$node" "
mkdir -p /opt/zookeeper-3.8.2/data
mkdir -p /opt/zookeeper-3.8.2/logs
printf '%s\n' '$id' > /opt/zookeeper-3.8.2/data/myid
touch /opt/zookeeper-3.8.2/data/initialize
" && echo "[OK] $node myid=$id" \
|| echo "[FAIL] $node"
done
预期输出结果类似于下图:

检查所有配置
在master上输入以下命令:
shell
for node in master slave01 slave02; do
echo -n "$node : "
ssh "$node" 'cat /opt/zookeeper-3.8.2/data/myid'
done
预期输出结果类似于下图:

shell
for node in master slave01 slave02; do
echo -n "$node : "
ssh "$node" 'sha256sum /opt/zookeeper-3.8.2/conf/zoo.cfg' | awk '{print $1}'
done
预期输出结果类似于下图(三台机器hash值都相同):

创建zookeeper启动和停止脚本
在master上输入以下命令:
shell
vi ~/cluster-scripts/start-zk.sh
写入以下内容:
tex
#!/usr/bin/env bash
ZK=/opt/zookeeper-3.8.2
NODES=(master slave01 slave02)
echo "========== 启动 ZooKeeper =========="
for node in "${NODES[@]}"; do
if ssh -o BatchMode=yes "$node" \
"$ZK/bin/zkServer.sh start" >/dev/null 2>&1; then
echo "[OK] $node 启动命令执行成功"
else
echo "[FAIL] $node 启动失败"
fi
done
sleep 5
echo "========== 集群状态 =========="
leader=0
follower=0
failed=0
for node in "${NODES[@]}"; do
mode=$(ssh "$node" "$ZK/bin/zkServer.sh status" 2>/dev/null |
awk '/Mode:/ {print $2}')
case "$mode" in
leader)
echo "[OK] $node : Leader"
((leader+=1))
;;
follower)
echo "[OK] $node : Follower"
((follower+=1))
;;
*)
echo "[FAIL] $node : 未正常运行"
((failed+=1))
;;
esac
done
echo "=============================="
if [[ $leader -eq 1 && $follower -eq 2 && $failed -eq 0 ]]; then
echo "[SUCCESS] ZooKeeper 集群正常(1 Leader + 2 Follower)"
else
echo "[FAIL] ZooKeeper 集群状态异常"
exit 1
fi
esc后:wq后在输入以下命令:
shell
vi ~/cluster-scripts/stop-zk.sh
写入以下内容:
shell
#!/usr/bin/env bash
ZK=/opt/zookeeper-3.8.2
NODES=(master slave01 slave02)
failed=0
echo "========== 停止 ZooKeeper =========="
for node in "${NODES[@]}"; do
ssh "$node" "$ZK/bin/zkServer.sh stop" >/dev/null 2>&1
done
sleep 3
for node in "${NODES[@]}"; do
result=$(ssh "$node" '/opt/jdk1.8/bin/jps' 2>/dev/null)
if echo "$result" | grep -q 'QuorumPeerMain'; then
echo "[FAIL] $node : ZooKeeper 仍在运行"
((failed+=1))
else
echo "[OK] $node : 已停止"
fi
done
if [[ $failed -eq 0 ]]; then
echo "[SUCCESS] ZooKeeper 集群已全部停止"
else
echo "[FAIL] 部分节点未停止"
exit 1
fi
esc后:wq后在输入以下命令:
shell
cd ~/cluster-scripts
chmod +x start-zk.sh stop-zk.sh
./start-zk.sh
输出结果类似于下图:

验证zookeeper数据读写
在master下输入以下命令:
shell
# 启动 ZooKeeper 命令行客户端,连接到 master 主机的 2181 端口(ZooKeeper 默认客户端端口)。-server 指定要连接的服务器地址。连接成功后进入 [zk: master:2181(CONNECTED) ...] 交互提示符。
/opt/zookeeper-3.8.2/bin/zkCli.sh -server master:2181
# 在 ZooKeeper 里创建一个节点(znode) /practice,其存储的数据为 hello。ZooKeeper 的节点类似文件系统的"路径+内容"结构。
create /practice hello
# 读取节点 /practice 的数据,应输出 hello(附带版本、时间戳等元信息)。
get /practice
# 修改节点 /practice 的数据为 world,版本号会自增(ZooKeeper 用版本做并发控制)。
set /practice world
# 再次读取,此时应输出 world,验证修改成功。
get /practice
# 列出根节点 / 下的所有子节点(即当前有哪些顶级 znode)。正常会看到 zookeeper(ZooKeeper 自带的保留节点)和刚建的 practice。
ls /
# 删除节点 /practice(无子节点才能直接 delete;若有子节点需先删子节点或用 deleteall)。
delete /practice
# 退出客户端,断开连接。
quit
输出结果类似于下图:

这一步证明 ZooKeeper 不只是成功选举了 Leader,而且能够处理客户端请求。
验证跨节点数据一致性
在master输入以下命令:
shell
# 连接 master 节点上的 ZooKeeper 客户端(端口 2181)。
/opt/zookeeper-3.8.2/bin/zkCli.sh -server master:2181
# 在 ZooKeeper 里创建 znode /cluster-test,数据为 hello-zookeeper。注意这里数据带了双引号(引号是 shell 层剥掉的,实际存入的内容是 hello-zookeeper,不含引号)。
create /cluster-test "hello-zookeeper"
# 退出客户端。关键点:退出客户端不会删除数据,znode 保存在 ZooKeeper 集群里(默认持久节点 persistent)。
quit
# 这次连接的是另一个节点 slave02 的 ZooKeeper(端口 2181)。
/opt/zookeeper-3.8.2/bin/zkCli.sh -server slave02:2181
# 读取 /cluster-test。这是整段的核心验证:在 master 上写入的数据,从 slave02 这个不同节点也能读到,说明:
# master 和 slave02 组成了 ZooKeeper 集群(不是各自独立的单机);
# 集群内部已完成数据同步/复制,各节点数据一致;
# 无论连集群中哪台机器,看到的数据都一样------这正是 ZooKeeper 作为分布式协调服务的基本要求。
get /cluster-test
# 退出客户端。
quit
输出结果类似于下图:

在master上删除测试数据:
shell
/opt/zookeeper-3.8.2/bin/zkCli.sh -server master:2181
delete /cluster-test
quit
部署 HBase 2.4.17 分布式集群
开始之前需要确保Hadoop和zookeeper都已启动,输入以下命令:
shell
hdfs dfsadmin -report | grep 'Live datanodes'

shell
for node in master slave01 slave02; do
echo -n "$node: "
ssh "$node" '/opt/zookeeper-3.8.2/bin/zkServer.sh status' 2>/dev/null | grep 'Mode:'
done
应有一个 leader、两个 follower。

上传hbase-2.4.17-bin.tar.gz
将hbase-2.4.17-bin.tar.gz上传到master的/home/user目录下。
上传完成后在master输入以下命令:
shell
cd ~/cluster-scripts
./distribute.sh ~/hbase-2.4.17-bin.tar.gz ~
安装hbase
在master上输入以下命令:
shell
for node in master slave01 slave02; do
echo "========== $node =========="
ssh -t "$node" '
if [ -d /opt/hbase-2.4.17 ]; then
echo "[OK] HBase 已安装,跳过"
else
sudo tar -xzf ~/hbase-2.4.17-bin.tar.gz -C /opt &&
sudo chown -R user:user /opt/hbase-2.4.17 &&
echo "[OK] HBase 安装完成"
fi
'
done
输出结果类似于下图:

验证hbase安装
在master输入以下命令:
shell
for node in master slave01 slave02; do
echo -n "$node: "
ssh "$node" '/opt/hbase-2.4.17/bin/hbase version 2>&1 | grep -m 1 "HBase "'
done
输出结果类似于下图:

配置hbase
HBase 主要需要修改三个文件:
| 文件 | 作用 |
|---|---|
hbase-env.sh |
指定 JDK、Hadoop 环境,使用外部 ZooKeeper |
hbase-site.xml |
指定 HDFS 存储路径、集群模式、ZooKeeper 地址 |
regionservers |
指定运行 RegionServer 的节点 |
修改 hbase-env.sh,在 master 执行:
vi /opt/hbase-2.4.17/conf/hbase-env.sh
在文件末尾添加:
export JAVA_HOME=/opt/jdk1.8
export HADOOP_HOME=/opt/hadoop-3.3.6
export HADOOP_CONF_DIR=/opt/hadoop-3.3.6/etc/hadoop
export HBASE_MANAGES_ZK=false
修改 hbase-site.xml,在 master 执行:
vi /opt/hbase-2.4.17/conf/hbase-site.xml
将配置整理为:
tex
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://master:9000/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>master,slave01,slave02</value>
</property>
<property>
<name>hbase.zookeeper.property.clientPort</name>
<value>2181</value>
</property>
<property>
<name>hbase.wal.provider</name>
<value>filesystem</value>
</property>
</configuration>
\
修改 regionservers,在 master 执行:
shell
printf 'slave01\nslave02\n' > /opt/hbase-2.4.17/conf/regionservers
cat /opt/hbase-2.4.17/conf/regionservers

分发配置
先将三个配置文件放到 master 的临时目录:
shell
mkdir -p ~/hbase-config
cp /opt/hbase-2.4.17/conf/hbase-env.sh ~/hbase-config/
cp /opt/hbase-2.4.17/conf/hbase-site.xml ~/hbase-config/
cp /opt/hbase-2.4.17/conf/regionservers ~/hbase-config/
通过通用脚本分发整个文件夹:
shell
cd ~/cluster-scripts
./distribute.sh ~/hbase-config ~
然后把配置复制到两台 slave 的 HBase 目录:
shell
for node in slave01 slave02; do
ssh "$node" '
cp ~/hbase-config/* /opt/hbase-2.4.17/conf/
' && echo "[OK] $node 配置同步完成" \
|| echo "[FAIL] $node 配置同步失败"
done
检查配置是否一致:
shell
for node in master slave01 slave02; do
echo -n "$node: "
ssh "$node" \
'sha256sum /opt/hbase-2.4.17/conf/hbase-site.xml' |
awk '{print $1}'
done
三台 SHA256 值应该相同。
再检查 RegionServer 列表:
for node in master slave01 slave02; do
echo "=== $node ==="
ssh "$node" 'cat /opt/hbase-2.4.17/conf/regionservers'
done
每台都应该是:
slave01
slave02
创建hbase启动与停止脚本
在 master 上执行:
shell
vi ~/cluster-scripts/start-hbase.sh
写入以下代码:
shell
#!/usr/bin/env bash
HBASE=/opt/hbase-2.4.17
JAVA=/opt/jdk1.8/bin/jps
FAILED=0
echo "========== 启动 HBase =========="
# 由 master 自动启动 HMaster 和两个 RegionServer
if "$HBASE/bin/start-hbase.sh" >/tmp/hbase-start.log 2>&1; then
echo "[OK] HBase 启动命令已执行"
else
echo "[FAIL] HBase 启动命令失败"
tail -n 8 /tmp/hbase-start.log
exit 1
fi
sleep 8
echo "========== JPS 进程检查 =========="
check() {
local node="$1"
local expected="$2"
local output
output=$(ssh -o BatchMode=yes "$node" "$JAVA" 2>/dev/null)
if echo "$output" | awk '{print $2}' | grep -qx "$expected"; then
printf "[OK] %-8s %s\n" "$node" "$expected"
else
printf "[FAIL] %-8s 缺少 %s\n" "$node" "$expected"
FAILED=1
fi
}
check master HMaster
check slave01 HRegionServer
check slave02 HRegionServer
if [[ "$FAILED" -eq 0 ]]; then
echo "[SUCCESS] HBase 进程启动成功"
else
echo "[FAIL] HBase 部分进程未启动"
exit 1
fi
在 master 上执行:
shell
vi ~/cluster-scripts/stop-hbase.sh
写入以下代码:
shell
#!/usr/bin/env bash
HBASE=/opt/hbase-2.4.17
JAVA=/opt/jdk1.8/bin/jps
FAILED=0
echo "========== 停止 HBase =========="
if "$HBASE/bin/stop-hbase.sh" >/tmp/hbase-stop.log 2>&1; then
echo "[OK] HBase 停止命令已执行"
else
echo "[WARN] HBase 停止命令异常"
tail -n 8 /tmp/hbase-stop.log
fi
sleep 5
echo "========== JPS 进程检查 =========="
for node in master slave01 slave02; do
output=$(ssh -o BatchMode=yes "$node" "$JAVA" 2>/dev/null)
names=$(echo "$output" | awk '$2=="HMaster" || $2=="HRegionServer" {print $2}')
if [[ -z "$names" ]]; then
printf "[OK] %-8s HBase 已停止\n" "$node"
else
printf "[FAIL] %-8s 仍运行: %s\n" "$node" "$names"
FAILED=1
fi
done
if [[ "$FAILED" -eq 0 ]]; then
echo "[SUCCESS] HBase 集群已停止"
else
echo "[FAIL] 部分 HBase 进程仍在运行"
exit 1
fi
启动hbase集群
在master上执行:
shell
cd ~/cluster-scripts
chmod +x start-hbase.sh stop-hbase.sh
./start-hbase.sh

hbase数据读写验证
在master中执行:
shell
/opt/hbase-2.4.17/bin/hbase shell
1. 创建表
create 'practice', 'info'
2. 插入数据
put 'practice', 'row1', 'info:name', 'alice'
put 'practice', 'row1', 'info:course', 'distributed-systems'
put 'practice', 'row2', 'info:name', 'bob'
put 'practice', 'row2', 'info:course', 'hbase'
3. 查询单条记录
get 'practice', 'row1'
预期包含:
info:course distributed-systems
info:name alice
4. 扫描整张表
scan 'practice'
应能看到 row1、row2 两条记录。
5. 统计记录数量
count 'practice'
预期:
2 row(s)
6. 检查表是否正常启用
is_enabled 'practice'
预期:
true
完成后,可以再次执行:
status 'simple'
quit
至此,HBase 部署和基础数据读写实验已经完成。
Kafka 2.0.0 三节点分布式集群部署
上传kafka_2.11-2.0.0.tgz
将kafka_2.11-2.0.0.tgz上传到master的/home/user目录下。
上传完成后在master输入以下命令:
shell
cd ~/cluster-scripts
./distribute.sh ~/kafka_2.11-2.0.0.tgz ~
安装kafka
在master执行:
shell
for node in master slave01 slave02; do
echo "========== $node =========="
ssh -t "$node" '
if [ -d /opt/kafka_2.11-2.0.0 ]; then
echo "[OK] Kafka 已安装"
else
sudo tar -xzf ~/kafka_2.11-2.0.0.tgz -C /opt &&
sudo chown -R user:user /opt/kafka_2.11-2.0.0 &&
echo "[OK] Kafka 安装完成"
fi
'
done
验证安装
shell
for node in master slave01 slave02; do
echo -n "$node: "
ssh "$node" 'test -x /opt/kafka_2.11-2.0.0/bin/kafka-server-start.sh' \
&& echo '[OK] Kafka 已安装' \
|| echo '[FAIL] Kafka 未安装'
done

配置 Kafka 集群
Kafka 使用:
/opt/kafka_2.11-2.0.0/config/server.properties
在master上执行:
shell
for node in master slave01 slave02; do
case "$node" in
master) id=0 ;;
slave01) id=1 ;;
slave02) id=2 ;;
esac
ssh "$node" "mkdir -p /opt/kafka_2.11-2.0.0/data"
ssh "$node" "cat > /opt/kafka_2.11-2.0.0/config/server.properties" <<EOF
broker.id=$id
listeners=PLAINTEXT://0.0.0.0:9092
advertised.listeners=PLAINTEXT://$node:9092
log.dirs=/opt/kafka_2.11-2.0.0/data
num.network.threads=3
num.io.threads=8
num.partitions=3
zookeeper.connect=master:2181,slave01:2181,slave02:2181/kafka
zookeeper.connection.timeout.ms=18000
default.replication.factor=3
offsets.topic.replication.factor=3
transaction.state.log.replication.factor=3
transaction.state.log.min.isr=2
min.insync.replicas=2
log.retention.hours=168
log.segment.bytes=1073741824
delete.topic.enable=true
auto.create.topics.enable=false
EOF
echo "[OK] $node 配置已写入(broker.id=$id)"
done
这里有几点需要了解:
broker.id:三台分别为0、1、2,不能重复。advertised.listeners:告知客户端该 Broker 的访问地址。log.dirs:Kafka 消息日志存储位置,不是 HBase 的日志目录。zookeeper.connect:使用现有 ZooKeeper 集群,/kafka用于隔离 Kafka 元数据。default.replication.factor=3:默认使用三个副本,便于后续容错实验。
检查配置,在master上执行:
shell
for node in master slave01 slave02; do
echo "========== $node =========="
ssh "$node" "grep -E '^(broker.id|advertised.listeners|zookeeper.connect|log.dirs)=' /opt/kafka_2.11-2.0.0/config/server.properties"
done
三台的 ZooKeeper 地址都应相同。

准备 ZooKeeper 中的 Kafka 路径
在 master 执行:
shell
/opt/zookeeper-3.8.2/bin/zkCli.sh -server master:2181
进入 ZooKeeper 客户端后:
shell
create /kafka ""
ls /
quit
ls / 的结果应包含 kafka。
如果提示 Node already exists,说明节点已经存在,可以继续使用,不要删除它。

编写 Kafka 集群启停脚本
创建 start-kafka.sh
在 master 执行:
shell
vi ~/cluster-scripts/start-kafka.sh
写入:
shell
#!/usr/bin/env bash
KAFKA=/opt/kafka_2.11-2.0.0
FAILED=0
echo "========== 启动 Kafka =========="
for node in master slave01 slave02; do
if ssh -o BatchMode=yes "$node" \
"export JAVA_HOME=/opt/jdk1.8;
export KAFKA_HEAP_OPTS='-Xms256m -Xmx512m';
$KAFKA/bin/kafka-server-start.sh -daemon $KAFKA/config/server.properties" \
>/dev/null 2>&1; then
echo "[OK] $node 启动命令执行成功"
else
echo "[FAIL] $node 启动命令失败"
FAILED=1
fi
done
sleep 8
echo "========== Kafka 进程检查 =========="
for node in master slave01 slave02; do
if ssh -o BatchMode=yes "$node" \
"/opt/jdk1.8/bin/jps -l" 2>/dev/null |
grep -q 'kafka.Kafka'; then
echo "[OK] $node Broker 进程运行中"
else
echo "[FAIL] $node Broker 未运行"
FAILED=1
fi
done
if [ "$FAILED" -eq 0 ]; then
echo "[SUCCESS] Kafka 三节点进程启动成功"
else
echo "[FAIL] Kafka 集群启动异常"
exit 1
fi
这个脚本会分别启动三台 Kafka Broker,并检查 JVM 进程。
考虑到内存有限,脚本将 Kafka 堆内存设置为最大 512 MB,减少它与 Hadoop、ZooKeeper、HBase 同时运行时的内存竞争。
2. 创建 stop-kafka.sh
在 master 执行:
shell
vi ~/cluster-scripts/stop-kafka.sh
写入:
shell
#!/usr/bin/env bash
set -u
NODES=(master slave01 slave02)
JAVA_JPS=/opt/jdk1.8/bin/jps
FAILED=0
remote() {
timeout -k 2 10 ssh -o BatchMode=yes -o ConnectTimeout=5 "$1" "$2"
}
pids_on() {
remote "$1" "$JAVA_JPS -l | awk '\$2 == \"kafka.Kafka\" {print \$1}'"
}
echo '========== 停止 Kafka =========='
for node in "${NODES[@]}"; do
if ! pids=$(pids_on "$node" 2>&1); then
echo "[FAIL] $node 无法检查 Broker: $pids"
FAILED=1
continue
fi
if [[ -z "$pids" ]]; then
echo "[OK] $node 原本就已停止"
continue
fi
if remote "$node" "kill $pids" >/dev/null 2>&1; then
echo "[OK] $node 已向 Broker 发送 SIGTERM (PID: $pids)"
else
echo "[FAIL] $node 无法向 Broker 发送 SIGTERM"
FAILED=1
fi
done
echo '========== 等待 Broker 退出 =========='
# Kafka 有时需要时间关闭日志与网络线程;最多等待约 45 秒。
for ((attempt=1;attempt<=15;attempt++)); do
remaining=0
for node in "${NODES[@]}"; do
if ! pids=$(pids_on "$node" 2>/dev/null); then
remaining=1
continue
fi
[[ -z "$pids" ]] || remaining=1
done
((remaining == 0)) && break
sleep 3
done
for node in "${NODES[@]}"; do
if ! pids=$(pids_on "$node" 2>&1); then
echo "[FAIL] $node 无法验证状态: $pids"
FAILED=1
elif [[ -n "$pids" ]]; then
echo "[FAIL] $node Broker 仍在运行 (PID: $pids)"
FAILED=1
else
echo "[OK] $node Broker 已停止"
fi
done
if (( FAILED == 0 )); then
echo '[SUCCESS] Kafka 集群已全部停止'
else
echo '[FAIL] Kafka 未完全停止,请检查对应节点的日志和 PID'
exit 1
fi
授权:
shell
cd ~/cluster-scripts
chmod +x start-kafka.sh stop-kafka.sh
启动kafka
先确认 ZooKeeper 三节点仍在运行。 Kafka 2.0.0 启动时依赖 ZooKeeper。
然后在 master 执行:
shell
cd ~/cluster-scripts
./start-kafka.sh
预期输出类似于下图:

Kafka 集群功能验证
1. 检查三个 Broker 是否注册
在 master 运行:
shell
printf 'ls /kafka/brokers/ids\nquit\n' | \
/opt/zookeeper-3.8.2/bin/zkCli.sh -server master:2181
预期看到:
shell
[0, 1, 2]

这表示三台 Broker 都成功注册到了 ZooKeeper。
2. 创建 Topic
Kafka 的 Topic 可以理解为一个消息主题,生产者将消息发送到 Topic,消费者从中读取消息。
执行:
shell
/opt/kafka_2.11-2.0.0/bin/kafka-topics.sh \
--create \
--zookeeper master:2181,slave01:2181,slave02:2181/kafka \
--replication-factor 3 \
--partitions 3 \
--topic g6-test
预期:
shell
Created topic "g6-test".
查看 Topic:
shell
/opt/kafka_2.11-2.0.0/bin/kafka-topics.sh \
--describe \
--zookeeper master:2181,slave01:2181,slave02:2181/kafka \
--topic g6-test
预期每个分区的 ReplicationFactor 为 3,Replicas 和 Isr 都包含 0,1,2(顺序可能不同)。
3. 生产消息
执行:
shell
printf 'hello kafka\nmessage from master\nG6 success\n' | \
/opt/kafka_2.11-2.0.0/bin/kafka-console-producer.sh \
--broker-list master:9092 \
--topic g6-test
4. 消费消息
执行:
shell
/opt/kafka_2.11-2.0.0/bin/kafka-console-consumer.sh \
--bootstrap-server slave01:9092 \
--topic g6-test \
--from-beginning \
--max-messages 3 \
--timeout-ms 15000
预期能够看到:
shell
hello kafka
message from master
G6 success
Processed a total of 3 messages
消息的显示顺序可能有所不同,因为 Topic 有三个分区。
这样就完成了从 master 生产消息、通过 slave01 连接消费的测试。
创建统一集群管理sh脚本
在master执行:
shell
vi ~/cluster-scripts/cluster.sh
输入以下内容:
shell
#!/usr/bin/env bash
# G0-G6 unified cluster controller. Run on master as user.
set -u
DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
HBASE=/opt/hbase-2.4.17
KAFKA=/opt/kafka_2.11-2.0.0
ZK=/opt/zookeeper-3.8.2
HADOOP=/opt/hadoop-3.3.6
NODES=(master slave01 slave02)
ERRORS=0
ok() { printf '[OK] %s\n' "$*"; }
fail() { printf '[FAIL] %s\n' "$*"; ERRORS=$((ERRORS+1)); }
run_script() {
local label="$1" file="$2" seconds="$3"
echo "========== ${label} =========="
if [[ ! -f "$DIR/$file" ]]; then
fail "未找到 $DIR/$file"
return 1
fi
if timeout -k 5 "${seconds}s" bash "$DIR/$file"; then
ok "${label} 命令执行完毕"
else
fail "${label} 失败或超过 ${seconds}s,请检查原脚本及日志"
return 1
fi
}
remote_jps() {
timeout -k 2 12 ssh -o BatchMode=yes -o ConnectTimeout=5 \
"$1" /opt/jdk1.8/bin/jps -l 2>/dev/null
}
has_jvm() {
local listing="$1" class="$2"
printf '%s\n' "$listing" | awk '{print $2}' | grep -Eq "(^|[.])${class//./[.]}$"
}
check_status() {
local node listing expected missing
local zk_leaders=0 zk_followers=0 result zk_mode
local hdfs_result yarn_result hbase_result kafka_result
ERRORS=0
echo '========== 节点 JVM 进程 =========='
for node in "${NODES[@]}"; do
if ! listing=$(remote_jps "$node"); then
fail "$node SSH/JPS 连接失败"
continue
fi
case "$node" in
master)
expected=(NameNode SecondaryNameNode ResourceManager HMaster QuorumPeerMain kafka.Kafka)
;;
slave01)
expected=(DataNode NodeManager HRegionServer QuorumPeerMain kafka.Kafka)
;;
slave02)
expected=(DataNode NodeManager HRegionServer QuorumPeerMain kafka.Kafka)
;;
esac
missing=''
for class in "${expected[@]}"; do
if ! has_jvm "$listing" "$class"; then missing+=" $class"; fi
done
if [[ -z "$missing" ]]; then
ok "$node 全部预期进程正常"
else
fail "$node 缺少:$missing"
fi
done
echo '========== 服务就绪检查 =========='
hdfs_result=$(timeout -k 2 20 "$HADOOP/bin/hdfs" dfsadmin -report 2>&1) || hdfs_result=''
if grep -Eq 'Live datanodes \(2\)' <<< "$hdfs_result"; then
ok 'HDFS 2/2 Live DataNodes'
else
fail 'HDFS 未确认 2 个 Live DataNode'
fi
yarn_result=$(timeout -k 2 20 "$HADOOP/bin/yarn" node -list 2>&1) || yarn_result=''
if grep -Eq 'Total Nodes:[[:space:]]*2' <<< "$yarn_result" && \
[[ $(grep -Ec '[[:space:]]RUNNING([[:space:]]|$)' <<< "$yarn_result") -ge 2 ]]; then
ok 'YARN 2/2 Running NodeManagers'
else
fail 'YARN 未确认 2 个 Running NodeManager'
fi
for node in "${NODES[@]}"; do
result=$(timeout -k 2 15 ssh -o BatchMode=yes -o ConnectTimeout=5 \
"$node" "$ZK/bin/zkServer.sh" status 2>&1) || result=''
zk_mode=$(sed -n 's/.*Mode: \(leader\|follower\).*/\1/p' <<< "$result" | tail -n 1)
case "$zk_mode" in
leader) zk_leaders=$((zk_leaders+1)); ok "$node ZooKeeper Leader" ;;
follower) zk_followers=$((zk_followers+1)); ok "$node ZooKeeper Follower" ;;
*) fail "$node ZooKeeper 未就绪" ;;
esac
done
if [[ $zk_leaders -ne 1 || $zk_followers -ne 2 ]]; then
fail "ZooKeeper 角色异常: Leader=$zk_leaders, Follower=$zk_followers"
fi
# Check the reported cluster state. HBase Shell can return code 1 on quit/exit.
# A timeout remains a failure even if some status text was printed.
local hbase_rc=0 kafka_rc=0
hbase_result=$(printf "status 'simple'\nquit\n" | \
timeout -k 3 120 "$HBASE/bin/hbase" shell -n 2>&1) || hbase_rc=$?
if [[ $hbase_rc -ne 124 && $hbase_rc -ne 137 ]] && \
grep -Eqi 'active master:[[:space:]]*master:16000' <<< "$hbase_result" && \
grep -Eq '2 live servers' <<< "$hbase_result" && \
grep -Eq '0 dead servers' <<< "$hbase_result"; then
ok 'HBase Active Master + 2 RegionServers 就绪'
else
fail "HBase 状态检查未通过(命令退出码: $hbase_rc)"
printf '%s\n' "$hbase_result" | tail -n 15 | sed 's/^/ | /'
fi
# Topic replication and ISR: compare member sets, irrespective of order.
kafka_result=$(timeout -k 3 90 "$KAFKA/bin/kafka-topics.sh" --describe \
--zookeeper 'master:2181,slave01:2181,slave02:2181/kafka' \
--topic g6-test 2>&1) || kafka_rc=$?
if [[ $kafka_rc -eq 0 ]] && awk '
/Partition:[[:space:]]*[0-9]+/ {
rep=$0; sub(/^.*Replicas:[[:space:]]*/, "", rep); sub(/[[:space:]]+Isr:.*$/, "", rep)
isr=$0; sub(/^.*Isr:[[:space:]]*/, "", isr); sub(/[[:space:]].*$/, "", isr)
nr=split(rep,r,","); ns=split(isr,t,",")
if (nr!=3 || ns!=3) bad=1
for (i=1;i<=nr;i++) {
found=0
for (j=1;j<=ns;j++) if (r[i]==t[j]) found=1
if (!found) bad=1
}
n++
}
END {if (n!=3 || bad) exit 1}
' <<< "$kafka_result"; then
ok 'Kafka g6-test: 3 分区、每分区 3 个同步副本'
else
fail "Kafka g6-test 副本/ISR 未确认(命令退出码: $kafka_rc)"
printf '%s\n' "$kafka_result" | tail -n 15 | sed 's/^/ | /'
fi
echo '================================='
if (( ERRORS == 0 )); then
echo '[SUCCESS] 全部分布式组件状态正常'
else
echo "[FAIL] 检查发现 $ERRORS 项异常"
return 1
fi
}
case "${1:-}" in
start)
run_script '启动 Hadoop' start-all.sh 120 || exit 1
run_script '启动 ZooKeeper' start-zk.sh 90 || exit 1
run_script '启动 HBase' start-hbase.sh 150 || exit 1
run_script '启动 Kafka' start-kafka.sh 120 || exit 1
check_status
;;
stop)
run_script '停止 Kafka' stop-kafka.sh 90 || true
run_script '停止 HBase' stop-hbase.sh 120 || true
run_script '停止 ZooKeeper' stop-zk.sh 90 || true
run_script '停止 Hadoop' stop-all.sh 120 || true
(( ERRORS == 0 )) && echo '[SUCCESS] 停止命令全部执行完毕(请按需检查残留进程)' || exit 1
;;
status) check_status ;;
restart)
bash "$0" stop || exit 1
bash "$0" start
;;
*)
echo '用法: ./cluster.sh {start|stop|status|restart}'
exit 2
;;
esac
在执行:
shell
cd ~/cluster-scripts
chmod +x cluster.sh
使用教程(都是在~/cluster-scripts目录下执行):
| 命令 | 功能 |
|---|---|
./cluster.sh start |
按依赖顺序启动全部组件 |
./cluster.sh stop |
逆序停止全部组件 |
./cluster.sh restart |
重启整个集群 |
./cluster.sh status |
检查三台机器的进程和关键服务 |
启动顺序是 Hadoop → ZooKeeper → HBase → Kafka;停止顺序相反。
/ | /'
fi
Topic replication and ISR: compare member sets, irrespective of order.
kafka_result= ( t i m e o u t − k 390 " (timeout -k 3 90 " (timeout−k390"KAFKA/bin/kafka-topics.sh" --describe
--zookeeper 'master:2181,slave01:2181,slave02:2181/kafka'
--topic g6-test 2>&1) || kafka_rc=$?
if \[ $kafka_rc -eq 0 ] && awk '
/Partition:\[:space:]*0-9+/ {
rep= 0 ; s u b ( / . ∗ R e p l i c a s : \[ : s p a c e : ] ∗ / , " " , r e p ) ; s u b ( / \[ : s p a c e : ] + I s r : . ∗ 0; sub(/^.*Replicas:\[:space:]*/, "", rep); sub(/\[:space:]+Isr:.* 0;sub(/.∗Replicas:\[:space:]∗/,"",rep);sub(/\[:space:]+Isr:.∗/, "", rep)
isr= 0 ; s u b ( / . ∗ I s r : \[ : s p a c e : ] ∗ / , " " , i s r ) ; s u b ( / \[ : s p a c e : ] . ∗ 0; sub(/^.*Isr:\[:space:]*/, "", isr); sub(/\[:space:].* 0;sub(/.∗Isr:\[:space:]∗/,"",isr);sub(/\[:space:].∗/, "", isr)
nr=split(rep,r,","); ns=split(isr,t,",")
if (nr!=3 || ns!=3) bad=1
for (i=1;i<=nr;i++) {
found=0
for (j=1;j<=ns;j++) if (ri==tj) found=1
if (!found) bad=1
}
n++
}
END {if (n!=3 || bad) exit 1}
' <<< "$kafka_result"; then
ok 'Kafka g6-test: 3 分区、每分区 3 个同步副本'
else
fail "Kafka g6-test 副本/ISR 未确认(命令退出码: k a f k a r c ) " p r i n t f ′ kafka_rc)" printf '%s\n' " kafkarc)"printf′kafka_result" | tail -n 15 | sed 's/^/ | /'
fi
echo '================================='
if (( ERRORS == 0 )); then
echo 'SUCCESS 全部分布式组件状态正常'
else
echo "FAIL 检查发现 $ERRORS 项异常"
return 1
fi
}
case "${1:-}" in
start)
run_script '启动 Hadoop' start-all.sh 120 || exit 1
run_script '启动 ZooKeeper' start-zk.sh 90 || exit 1
run_script '启动 HBase' start-hbase.sh 150 || exit 1
run_script '启动 Kafka' start-kafka.sh 120 || exit 1
check_status
;;
stop)
run_script '停止 Kafka' stop-kafka.sh 90 || true
run_script '停止 HBase' stop-hbase.sh 120 || true
run_script '停止 ZooKeeper' stop-zk.sh 90 || true
run_script '停止 Hadoop' stop-all.sh 120 || true
(( ERRORS == 0 )) && echo 'SUCCESS 停止命令全部执行完毕(请按需检查残留进程)' || exit 1
;;
status) check_status ;;
restart)
bash "$0" stop || exit 1
bash "$0" start
;;
*)
echo '用法: ./cluster.sh {start|stop|status|restart}'
exit 2
;;
esac
在执行:
```shell
cd ~/cluster-scripts
chmod +x cluster.sh
使用教程(都是在~/cluster-scripts目录下执行):
| 命令 | 功能 |
|---|---|
./cluster.sh start |
按依赖顺序启动全部组件 |
./cluster.sh stop |
逆序停止全部组件 |
./cluster.sh restart |
重启整个集群 |
./cluster.sh status |
检查三台机器的进程和关键服务 |
启动顺序是 Hadoop → ZooKeeper → HBase → Kafka;停止顺序相反。