Hadoop+zookeeper+HBase+Kafka集群搭建全流程

本文档仅用于流程参考。

如有错误请联系我进行修改。

行文比较粗糙,还请见谅。

请善用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.exe
  • Xftp-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;停止顺序相反。

相关推荐
后台模板学习5 小时前
Kafka 与 RabbitMQ 在用户认证与权限管理场景
kafka·rabbitmq·linq
真上帝的左手7 小时前
12. 消息队列-Kafka进阶
分布式·中间件·kafka
千里达景区票务系统1 天前
消息队列选型对比:RabbitMQ vs RocketMQ vs Kafka
kafka·消息队列·rabbitmq·rocketmq
Sayai1 天前
Kafka 跨数据中心日志同步实战:MirrorMaker 2 选型对比 + Flink 中继任务落地(双中心互备与消费者组隔离避坑)
大数据·运维·分布式·flink·kafka·消息队列·高可用
Gl�ria1 天前
Hadoop 速刷:HDFS / MR / YARN / Spark / Hive
hadoop·hdfs·mr
FserSuN1 天前
Hive 分区表存储与查询优化学习总结
hive·hadoop·学习
jianjinwssy1 天前
从零设计一个 Agent 安全沙箱:Docker 隔离、三级风控与 Kafka 审计
人工智能·docker·kafka
一条泥憨鱼2 天前
【Kafka】零基础学习教程
java·分布式·学习·kafka
测试盐2 天前
spark作业日志定位过程
java·hadoop·spark