大数据服务完全分布式部署- Hadoop、hive(阿里云版)
阿里云
创建 VPC
专有网络VPC(Virtual Private Cloud)是云上安全隔离的虚拟网络环境,支持自定义网络配置、部署和访问云产品资源。
VPC提供了类似于传统数据中心的安全和可配置的私有网络空间,同时又具备云计算的弹性和可扩展性。用户能够完全掌控自己的专有网络,包括选择自己的IP地址范围、创建交换机、配置路由表和网关等。


安全组
安全组是一种虚拟防火墙,能够控制ECS实例的出入站流量。您可以将具有相同安全需求并相互信任的ECS实例放入相同的安全组,以划分安全域,保障云上资源的安全。本文介绍安全组的功能、分类、最佳实践和操作指引等。

下面的规则建议全删然后只加自己的或者公司的专有网络
购买云服务器 ECS









按年付费
按量付费
开始配置工作
这里推荐的ssh终端工具是termius,全平台手机平板都能用,而且性能好、颜值高。(之前用finalshell发现太占内存了)
ssh工具准备
创建密钥,一次就好。
导入之前生成的密钥后,点下右上角的勾即可。
然后就可以创建链接了,共三个节点所以需要设置三次。
连上三台机子第一件事先下载trash工具,每个机子都要下载,以后的工作尽量用trash来代替rm
sudo apt install trash-cli
具体配置
主机名映射
三个节点,每个节点内都要设置一次vim /etc/hosts。
vim /etc/hosts
加入下面代码
注意这里用私网ip,否则后面没法启动namenode
node1的私网IP node1 node1
node2的私网IP node2 node2
node3的私网IP node3 node3
光这样还不能直接ssh,还需要把密钥文件上传到每个节点上。
打开隐藏文件的显示
直接把左边的bigdata-key.pem给拖拽右边就行
改名改权限
mv bigdata-key.pem id_rsa
chmod 400 id_rsa

现在就能ssh到其他节点了,但是回来只能exit。
集群分发脚本(root用户免密登陆)
编写分发脚本
vim /usr/local/bin/xsync
#!/usr/bin/env bash
#
# 批量分发文件/目录到集群节点(保持原有绝对路径)
# 节点列表:node1 node2 node3
#
# 用法示例:
# xsync 文件1 文件2 文件3 ...
#
# 参数说明:
# 文件1 文件2 文件3 ...
# 一个或多个需要分发到集群所有节点的文件或目录。
# - 文件:将覆盖目标节点相同路径下的同名文件。
# - 目录:将整个目录及内容同步到目标节点相同路径下。
#
# 示例:
# xsync /etc/hosts /home/ecs-user/.bashrc /opt/module/jdk
# ↑ 分发 /etc/hosts、/home/ecs-user/.bashrc 两个文件 和 /opt/module/jdk 整个目录
#
# 注意事项:
# 1. 请提前配置好免密登录(当前节点到所有目标节点)。
# 2. 目录结构会保持一致,目标路径与源路径相同。
# 3. 同步时会覆盖目标节点上的同名文件/目录。
#
set -euo pipefail
# 节点列表
hosts=(node1 node2 node3)
# rsync 参数(可加 --progress 查看传输进度)
RSYNC_OPTS=(-av)
# 1) 参数校验
if [ $# -lt 1 ]; then
echo "用法: $(basename "$0") 文件1 [文件2 ...]"
exit 1
fi
# 2) 遍历节点
for host in "${hosts[@]}"; do
echo "========== 同步至:$host =========="
# 3) 遍历每个要同步的文件/目录
for file in "$@"; do
if [ -e "$file" ]; then
# 绝对父目录路径
pdir="$(cd -P "$(dirname "$file")" && pwd)"
# 文件或目录名称
fname="$(basename "$file")"
# 远端创建父目录
ssh "$host" "mkdir -p '$pdir'"
# 同步到远端
rsync "${RSYNC_OPTS[@]}" "$pdir/$fname" "$host:$pdir/"
echo "✓ 已同步:$pdir/$fname → $host:$pdir/"
else
echo "⚠ 跳过:$file 不存在"
fi
done
done
echo "✅ 全部同步完成"
给上权限
chmod 755 /usr/local/bin/xsync
测试并分发改脚本到其他节点
xsync /usr/local/bin/xsync

去其他节点检查下
ll /etc/loacl/bin/xsync

这就做好了,最后分发下刚才的密钥文件
xsync ~/.ssh/id_rsa

现在三个节点可以随意ssh其他节点了。
普通用户之间免密登陆
方案一
给出两个方案,下面是方案一,个人建议直接方案二
三个节点都需要
先新建普通用户 hadoop
useradd -m -d /home/hadoop -s /bin/bash -c "Hadoop 服务账户" hadoop
## 若需删除用户并同时删除主目录
## userdel -r hadoop
改密码
passwd hadoop
切换成hadoop用户
su - hadoop

ssh-keygen -t rsa -b 4096
# 删除私钥
# rm -f /home/hadoop/.ssh/id_rsa
# 删除公钥
# rm -f /home/hadoop/.ssh/id_rsa.pub
ssh-copy-id node1
ssh-copy-id node2
ssh-copy-id node3

如果没被拒绝,三个节点都新建用户、生成密钥并分发即可不用管后面的,但是出于方便后面的多用户管理,建议采用后面的方案。
方案二
若被拒绝了,则采用方案二
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/hadoop/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
hadoop@node1: Permission denied (publickey).
先exit退出来,用root身份操作。
这里直接做了一个脚本工具,后续若有新加用户,直接一步到位。
mkdir -p /root/scripts
vim /root/scripts/cluster_user_ssh.sh
#!/usr/bin/env bash
# 说明:
# - 在 node1 上以 root 执行(要求 root 已能免密到所有节点)
# - 仅使用 RSA(4096) 公钥免密,不开启密码登录
# - 统一主组管理:默认将新用户的主组设为 DEFAULT_GROUP(避免一人一组)
# - 子命令:
# create <username> # 创建用户并分发密钥,实现节点间免密(主组=DEFAULT_GROUP)
# status <username> # 查看各节点用户与密钥状态
# rotate <username> # 轮换该用户的密钥并在所有节点覆盖
# delete <username> [--purge] # 删除用户;--purge 连同家目录一起删除
#
# 使用示例:
# /root/scripts/cluster_user_ssh.sh create hadoop
# /root/scripts/cluster_user_ssh.sh status hadoop
# /root/scripts/cluster_user_ssh.sh rotate hadoop
# /root/scripts/cluster_user_ssh.sh delete hadoop --purge
#
set -euo pipefail
# ========= 节点清单:按需修改 =========
NODES=("node1" "node2" "node3")
# 本机在 NODES 中的逻辑名(用于判断是否复制私钥;按你的 /etc/hosts 对应)
LOCAL_NODE="${NODES[0]}"
# 统一主组(避免一人一组)
DEFAULT_GROUP="bigdata"
# 可选:给新用户添加的附加组(用逗号分隔,如需要 docker 等)
EXTRA_GROUPS="" # 例如:EXTRA_GROUPS="sudo,docker"
# ====================================
# ======== 基础函数 ========
die() { echo "[ERR] $*" >&2; exit 1; }
need_cmd() {
for c in "$@"; do
command -v "$c" >/dev/null 2>&1 || die "缺少命令: $c"
done
}
run_remote() {
local host="$1"; shift
ssh -o BatchMode=yes "root@${host}" "$@"
}
copy_to_remote() {
local src="$1" host="$2" dst="$3"
scp -q "$src" "root@${host}:${dst}"
}
usage() {
cat <<EOF
用法:
$0 create <username> # 创建用户(主组=${DEFAULT_GROUP})并分发 RSA(4096) 密钥,实现节点间免密
$0 status <username> # 查看各节点用户与密钥状态
$0 rotate <username> # 轮换该用户密钥并在所有节点覆盖
$0 delete <username> [--purge] # 删除用户;--purge 时连同家目录一起删除
说明:
- 本脚本不启用密码登录(PasswordAuthentication=no)。
- 外部 SSH 工具(Xshell/MobaXterm/FinalShell 等)登录该普通用户时,
请从任意节点下载 /home/<username>/.ssh/id_rsa 到本地并在工具中导入该私钥。
- 轮换密钥(rot ate):用于怀疑密钥泄漏、人员变动、或定期安全更新;它会生成新密钥并覆盖所有节点的私钥和 authorized_keys。
EOF
}
# ======== 依赖检查 ========
need_cmd ssh scp ssh-keygen adduser id awk grep sed printf getent
# ======== 路径函数 ========
user_home() { printf "/home/%s" "$1"; }
ssh_dir() { printf "%s/.ssh" "$(user_home "$1")"; }
pri_key() { printf "%s/id_rsa" "$(ssh_dir "$1")"; }
pub_key() { printf "%s.pub" "$(pri_key "$1")"; }
auth_keys() { printf "%s/authorized_keys" "$(ssh_dir "$1")"; }
# ======== 组相关 ========
ensure_group_local() {
local grp="$1"
if ! getent group "$grp" >/dev/null 2>&1; then
echo "[INFO] 本机创建组: ${grp}"
addgroup "$grp"
fi
}
ensure_group_remote() {
local host="$1" grp="$2"
run_remote "$host" "getent group '${grp}' >/dev/null 2>&1 || addgroup '${grp}'"
}
maybe_add_extra_groups_local() {
local user="$1"
if [[ -n "$EXTRA_GROUPS" ]]; then
usermod -a -G "$EXTRA_GROUPS" "$user" || true
fi
}
maybe_add_extra_groups_remote() {
local host="$1" user="$2"
if [[ -n "$EXTRA_GROUPS" ]]; then
run_remote "$host" "usermod -a -G '$EXTRA_GROUPS' '$user' || true"
fi
}
# ======== 子命令实现 ========
create_user_and_keys_local() {
local user="$1"
local home; home="$(user_home "$user")"
local sdir; sdir="$(ssh_dir "$user")"
local prik; prik="$(pri_key "$user")"
local pubk; pubk="$(pub_key "$user")"
local auth; auth="$(auth_keys "$user")"
ensure_group_local "$DEFAULT_GROUP"
if ! id -u "$user" >/dev/null 2>&1; then
echo "[INFO] 本机不存在用户 ${user},正在以主组 ${DEFAULT_GROUP} 创建..."
adduser --disabled-password --gecos "" --ingroup "$DEFAULT_GROUP" "$user"
maybe_add_extra_groups_local "$user"
else
# 已存在用户:如主组非 DEFAULT_GROUP,不强改;加入 bigdata 为附加组并提示
local cur_gid cur_grp
cur_gid="$(id -g "$user")"
cur_grp="$(getent group "$cur_gid" | cut -d: -f1 || true)"
if [[ "$cur_grp" != "$DEFAULT_GROUP" ]]; then
echo "[WARN] 用户 ${user} 的当前主组为 ${cur_grp},未强制改为 ${DEFAULT_GROUP}。"
echo " 已将其加入 ${DEFAULT_GROUP} 作为附加组(如需变更主组,可人工执行:usermod -g ${DEFAULT_GROUP} ${user})。"
usermod -a -G "$DEFAULT_GROUP" "$user" || true
fi
maybe_add_extra_groups_local "$user"
fi
install -d -m 700 -o "$user" -g "$DEFAULT_GROUP" "$sdir"
if [[ ! -f "$pubk" ]]; then
echo "[INFO] 生成 RSA(4096) 密钥..."
sudo -u "$user" ssh-keygen -t rsa -b 4096 -f "$prik" -N '' >/dev/null
else
echo "[INFO] 本机已有密钥,跳过生成"
fi
touch "$auth"
chown "$user:$DEFAULT_GROUP" "$auth"
chmod 600 "$auth"
if ! grep -q -F "$(cat "$pubk")" "$auth"; then
cat "$pubk" >> "$auth"
fi
}
distribute_pubkey_to_all() {
local user="$1"
local pubk; pubk="$(pub_key "$user")"
local sdir; sdir="$(ssh_dir "$user")"
local auth; auth="$(auth_keys "$user")"
echo "[STEP] 分发公钥到各节点 authorized_keys(统一主组:${DEFAULT_GROUP})"
for n in "${NODES[@]}"; do
echo "[INFO] 节点: ${n} -> 写入 authorized_keys"
ensure_group_remote "$n" "$DEFAULT_GROUP"
# 创建用户(如不存在),主组设为 DEFAULT_GROUP
run_remote "$n" "id -u $user >/dev/null 2>&1 || adduser --disabled-password --gecos '' --ingroup '$DEFAULT_GROUP' $user"
# 若已存在用户但主组不同,仅加入为附加组并告警(不强改主组)
run_remote "$n" "
CUR_GID=\$(id -g $user 2>/dev/null || true);
CUR_GRP=\$(getent group \"\$CUR_GID\" | cut -d: -f1 2>/dev/null || true);
if [ \"\$CUR_GRP\" != '$DEFAULT_GROUP' ]; then
echo '[WARN][$HOSTNAME] 用户 $user 主组为 '\$CUR_GRP',未改为 $DEFAULT_GROUP;加入为附加组';
usermod -a -G '$DEFAULT_GROUP' '$user' || true;
fi
"
# 附加组
if [[ -n "$EXTRA_GROUPS" ]]; then
maybe_add_extra_groups_remote "$n" "$user"
fi
# 分发公钥
copy_to_remote "$pubk" "$n" "/tmp/${user}_id.pub"
run_remote "$n" "
install -d -m 700 -o ${user} -g ${DEFAULT_GROUP} ${sdir} && \
touch ${auth} && \
chown ${user}:${DEFAULT_GROUP} ${auth} && \
chmod 600 ${auth} && \
grep -q -F \"\$(cat /tmp/${user}_id.pub)\" ${auth} || cat /tmp/${user}_id.pub >> ${auth} && \
rm -f /tmp/${user}_id.pub
"
done
}
distribute_private_key_to_others() {
local user="$1"
local prik; prik="$(pri_key "$user")"
local sdir; sdir="$(ssh_dir "$user")"
echo "[STEP] 分发私钥到其它节点(用于任意节点互登免密)"
for n in "${NODES[@]}"; do
if [[ "$n" != "$LOCAL_NODE" ]]; then
echo "[INFO] 复制私钥到 ${n}"
ensure_group_remote "$n" "$DEFAULT_GROUP"
run_remote "$n" "install -d -m 700 -o ${user} -g ${DEFAULT_GROUP} ${sdir}"
copy_to_remote "$prik" "$n" "$prik"
run_remote "$n" "chown ${user}:${DEFAULT_GROUP} ${prik} && chmod 600 ${prik}"
fi
done
}
cmd_create() {
local user="${1:-}"; [[ -z "$user" ]] && die "缺少用户名。用法:$0 create <username>"
echo "[STEP] 本机创建用户与密钥(主组=${DEFAULT_GROUP})"
create_user_and_keys_local "$user"
distribute_pubkey_to_all "$user"
distribute_private_key_to_others "$user"
echo
echo "[OK] 用户 ${user} 的 RSA(4096) 公钥免密已在所有节点就绪(主组=${DEFAULT_GROUP})。"
echo
echo "【验证步骤】"
echo "1) 在任意节点执行:"
echo " su - ${user}"
echo "2) 互登测试(应免密直连):"
echo " ssh ${NODES[1]} # 或:ssh ${NODES[2]}"
echo
echo "【外部 SSH 工具使用说明】"
echo " 本脚本不启用密码登录。请从任意节点下载私钥:/home/${user}/.ssh/id_rsa"
echo " 将其导入 Xshell/MobaXterm/FinalShell 等工具后,使用 ${user}@nodeX 即可登录。"
}
cmd_status() {
local user="${1:-}"; [[ -z "$user" ]] && die "缺少用户名。用法:$0 status <username>"
local sdir; sdir="$(ssh_dir "$user")"
local prik; prik="$(pri_key "$user")"
local auth; auth="$(auth_keys "$user")"
echo "[STEP] 查询各节点状态(用户/主组/密钥/authorized_keys)"
for n in "${NODES[@]}"; do
echo "---- ${n} ----"
if run_remote "$n" "id -u ${user} >/dev/null 2>&1"; then
echo "用户 : 存在"
run_remote "$n" "id -g -n ${user} 2>/dev/null" || true
run_remote "$n" "printf '主组 : '; id -g -n ${user} 2>/dev/null || echo '未知'"
run_remote "$n" "test -d ${sdir} && echo 'SSH目录 : 存在' || echo 'SSH目录 : 不存在'"
run_remote "$n" "test -f ${prik} && echo '私钥 : 存在' || echo '私钥 : 不存在'"
run_remote "$n" "test -f ${auth} && echo 'authorized_keys: 存在' || echo 'authorized_keys: 不存在'"
else
echo "用户 : 不存在"
fi
done
}
cmd_rotate() {
local user="${1:-}"; [[ -z "$user" ]] && die "缺少用户名。用法:$0 rotate <username>"
local sdir; sdir="$(ssh_dir "$user")"
local prik; prik="$(pri_key "$user")"
local pubk; pubk="$(pub_key "$user")"
local auth; auth="$(auth_keys "$user")"
echo "[STEP] 本机轮换 ${user} 的 RSA(4096) 密钥(生成新钥并覆盖全集群)"
if ! id -u "$user" >/dev/null 2>&1; then
die "本机不存在用户 ${user},请先执行:$0 create ${user}"
fi
# 备份旧密钥(仅本机)
if [[ -f "$prik" ]]; then
mv -f "${prik}" "${prik}.bak.$(date +%s)"
[[ -f "${pubk}" ]] && mv -f "${pubk}" "${pubk}.bak.$(date +%s)" || true
fi
sudo -u "$user" ssh-keygen -t rsa -b 4096 -f "$prik" -N '' >/dev/null
touch "$auth"; chown "$user:$DEFAULT_GROUP" "$auth"; chmod 600 "$auth"
# 清空并写入新的公钥(本机)
: > "$auth"
cat "$pubk" >> "$auth"
echo "[STEP] 向各节点写入新的公钥与私钥(覆盖)"
for n in "${NODES[@]}"; do
echo "[INFO] 节点: ${n}"
ensure_group_remote "$n" "$DEFAULT_GROUP"
run_remote "$n" "id -u ${user} >/dev/null 2>&1 || adduser --disabled-password --gecos '' --ingroup '$DEFAULT_GROUP' ${user}"
# 覆盖 authorized_keys
copy_to_remote "$pubk" "$n" "/tmp/${user}_id.pub"
run_remote "$n" "
install -d -m 700 -o ${user} -g ${DEFAULT_GROUP} ${sdir} && \
umask 077 && \
cat /tmp/${user}_id.pub > ${auth} && \
chown ${user}:${DEFAULT_GROUP} ${auth} && chmod 600 ${auth} && \
rm -f /tmp/${user}_id.pub
"
# 覆盖私钥
run_remote "$n" "install -d -m 700 -o ${user} -g ${DEFAULT_GROUP} ${sdir}"
copy_to_remote "$prik" "$n" "$prik"
run_remote "$n" "chown ${user}:${DEFAULT_GROUP} ${prik} && chmod 600 ${prik}"
done
echo
echo "[OK] 已完成 ${user} 的密钥轮换并在集群内全部覆盖。"
echo "【外部 SSH 工具提醒】请重新下载新的 /home/${user}/.ssh/id_rsa 到本地并更新工具内的私钥。"
}
cmd_delete() {
local user="${1:-}"; shift || true
[[ -z "$user" ]] && die "缺少用户名。用法:$0 delete <username> [--purge]"
local purge="no"
if [[ "${1:-}" == "--purge" ]]; then purge="yes"; fi
echo "[WARN] 即将在所有节点删除用户 ${user}。"
if [[ "$purge" == "yes" ]]; then
echo "[WARN] --purge 模式:将连同家目录一起删除,操作不可逆!"
else
echo "[INFO] 不带 --purge:仅删除账号,保留家目录(/home/${user})。"
fi
read -r -p "确认继续? [y/N]: " ans
[[ "${ans}" == "y" || "${ans}" == "Y" ]] || die "用户取消。"
for n in "${NODES[@]}"; do
echo "[INFO] 节点: ${n} -> 删除用户 ${user}"
# 尝试结束该用户的进程,避免 userdel 失败
run_remote "$n" "id -u ${user} >/dev/null 2>&1 && pkill -u ${user} || true"
if [[ "$purge" == "yes" ]]; then
run_remote "$n" "id -u ${user} >/dev/null 2>&1 && userdel -r ${user} || true"
else
run_remote "$n" "id -u ${user} >/dev/null 2>&1 && userdel ${user} || true"
fi
done
echo "[OK] 已在所有节点完成对 ${user} 的删除操作。"
}
# ======== 主调度 ========
main() {
local cmd="${1:-}"; shift || true
case "$cmd" in
create) cmd_create "$@";;
status) cmd_status "$@";;
rotate) cmd_rotate "$@";;
delete) cmd_delete "$@";;
""|-h|--help|help) usage;;
*) die "未知子命令:$cmd 使用 --help 查看帮助";;
esac
}
main "$@"
chmod +x /root/scripts/cluster_user_ssh.sh
/root/scripts/cluster_user_ssh.sh -h
/root/scripts/cluster_user_ssh.sh create hadoop

现在普通用户hadoop也可以无缝ssh了,至于termius的普通用户登录则需要和root类似,通过下载服务器节点中的密钥文件/home//.ssh/id_rsa 并上传到shell工具中即可。
# 设置密码
sudo passwd hadoop
# 加到sudo组
usermod -aG sudo hadoop
大数据组件
先准备好目录
# 三个节点都要
mkdir -p /export/server
cd /export/server/
jdk
# 解压
tar -zxvf /export/server/jdk-17.0.16_linux-x64_bin.tar.gz -C /export/server/
# 软链(-s 软链,-f 覆盖,-n 当目标是软链名时按普通文件对待)
ln -sfn /export/server/jdk-17.0.16 /export/server/jdk
# 分发
xsync /export/server/jdk /export/server/jdk-17.0.16
配置环境变量
vim /etc/profile
加入下面代码
export JAVA_HOME=/export/server/jdk
export PATH=$PATH:$JAVA_HOME/bin
保存退出,也分发一下
xsync /etc/profile
每个节点都要
# source一下
source /etc/profile
# 删除自带的java
rm -f /usr/bin/java
# 创建软链接
ln -s /export/server/jdk/bin/java /usr/bin/java
# 校验下
java -version
javac -version
集群命令批量执行脚本
vim /usr/local/bin/xcall
#!/usr/bin/env bash
#
# 批量在集群节点执行命令(加载环境变量,保持当前工作目录)
# 节点列表:node1 node2 node3
#
# 用法示例:
# xcall "jps"
# xcall "hostname && whoami"
# xcall "ls -l"
#
# 说明:
# 1) 显式加载 /etc/profile 与 ~/.bashrc,避免非登录/非交互 shell 下 PATH 失效
# 2) 在远端先 cd 到本地当前目录再执行命令,保证路径一致
# 3) 需要提前配置免密登录
#
set -euo pipefail
# 节点列表
hosts=(node1 node2 node3)
# 参数校验
if [ $# -lt 1 ]; then
echo "用法: $(basename "$0") \"命令\""
exit 1
fi
# 要执行的命令(原样传递给远端)
cmd="$*"
# 本地当前目录(在远端也切到该目录)
workdir="$(pwd)"
for host in "${hosts[@]}"; do
echo "========== $host =========="
ssh -o BatchMode=yes "$host" "
# 尽量加载环境(不存在也不报错)
[ -f /etc/profile ] && source /etc/profile >/dev/null 2>&1 || true
[ -f ~/.bashrc ] && source ~/.bashrc >/dev/null 2>&1 || true
# 切换到调用时的工作目录(不存在则忽略)
cd '$workdir' 2>/dev/null || true
# 执行命令
$cmd
"
done
chmod 755 /usr/local/bin/xcall

Hadoop
官网下载
先解压
tar -zxvf ~/hadoop-3.4.1.tar.gz -C /export/server/
cd /export/server/
ln -s /export/server/hadoop-3.4.1 hadoop
cd hadoop

HDFS
配置文件
配置HDFS集群,我们主要涉及到如下文件的修改:
workers:配置从节点(DataNode)有哪些
hadoop-env.sh:配置Hadoop的相关环境变量
core-site.xml:Hadoop核心配置文件
hdfs-site.xml:HDFS核心配置文件
这些文件均存在与$HADOOP_HOME/etc/hadoop文件夹中。
PS:SHADOOP_HOME是后续我们要设置的环境变量,其指代Hadoop安装文件夹即/export/server/hadoop
work文件
cd /export/server/hadoop/etc/hadoop/
vim /export/server/hadoop/etc/hadoop/workers
删除第一行,然后添加如下三个节点
node1
node2
node3
hadoop-env文件
vim /export/server/hadoop/etc/hadoop/hadoop-env.sh
在顶部添加如下内容
# ====== 基础路径======
export JAVA_HOME=/export/server/jdk # JDK 安装目录
export HADOOP_HOME=/export/server/hadoop # Hadoop 根目录
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop # Hadoop 配置目录
export HADOOP_LOG_DIR=$HADOOP_HOME/logs # Hadoop 日志目录(确保可写)
# ====== JVM 兼容性设置(JDK17+/Guice/cglib 反射所需)======
# 说明:
# - 解决报错:java.lang.reflect.InaccessibleObjectException: module java.base does not "opens java.lang" ...
# - 仅对以本 env 启动的 Hadoop 进程生效;不影响系统其他 Java 程序。
# - 如需回滚,删除这一行即可。
export HADOOP_OPTS="$HADOOP_OPTS \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
core-site文件
vim /export/server/hadoop/etc/hadoop/core-site.xml
删除<configuration></configuration>,并添加如下内容
<configuration>
<!--
fs.defaultFS
指定 Hadoop 文件系统的默认 URI
格式:schema://host:port
例如 hdfs://node1:8020 表示 HDFS 集群,NameNode 在 node1 主机上,RPC 端口为 8020
注意:这里的主机名必须能被集群所有节点解析
-->
<property>
<name>fs.defaultFS</name>
<value>hdfs://node1:8020</value>
</property>
<!--
io.file.buffer.size
Hadoop 读写文件时使用的缓冲区大小(单位:字节)
默认值为 4096,这里设置为 131072(128KB)以提升大文件的读写效率
值过大可能导致小文件场景下内存浪费
-->
<property>
<name>io.file.buffer.size</name>
<value>131072</value>
</property>
</configuration>
hdfs-site文件
vim /export/server/hadoop/etc/hadoop/hdfs-site.xml
删除<configuration></configuration>,并添加如下内容
<configuration>
<!-- DataNode 存储目录的权限设置,700 表示仅属主可读写执行 -->
<property>
<name>dfs.datanode.data.dir.perms</name>
<value>700</value>
</property>
<!-- NameNode 元数据存储目录 -->
<property>
<name>dfs.namenode.name.dir</name>
<value>/data/nn</value>
</property>
<!-- 允许连接到 NameNode 的主机列表 -->
<property>
<name>dfs.namenode.hosts</name>
<value>node1,node2,node3</value>
</property>
<!-- HDFS 块大小(单位:字节),此处为 256MB -->
<property>
<name>dfs.blocksize</name>
<value>268435456</value>
</property>
<!-- NameNode 处理请求的线程数量 -->
<property>
<name>dfs.namenode.handler.count</name>
<value>100</value>
</property>
<!-- DataNode 存储数据块的目录 -->
<property>
<name>dfs.datanode.data.dir</name>
<value>/data/dn</value>
</property>
</configuration>
在node1节点:
mkdir -p /data/nn
mkdir /data/dn
在node2和node3节点:
mkdir -p /data/dn
分发配置好的Hadoop文件夹
cd /export/server/
xsync hadoop-3.4.1/ hadoop
配置下Hadoop的脚本到环境变量
vim /etc/profile
在底部追加
export HADOOP_HOME=/export/server/hadoop
export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin
然后每个节点
source /etc/profile
授权为普通用户hadoop
安全起见,不要用root去控制,改成普通用户hadoop
xcall ls -ld /data /export/
xcall chown -R hadoop:bigdata /data
xcall chown -R hadoop:bigdata /export

格式化并启动
su - hadoop
注意,所有设计配置变动的,比如更改配置文件,更改jdk版本都需要重新格式化。
若格式化后发现缺少服务,则可能需要去手动清空一下data目录下的一些文件。
hadoop namenode -format
# 如只有主节点启动namenode
# xcall rm -rf /data/dn/*
平时如果有什么问题需要重置集群,先格式化,然后每个节点删除/data/dn/*
一键启动
start-dfs.sh
查看进程
xcall jps
一键停止集群
stop-dfs.sh

webui(http://node1的公网IP:9870)应该也是能正常访问的
打包备份
(不推荐,文件太大了,建议用yarn配置好后的备份方案)
可以留存上面几个配置文件,也可以直接打包整个Hadoop文件夹
mkdir /export/server/back/
tar -zcvf /export/server/back/hadoop-ok-1.tar.gz /export/server/hadoop-3.4.1/
YARN
mapred-env
cd /export/server/hadoop/etc/hadoop
vim /export/server/hadoop/etc/hadoop/mapred-env.sh
顶部添加
# ====== 基础路径 / 日志 ======
export JAVA_HOME=/export/server/jdk
# JobHistoryServer 堆大小(单位 MB)
export HADOOP_JOB_HISTORYSERVER_HEAPSIZE=1000
# 日志级别(旧变量,兼容保留)
# export HADOOP_MAPRED_ROOT_LOGGER=INFO,RFA
# 建议同时设置新的根日志变量(可选)
export HADOOP_ROOT_LOGGER=INFO,RFA
# ====== JDK17+ 兼容性开关(解决 HistoryServer/AM Web 报 InaccessibleObjectException)======
export MAPRED_OPTS="$MAPRED_OPTS \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
mapred-site
vim /export/server/hadoop/etc/hadoop/mapred-site.xml
替换原文件的<configuration></configuration>
<configuration>
<!-- MapReduce 的运行框架设置为 YARN -->
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
<description>MapReduce 的运行框架设置为 YARN</description>
</property>
<!-- 历史服务器 RPC 地址(JobHistory Server) -->
<property>
<name>mapreduce.jobhistory.address</name>
<value>node1:10020</value>
<description>历史服务器 RPC 监听地址</description>
</property>
<!-- 历史服务器 Web UI 地址 -->
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>node1:19888</value>
<description>历史服务器 Web 端口为 node1 的 19888</description>
</property>
<!-- 历史信息在 HDFS 的记录临时路径 -->
<property>
<name>mapreduce.jobhistory.intermediate-done-dir</name>
<value>/data/mr-history/tmp</value>
<description>历史信息在 HDFS 的记录临时路径</description>
</property>
<!-- 历史信息在 HDFS 的记录完成路径 -->
<property>
<name>mapreduce.jobhistory.done-dir</name>
<value>/data/mr-history/done</value>
<description>历史信息在 HDFS 的记录完成路径</description>
</property>
<!-- ApplicationMaster 环境变量:指定 MapReduce 的 HOME -->
<property>
<name>yarn.app.mapreduce.am.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
<description>MapReduce HOME 设置为 HADOOP_HOME</description>
</property>
<!-- Map 任务环境变量:指定 MapReduce 的 HOME -->
<property>
<name>mapreduce.map.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
<description>Map 任务环境变量设置为 HADOOP_HOME</description>
</property>
<!-- Reduce 任务环境变量:指定 MapReduce 的 HOME -->
<property>
<name>mapreduce.reduce.env</name>
<value>HADOOP_MAPRED_HOME=${HADOOP_HOME}</value>
<description>Reduce 任务环境变量设置为 HADOOP_HOME</description>
</property>
</configuration>
yarn-env
vim yarn-env.sh
顶部添加
# ====== 基础路径 ======
export JAVA_HOME=/export/server/jdk
export HADOOP_HOME=/export/server/hadoop
export HADOOP_CONF_DIR=$HADOOP_HOME/etc/hadoop
export HADOOP_LOG_DIR=$HADOOP_HOME/logs
# ====== JDK17+ 兼容性开关(解决 NodeManager/ResourceManager 启动时报 InaccessibleObjectException)======
export HADOOP_OPTS="${HADOOP_OPTS} \
--add-opens=java.base/java.lang=ALL-UNNAMED \
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
yarn-site
vim yarn-site.xml
替换原文件的<configuration></configuration>
<configuration>
<!-- ResourceManager 设置在 node1 节点 -->
<property>
<name>yarn.resourcemanager.hostname</name>
<value>node1</value>
<description>ResourceManager 主机名</description>
</property>
<!-- NodeManager 中间数据本地存储路径 -->
<property>
<name>yarn.nodemanager.local-dirs</name>
<value>/data/nm-local</value>
<description>NodeManager 中间数据本地存储路径</description>
</property>
<!-- NodeManager 日志本地存储路径 -->
<property>
<name>yarn.nodemanager.log-dirs</name>
<value>/data/nm-log</value>
<description>NodeManager 数据日志本地存储路径</description>
</property>
<!-- 为 MapReduce 程序开启 Shuffle 服务 -->
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
<description>为 MapReduce 程序开启 Shuffle 服务</description>
</property>
<!-- 历史服务器日志访问 URL -->
<property>
<name>yarn.log.server.url</name>
<value>http://node1:19888/jobhistory/logs</value>
<description>历史服务器日志访问 URL</description>
</property>
<!-- Web 代理服务主机和端口 -->
<property>
<name>yarn.web-proxy.address</name>
<value>node1:8089</value>
<description>Web 代理服务主机和端口</description>
</property>
<!-- 开启日志聚合 -->
<property>
<name>yarn.log-aggregation-enable</name>
<value>true</value>
<description>开启日志聚合功能</description>
</property>
<!-- 程序日志在 HDFS 的存储路径 -->
<property>
<name>yarn.nodemanager.remote-app-log-dir</name>
<value>/tmp/logs</value>
<description>程序运行日志在 HDFS 的存储路径</description>
</property>
<!-- 使用公平调度器 -->
<property>
<name>yarn.resourcemanager.scheduler.class</name>
<value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler</value>
<description>选择公平调度器(Fair Scheduler)</description>
</property>
</configuration>
同步配置
xsync `pwd`/

启动
# 启动yarn
start-yarn.sh
# 停止yarn
stop-yarn.sh
#启动history
mapred --daemon start historyserver
mapred --daemon stop historyserver


轻量级备份(强烈推荐)
# 先确保目标目录存在
mkdir -p /export/server/back
# 打包并保留原文件
tar -czvf /export/server/back/hadoop_conf_$(date +%F).tar.gz -C /export/server/hadoop/etc/hadoop .
Mysql
安装
# 1. 更新系统软件包索引
sudo apt update
# 2. 安装 MySQL 服务器(会自动拉取最新可用版本)
sudo apt install -y mysql-server
# 3. 启动并设置开机自启
sudo systemctl enable --now mysql
# 4. 检查 MySQL 状态
sudo systemctl status mysql

# MySQL 提供的安全初始化向导
sudo mysql_secure_installation
执行后会有几个步骤(推荐选择):
- 设置 root 密码等级
- 移除匿名用户 → Y
- 禁止 root 远程登录 → Y(我们会用新用户)
- 删除 test 数据库 → Y
- 重新加载权限 → Y
修改监听配置
编辑
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
修改(允许来自任意 IP 的连接请求)
bind-address = 0.0.0.0
重启:
sudo systemctl restart mysql
监听放开不等于谁都能连,真正能登录的账号还需要授权。
root 没有远程授权时,即使能到端口也会被拒绝。
Hive
为hive准备专用账号
sudo mysql -u root -p
这里是本地连接不用密码的,直接回车即可
-- 如果已存在旧库,先删除
DROP DATABASE IF EXISTS hive;
-- 重新创建 Hive 元数据库
CREATE DATABASE hive
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
-- 如果已存在旧用户,先删除
DROP USER IF EXISTS 'hive'@'%';
-- 创建 Hive 用户(允许远程连接)
CREATE USER 'hive'@'%' IDENTIFIED BY 'Hive!123';
-- 授权 Hive 用户管理 hive 库
GRANT ALL PRIVILEGES ON hive.* TO 'hive'@'%';
-- 刷新权限
FLUSH PRIVILEGES;
EXIT;
配置Hadoop
core-site
新增两个配置
<!-- 允许 hadoop 用户代理访问的主机列表,* 表示任意主机 -->
<property>
<name>hadoop.proxyuser.hadoop.hosts</name>
<value>*</value>
</property>
<!-- 允许 hadoop 用户代理访问的用户组,* 表示任意用户组 -->
<property>
<name>hadoop.proxyuser.hadoop.groups</name>
<value>*</value>
</property>
下载
官网
然后把准备好的hive包通过sftp拖到/export/server下
mysql driver包
cd /export/server
wget https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar
安装
tar -zxvf /export/server/apache-hive-4.1.0-bin.tar.gz -C /export/server/
mv /export/server/mysql-connector-j-8.0.33.jar /export/server/apache-hive-4.1.0-bin/lib/
ln -s /export/server/apache-hive-4.1.0-bin hive
配置文件
hive-env
cd /export/server/hive/conf
cp hive-env.sh.template hive-env.sh
vim /export/server/hive/conf/hive-env.sh
顶部加上
# Hadoop 根路径
export HADOOP_HOME=/export/server/hadoop
# Hive 根路径(建议加上,后面引用更清晰)
export HIVE_HOME=/export/server/hive
# Hive 配置目录
export HIVE_CONF_DIR=${HIVE_HOME}/conf
# Hive 的额外依赖 JAR 搜索路径(用于放置 MySQL 驱动等第三方 JAR)
export HIVE_AUX_JARS_PATH=${HIVE_HOME}/lib
# 增大 HS2 堆
export HIVESERVER2_HEAPSIZE=4096
export HADOOP_HEAPSIZE=4096
hive-site
vim /export/server/hive/conf/hive-site.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- Hive Metastore 使用的 JDBC 连接 URL(MySQL 8.x) -->
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://node1:3306/hive?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&charsetEncoding=UTF-8&serverTimezone=Asia/Tokyo</value>
<description>Hive Metastore 连接 MySQL 的 JDBC URL;允许自动建库,关闭 SSL,设置时区与字符集</description>
</property>
<!-- MySQL 8.x 驱动类(8.x 必须使用 com.mysql.cj.jdbc.Driver) -->
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.cj.jdbc.Driver</value>
<description>MySQL Connector/J 8.0.x 对应的 JDBC 驱动类</description>
</property>
<!-- 数据库用户名 -->
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>hive</value>
<description>Metastore 数据库用户名</description>
</property>
<!-- 数据库密码 -->
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>Hive!123</value>
<description>Metastore 数据库密码</description>
</property>
<!-- HiveServer2 绑定主机(可按需修改为具体主机名或 0.0.0.0) -->
<property>
<name>hive.server2.thrift.bind.host</name>
<value>node1</value>
<description>HiveServer2 绑定主机名</description>
</property>
<!-- Hive Metastore Thrift 服务地址 -->
<property>
<name>hive.metastore.uris</name>
<value>thrift://node1:9083</value>
<description>Metastore 对外提供的 Thrift 服务地址</description>
</property>
<!-- 事件通知 API 鉴权(很多入门/实验环境会先关闭) -->
<property>
<name>hive.metastore.event.db.notification.api.auth</name>
<value>false</value>
<description>是否开启 Metastore 事件通知 API 的鉴权</description>
</property>
</configuration>
初始化
创建日志文件夹
mkdir /export/server/hive/logs
cd /export/server/hive
bin/schematool -initSchema -dbType mysql -verbose

确定hive目录的权限是属于普通用户hadoop的,如果不是
chown -R hadoop:bigdata apache-hive-4.1.0-bin hive
启动
一般起后台服务即可
# 先启动metastore服务
nohup /export/server/hive/bin/hive --service metastore >> logs/metastore.log 2>&1 &
# 然后启动hiveserver2服务
nohup /export/server/hive/bin/hive --service hiveserver2 >> logs/hiveserver2.log 2>&1 &
然后就能用datagrip等工具直接连接了。
想验证的话
ps -ef|grep 【两个runjar的进程号】
若需要beeline的方式写sql
/export/server/hive/bin/beeline -u "jdbc:hive2://node1:10000/default" -n hadoop -p ""
然后就能show databases;,愉快写sql了。
一键脚本
vim /export/server/hive/scripts/hivectl.env
# ===== hivectl.env =====
# Hive 管控统一配置(被 hivectl.sh source)
# 建议文件编码:UTF-8,换行:LF(Unix)
# 基础路径
export HIVE_PREFIX="/export/server/hive"
export HIVE_HOME="${HIVE_HOME:-/export/server/apache-hive-4.1.0-bin}"
export HIVE_BIN="${HIVE_BIN:-$HIVE_PREFIX/bin}"
export HIVE_CONF_DIR="${HIVE_CONF_DIR:-$HIVE_HOME/conf}"
# 运行/日志目录
export HIVE_RUN_DIR="${HIVE_RUN_DIR:-$HIVE_PREFIX/run}"
export HIVE_LOG_DIR="${HIVE_LOG_DIR:-$HIVE_PREFIX/logs}"
# 可执行文件
export HIVE_CMD="${HIVE_CMD:-$HIVE_BIN/hive}"
export BEELINE_CMD="${BEELINE_CMD:-$HIVE_BIN/beeline}"
# Java/Hadoop 环境
export JAVA_HOME="${JAVA_HOME:-/usr/lib/jvm/java-11-openjdk-amd64}"
export HADOOP_HOME="${HADOOP_HOME:-/export/server/hadoop-3.4.1}"
export HADOOP_CONF_DIR="${HADOOP_CONF_DIR:-$HADOOP_HOME/etc/hadoop}"
# 统一 Java 选项(你环境里已有 add-opens)
export JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS:- --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED}"
# ---- 日志输出策略(遵循 HIVE-19886)----
# 不再用命令行 --hiveconf 传 hive.root.logger,而是用 HADOOP_CLIENT_OPTS。
# 默认走控制台 appender(stdout 通过 nohup 重定向到日志文件)。
# 若需 Log4j2 的滚动文件,请将 HIVE_ROOT_LOGGER 改为 INFO,RFA 并确认 hive-log4j2.properties 定义了 appender RFA。
export HIVE_ROOT_LOGGER="${HIVE_ROOT_LOGGER:-console}"
# 强制覆盖到 HADOOP_CLIENT_OPTS(避免继承系统里旧的 INFO,DRFA)
# 如需追加自定义 JVM 选项,可通过 HADOOP_CLIENT_OPTS_EXTRA 注入。
export HADOOP_CLIENT_OPTS="-Dhive.root.logger.level=INFO -Dhive.root.logger.appender=${HIVE_ROOT_LOGGER} ${HADOOP_CLIENT_OPTS_EXTRA:-}"
# Metastore 配置
export METASTORE_HOST="${METASTORE_HOST:-node1}"
export METASTORE_PORT="${METASTORE_PORT:-9083}"
export HIVE_METASTORE_URIS="${HIVE_METASTORE_URIS:-thrift://${METASTORE_HOST}:${METASTORE_PORT}}"
# HiveServer2 配置
export HS2_MODE="${HS2_MODE:-binary}" # binary 或 http
export HS2_HOST="${HS2_HOST:-node1}"
export HS2_PORT="${HS2_PORT:-10000}" # binary 端口
export HS2_HTTP_PORT="${HS2_HTTP_PORT:-10001}"
export HS2_HTTP_PATH="${HS2_HTTP_PATH:-cliservice}"
# 认证
export HS2_AUTH="${HS2_AUTH:-NONE}" # NONE / KERBEROS / LDAP ...
export HS2_KRB_PRINCIPAL="${HS2_KRB_PRINCIPAL:-}"
# Beeline 默认凭据
export BEELINE_USER="${BEELINE_USER:-hadoop}"
export BEELINE_PASSWORD="${BEELINE_PASSWORD:-}"
# 启停与就绪检测
export START_TIMEOUT_SEC="${START_TIMEOUT_SEC:-90}"
export STOP_GRACE_SEC="${STOP_GRACE_SEC:-10}"
# 日志显示策略
export TAIL_LINES="${TAIL_LINES:-200}" # logs/尾部行数(用于 -v)
export ERROR_SCAN_LINES="${ERROR_SCAN_LINES:-400}" # 错误扫描行数
# 日志文件
export METASTORE_LOG="${METASTORE_LOG:-$HIVE_LOG_DIR/metastore.log}"
export HS2_LOG="${HS2_LOG:-$HIVE_LOG_DIR/hiveserver2.log}"
# PID 文件
export METASTORE_PID_FILE="${METASTORE_PID_FILE:-$HIVE_RUN_DIR/metastore.pid}"
export HS2_PID_FILE="${HS2_PID_FILE:-$HIVE_RUN_DIR/hiveserver2.pid}"
# 体检时额外检查端口
export EXTRA_CHECK_PORTS="${EXTRA_CHECK_PORTS:-$HS2_PORT $HS2_HTTP_PORT $METASTORE_PORT}"
# ===== 结束 =====
vim /export/server/hive/scripts/hivectl.sh
#!/usr/bin/env bash
# ===== hivectl.sh =====
# 统一管理 Hive Metastore 与 HiveServer2 的启动/停止/状态/日志
# 用法:
# ./hivectl.sh start|stop|restart|status [-v]|logs|tail|beeline|doctor
set -euo pipefail
# -------- 加载配置 --------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HIVECTL_ENV="${HIVECTL_ENV:-$SCRIPT_DIR/hivectl.env}"
if [[ ! -f "$HIVECTL_ENV" ]]; then
echo "未找到配置文件:$HIVECTL_ENV" >&2
exit 1
fi
# shellcheck disable=SC1090
source "$HIVECTL_ENV"
# -------- 工具函数 --------
mkdir -p "$HIVE_LOG_DIR" "$HIVE_RUN_DIR"
ts() { date "+%Y-%m-%d %H:%M:%S"; }
log() { echo "[$(ts)] $*"; }
require_cmd() {
for c in "$@"; do
command -v "$c" >/dev/null 2>&1 || { echo "缺少命令:$c" >&2; exit 1; }
done
}
is_running() {
local pidfile="$1"
if [[ -f "$pidfile" ]]; then
local pid
pid="$(cat "$pidfile" 2>/dev/null || true)"
[[ -n "${pid:-}" ]] && ps -p "$pid" >/dev/null 2>&1
else
return 1
fi
}
write_pid() { echo "$2" > "$1"; }
rm_pid() { rm -f "$1"; }
wait_port_listen() {
local host="$1" port="$2" timeout="${3:-30}"
local start=$(date +%s)
while true; do
if ss -ltnp 2>/dev/null | grep -qE "[:.]${port}\s"; then
return 0
fi
local now=$(date +%s)
(( now - start >= timeout )) && return 1
sleep 1
done
}
wait_tcp_connect() {
local host="$1" port="$2" timeout="${3:-30}"
local start=$(date +%s)
while true; do
if nc -z "$host" "$port" >/dev/null 2>&1; then
return 0
fi
local now=$(date +%s)
(( now - start >= timeout )) && return 1
sleep 1
done
}
hs2_jdbc_url() {
local url
if [[ "${HS2_MODE,,}" == "http" ]]; then
url="jdbc:hive2://${HS2_HOST}:${HS2_HTTP_PORT}/default;transportMode=http;httpPath=${HS2_HTTP_PATH}"
else
url="jdbc:hive2://${HS2_HOST}:${HS2_PORT}/default"
fi
if [[ "${HS2_AUTH^^}" == "KERBEROS" && -n "${HS2_KRB_PRINCIPAL:-}" ]]; then
url="${url};principal=${HS2_KRB_PRINCIPAL}"
fi
echo "$url"
}
beeline_ping() {
local url user pass
url="$(hs2_jdbc_url)"
user="${BEELINE_USER}"
pass="${BEELINE_PASSWORD}"
"$BEELINE_CMD" -u "$url" -n "$user" -p "$pass" -e "SELECT 1" >/dev/null 2>&1
}
wait_hs2_ready() {
local timeout="${1:-$START_TIMEOUT_SEC}"
log "等待 HiveServer2 就绪(最长 ${timeout}s)..."
if [[ "${HS2_MODE,,}" == "http" ]]; then
wait_tcp_connect "$HS2_HOST" "$HS2_HTTP_PORT" "$timeout" || return 1
else
wait_tcp_connect "$HS2_HOST" "$HS2_PORT" "$timeout" || return 1
fi
local start=$(date +%s)
while true; do
if beeline_ping; then
log "HiveServer2 探活成功"
return 0
fi
local now=$(date +%s)
(( now - start >= timeout )) && break
sleep 2
done
return 1
}
print_usage() {
cat <<EOF
用法:
$(basename "$0") start 启动 Metastore 与 HiveServer2(带就绪检测)
$(basename "$0") stop 停止 HiveServer2 与 Metastore
$(basename "$0") restart 重启
$(basename "$0") status [-v] 查看状态(默认仅摘要;-v 显示日志尾部 ${TAIL_LINES} 行)
$(basename "$0") logs 快速查看最新日志(${TAIL_LINES} 行)
$(basename "$0") tail 实时跟随日志(Ctrl-C 退出)
$(basename "$0") beeline 打开 Beeline 会话(基于 env 构造 JDBC)
$(basename "$0") doctor 一键体检(依赖/端口/配置摘要)
环境文件:
$HIVECTL_ENV
EOF
}
# -------- 错误摘要扫描 --------
scan_errors() {
local file="$1" label="$2"
[[ -f "$file" ]] || return 0
# 仅在日志中出现关键字时输出摘要
local patt='ERROR|FATAL|Exception|Unable to locate appender'
if tail -n "$ERROR_SCAN_LINES" "$file" | egrep -q "$patt"; then
echo "--- ${label}:发现可疑日志 ---"
tail -n "$ERROR_SCAN_LINES" "$file" | egrep -n "$patt" | tail -n 50
fi
}
# -------- 启动/停止实现 --------
start_metastore() {
if is_running "$METASTORE_PID_FILE"; then
log "Metastore 已在运行,PID=$(cat "$METASTORE_PID_FILE")"
return 0
fi
require_cmd "$HIVE_CMD" nohup ss nc
log "启动 Metastore ... 日志: $METASTORE_LOG"
nohup "$HIVE_CMD" --service metastore >> "$METASTORE_LOG" 2>&1 &
local pid=$!; write_pid "$METASTORE_PID_FILE" "$pid"; sleep 1
wait_port_listen "$METASTORE_HOST" "$METASTORE_PORT" "$START_TIMEOUT_SEC" || true
if ! wait_tcp_connect "$METASTORE_HOST" "$METASTORE_PORT" "$START_TIMEOUT_SEC"; then
log "错误:无法连接 Metastore ${METASTORE_HOST}:${METASTORE_PORT},查看日志 $METASTORE_LOG"
return 1
fi
log "✅ Metastore 已启动,PID=$pid"
}
start_hs2() {
if is_running "$HS2_PID_FILE"; then
log "HiveServer2 已在运行,PID=$(cat "$HS2_PID_FILE")"
return 0
fi
require_cmd "$HIVE_CMD" "$BEELINE_CMD" nohup ss nc
log "启动 HiveServer2 ... 日志: $HS2_LOG"
nohup "$HIVE_CMD" --service hiveserver2 \
--hiveconf hive.metastore.uris="${HIVE_METASTORE_URIS}" \
>> "$HS2_LOG" 2>&1 &
local pid=$!; write_pid "$HS2_PID_FILE" "$pid"; sleep 1
if ! wait_hs2_ready "$START_TIMEOUT_SEC"; then
log "错误:HiveServer2 未在 ${START_TIMEOUT_SEC}s 内就绪,请查看日志 $HS2_LOG"
return 1
fi
log "✅ HiveServer2 已启动,PID=$pid"
}
stop_service() {
local name="$1" pidfile="$2" logfile="$3"
if ! is_running "$pidfile"; then
log "$name 未在运行"
rm_pid "$pidfile" || true
return 0
fi
local pid; pid="$(cat "$pidfile")"
log "停止 $name,PID=$pid ..."
kill "$pid" 2>/dev/null || true
local start=$(date +%s)
while ps -p "$pid" >/dev/null 2>&1; do
local now=$(date +%s)
if (( now - start >= STOP_GRACE_SEC )); then
log "$name 未优雅退出,发送 SIGKILL"
kill -9 "$pid" 2>/dev/null || true
break
fi
sleep 1
done
ps -p "$pid" >/dev/null 2>&1 && { log "❌ $name 停止失败,日志:$logfile"; return 1; }
rm_pid "$pidfile" || true
log "✅ $name 已停止"
}
start_all() { start_metastore; start_hs2; }
stop_all() { stop_service "HiveServer2" "$HS2_PID_FILE" "$HS2_LOG" || true; stop_service "Metastore" "$METASTORE_PID_FILE" "$METASTORE_LOG" || true; }
status_all() {
local verbose="${1:-0}"
echo "=== 状态 ==="
if is_running "$METASTORE_PID_FILE"; then
echo "Metastore : 运行中 (PID $(cat "$METASTORE_PID_FILE"))"
else
echo "Metastore : 未运行"
fi
if is_running "$HS2_PID_FILE"; then
echo "HiveServer2: 运行中 (PID $(cat "$HS2_PID_FILE"))"
else
echo "HiveServer2: 未运行"
fi
echo "=== 端口监听 ==="
ss -ltnp | egrep ":(${METASTORE_PORT}|${HS2_PORT}|${HS2_HTTP_PORT})\b" || echo "无相关监听"
# 错误摘要:只在发现问题时输出
scan_errors "$METASTORE_LOG" "Metastore 日志"
scan_errors "$HS2_LOG" "HiveServer2 日志"
# 仅在 -v 模式下输出日志尾部
if [[ "$verbose" == "1" ]]; then
echo "=== 日志尾部(${TAIL_LINES} 行)==="
echo "--- $METASTORE_LOG ---"; tail -n "$TAIL_LINES" "$METASTORE_LOG" 2>/dev/null || true
echo "--- $HS2_LOG ---"; tail -n "$TAIL_LINES" "$HS2_LOG" 2>/dev/null || true
fi
}
logs_quick() {
echo "=== $METASTORE_LOG (末尾 ${TAIL_LINES} 行) ==="
tail -n "$TAIL_LINES" "$METASTORE_LOG" 2>/dev/null || true
echo
echo "=== $HS2_LOG (末尾 ${TAIL_LINES} 行) ==="
tail -n "$TAIL_LINES" "$HS2_LOG" 2>/dev/null || true
}
logs_tail() {
echo "Ctrl-C 退出跟随"
tail -n "$TAIL_LINES" -F "$METASTORE_LOG" "$HS2_LOG"
}
open_beeline() {
require_cmd "$BEELINE_CMD"
local url; url="$(hs2_jdbc_url)"
log "打开 Beeline:$url"
exec "$BEELINE_CMD" -u "$url" -n "$BEELINE_USER" -p "$BEELINE_PASSWORD"
}
doctor() {
echo "=== HIVECTL 体检 ==="
echo "[1/6] 可执行文件"
for c in "$HIVE_CMD" "$BEELINE_CMD" ss nc awk sed grep java; do
printf " - %s: " "$c"
if command -v "$c" >/dev/null 2>&1; then echo "OK"; else echo "缺失"; fi
done
echo "[2/6] 关键路径"
printf " - HIVE_HOME=%s : " "$HIVE_HOME"; [[ -d "$HIVE_HOME" ]] && echo "OK" || echo "不存在"
printf " - HIVE_CONF_DIR=%s : " "$HIVE_CONF_DIR"; [[ -d "$HIVE_CONF_DIR" ]] && echo "OK" || echo "不存在"
printf " - HIVE_RUN_DIR=%s : " "$HIVE_RUN_DIR"; [[ -d "$HIVE_RUN_DIR" ]] && echo "OK" || echo "不存在"
printf " - HIVE_LOG_DIR=%s : " "$HIVE_LOG_DIR"; [[ -d "$HIVE_LOG_DIR" ]] && echo "OK" || echo "不存在"
echo "[3/6] 端口连通"
for p in $EXTRA_CHECK_PORTS; do
[[ "$p" =~ ^[0-9]+$ ]] || continue
printf " - %s:%s : " "$HS2_HOST" "$p"
if nc -z "$HS2_HOST" "$p" >/dev/null 2>&1; then echo "可达"; else echo "不可达"; fi
done
echo "[4/6] 配置摘要"
echo " - Metastore URIs: $HIVE_METASTORE_URIS"
echo " - HS2_MODE: $HS2_MODE"
echo " - HS2 Binary: ${HS2_HOST}:${HS2_PORT}"
echo " - HS2 HTTP : ${HS2_HOST}:${HS2_HTTP_PORT} path=${HS2_HTTP_PATH}"
echo " - HS2_AUTH : $HS2_AUTH"
[[ "${HS2_AUTH^^}" == "KERBEROS" ]] && echo " - principal: $HS2_KRB_PRINCIPAL"
echo " - HIVE_ROOT_LOGGER: $HIVE_ROOT_LOGGER"
echo "[5/6] 运行状态"
status_all 0
echo "[6/6] Beeline URL"
echo " - URL: $(hs2_jdbc_url)"
echo "体检完成。"
}
# -------- 主流程 --------
cmd="${1:-}"
case "${cmd}" in
start) start_all ;;
stop) stop_all ;;
restart) stop_all || true; start_all ;;
status) # 可选 -v
shift || true
if [[ "${1:-}" == "-v" ]]; then status_all 1; else status_all 0; fi ;;
logs) logs_quick ;;
tail) logs_tail ;;
beeline) open_beeline ;;
doctor) doctor ;;
""|-h|--help|help) print_usage ;;
*) echo "未知命令:$cmd" >&2; print_usage; exit 2 ;;
esac
# ===== 结束 =====
# 赋权
chmod +x /export/server/hive/scripts/hivectl.sh
/export/server/hive/scripts/hivectl.sh start
/export/server/hive/scripts/hivectl.sh status
/export/server/hive/scripts/hivectl.sh stop
# 先清日志,重启
:> /export/server/hive/logs/hiveserver2.log
:> /export/server/hive/logs/metastore.log
/export/server/hive/scripts/hivectl.sh restart
测试
本地模式
/export/server/hive/bin/beeline -u "jdbc:hive2://node1:10000/default" -n hadoop -p "" \
-e "SET hive.execution.engine=mr; SET mapreduce.framework.name=local; CREATE DATABASE IF NOT EXISTS test_local; CREATE TABLE IF NOT EXISTS test_local.t1(id INT, name STRING); INSERT INTO test_local.t1 VALUES(1,'local_test'); SELECT * FROM test_local.t1;"
yarn模式
/export/server/hive/bin/beeline -u "jdbc:hive2://node1:10000/default" -n hadoop -p "" \
-e "SET hive.execution.engine=mr; SET mapreduce.framework.name=yarn; CREATE DATABASE IF NOT EXISTS test_yarn; CREATE TABLE IF NOT EXISTS test_yarn.t1(id INT, name STRING); INSERT INTO test_yarn.t1 VALUES(2,'yarn_test'); SELECT * FROM test_yarn.t1;"
DataGrip


这里用户名其实就是Linux对应的用户,密码可以不用输入测试连接能通过就可以点应用了。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)