告别 nohup:Hive Metastore 交给 systemd 托管的完整步骤

Hive Metastore 从 nohup 手工启动迁移到 systemd 托管

背景

在 Hadoop 3 + Hive 3 的学习环境中,Hive Metastore 通常是通过如下方式启动的:

bash 复制代码
nohup hive --service metastore -p 9083 > /tmp/hive-metastore.log 2>&1 &

这条命令简单直接,几秒钟就能把 metastore 拉起来。在初期搭建和调试阶段,它完全够用------能跑通就行。但随着集群使用频率增加,nohup 方式的短板开始逐渐暴露。

本文记录了在 hadoop3 节点上,将 Hive Metastore 从 nohup 手工启动迁移到 systemd 托管的完整过程,包括迁移动机、配置细节、验证步骤,以及一个日常管理脚本。


为什么不建议长期使用 nohup 管理 Hive Metastore

每次重启都要手动恢复

只要 hadoop3 主机重启(断电、维护、内核升级),metastore 进程就会消失。你必须重新登录、手工执行启动命令,才能让集群恢复正常。如果当时不在电脑旁,或者忘记了,所有依赖 metastore 的操作------Hive 查询、Spark 读 Hive 表------全部会报连接失败。排查半天才想起来"哦对,metastore 没启动",这种体验并不愉快。

进程崩溃没有任何通知

nohup 启动的进程没有任何监控和自愈机制。metastore 如果因为内存不足、MySQL 连接超时、或者其他异常原因悄悄崩溃,日志文件里可能有报错,但不会有任何主动通知。你只会在执行 Hive 操作时收到一个莫名其妙的连接错误,再去 jps 一查才发现进程早就没了。如果正在跑一个长时间的作业,中途 metastore 挂掉会直接导致作业失败。

停止进程的方式很原始

要停掉 nohup 启动的 metastore,流程是:先 jps 找到 RunJar 的 PID,再 kill PID。如果机器上同时跑着 HBase、HDFS、YARN 等多个 Java 进程,jps 输出里一堆 RunJar,你得靠经验判断哪个才是 metastore。操作繁琐,而且容易误杀其他进程。

日志管理会失控

/tmp/hive-metastore.log 这个文件没有任何轮转机制。metastore 运行越久,文件越大。/tmp 分区空间本来就有限,时间长了可能被撑满,进而影响其他依赖 /tmp 的程序。出问题要翻日志时,面对一个几百 MB 的单文件也很难快速定位。

总结 :短期学习用 nohup 完全没问题,能跑通就行。但如果集群要持续使用、跑真实作业,这些问题迟早会踩到。


为什么迁移到 systemd

systemd 是 CentOS 7 / RHEL 7 及以后版本的标准服务管理器,用它来托管 Hive Metastore 可以一次性解决上面提到的所有问题:

问题 nohup systemd
开机自启 需要手动操作 systemctl enable 一条命令搞定
崩溃重启 没有任何恢复机制 Restart=on-failure 自动拉起
启停管理 jps + kill,容易误杀 systemctl start/stop/restart,明确针对具体服务
日志管理 单文件无限增长 journald 自动轮转,支持按时间、按服务过滤
状态查询 依赖 ps/jps 猜测 systemctl status 实时显示运行状态、PID、资源占用

方向明确了,下面进入实操。


迁移步骤

第一步:停掉现有的 nohup 进程

迁移之前,先确保旧的 metastore 进程已经完全退出,端口 9083 空出来。

bash 复制代码
ss -lntp | grep 9083

找到占用 9083 的进程 PID 后,用 kill 停掉它:

bash 复制代码
kill <PID>
sleep 2
ss -lntp | grep 9083

如果 grep 9083 还有输出,说明进程没有完全退出,把新看到的 PID 再停一次,直到 9083 端口确认空闲。

第二步:创建 systemd 服务文件

这是迁移的核心步骤。在 /etc/systemd/system/ 下创建 hive-metastore.service

直接运行下面的命令即可

bash 复制代码
cat >/etc/systemd/system/hive-metastore.service <<'EOF'
[Unit]
Description=Apache Hive Metastore
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
Group=root
Environment="JAVA_HOME=/export/servers/jdk1.8.0_241"
Environment="HADOOP_HOME=/export/servers/hadoop-3.3.0"
Environment="HIVE_HOME=/export/servers/hive-3.1.3"
Environment="HIVE_CONF_DIR=/export/servers/hive-3.1.3/conf"
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/export/servers/jdk1.8.0_241/bin:/export/servers/hadoop-3.3.0/bin:/export/servers/hadoop-3.3.0/sbin:/export/servers/hive-3.1.3/bin"
WorkingDirectory=/export/servers/hive-3.1.3
ExecStart=/export/servers/hive-3.1.3/bin/hive --service metastore -p 9083
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
SuccessExitStatus=143
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF

几个关键配置项的说明

  • Environment :这是最容易踩坑的地方。systemd 启动的服务不会读取 /etc/profile/etc/profile.d/*.sh 中定义的环境变量。所以 JAVA_HOMEHADOOP_HOMEHIVE_HOME 这些变量必须在 service 文件里显式声明,绝不能省略。你在 /etc/profile.d/ 下配的脚本(比如 50-hive.sh)继续保留给日常登录 shell 使用,但 systemd 不会碰它。
  • Type=simple :因为 hive --service metastore 是前台运行的程序,不会自行 fork 到后台,所以用 simple 类型即可。
  • Restart=on-failure + RestartSec=5 :如果 metastore 异常退出(非正常 systemctl stop),systemd 会在 5 秒后自动尝试重新拉起。
  • SuccessExitStatus=143 :Java 进程被 SIGTERM(即 kill)终止时,退出码是 143。把它列为"成功退出状态",可以避免 systemd 在正常停止服务时报 failure。
  • LimitNOFILE=65535:提高文件描述符上限,metastore 在连接较多时可能需要。
  • StandardOutput=journal / StandardError=journal :所有输出统一交给 journald 管理,不再需要手工维护日志文件。
  • User=root :当前环境为了和原有配置一致,暂时使用 root 运行。更规范的做法是后续创建专用的 hive 用户,再修改 User/Group

第三步:加载、启用、启动

bash 复制代码
systemctl daemon-reload
systemctl enable hive-metastore
systemctl start hive-metastore
  • daemon-reload:让 systemd 加载新建的 service 文件。
  • enable:设置开机自启。
  • start:立即启动服务。

验证

服务启动后,需要从三个维度确认一切正常:

1. 服务运行状态

bash 复制代码
systemctl status hive-metastore --no-pager -l

预期看到 active (running),以及 enabled 标识(说明开机自启已生效)。

2. 端口监听

bash 复制代码
ss -lntp | grep 9083

预期看到 9083 端口被一个 java 进程监听。

3. 日志检查

bash 复制代码
journalctl -u hive-metastore -n 50 --no-pager

预期能看到 metastore 的正常启动日志。如果出现 SLF4J: Class path contains multiple SLF4J bindings 告警,这是 Hive 自带的 jar 与 Hadoop 的 jar 冲突导致的,只是打印告警,不影响 metastore 正常运行,先不用处理。

4. 重启验证(推荐)

如果条件允许,建议做一次完整的重启验证,确认开机自启确实生效:

bash 复制代码
reboot

重连后再次执行:

bash 复制代码
systemctl status hive-metastore --no-pager -l
ss -lntp | grep 9083

如果重启后 metastore 自动起来了,整个迁移就彻底闭环了。


日常管理

迁移完成后,以后所有对 Hive Metastore 的管理操作都通过 systemctl 进行:

bash 复制代码
systemctl start hive-metastore        # 启动
systemctl stop hive-metastore         # 停止
systemctl restart hive-metastore      # 重启
systemctl status hive-metastore --no-pager -l  # 查看状态
journalctl -u hive-metastore -f       # 实时跟踪日志

不需要再记 nohup 命令,不需要 jps + kill,不需要手动管理日志文件。


管理脚本说明

为了进一步简化日常操作,编写了一个 Bash 管理脚本 hive-metastore.sh,放置在 hadoop3 节点的 /root/ 目录下。

脚本的主要功能包括:

  • 启动 / 停止 / 重启 :封装 systemctl 命令,启动后自动等待端口就绪并反馈结果
  • 状态查看:一次性展示服务运行状态、端口监听情况、开机自启状态
  • 日志查看:支持实时跟踪和查看最近 N 行日志
  • 开机自启管理:快捷启用/禁用开机自启
  • 环境信息:查看 Java 版本、HIVE_HOME、HADOOP_HOME 等环境配置
  • 交互菜单 :传入 菜单menu 参数可进入交互式操作界面
  • 中英文参数 :支持中文参数(如启动停止)和英文参数(如 startstop
  • 安全确认 :停止和重启操作会进行二次确认,防止误操作(可通过 -f 跳过)

使用示例:

bash 复制代码
./hive-metastore.sh            # 默认查看状态
./hive-metastore.sh 启动       # 启动服务
./hive-metastore.sh stop       # 停止服务
./hive-metastore.sh 菜单       # 进入交互菜单
./hive-metastore.sh 帮助       # 查看全部命令

创建脚本文件后,记得授予执行权限:

bash 复制代码
chmod +x /root/hive-metastore.sh

附录:Hive Metastore 管理脚本(原样)

以下是完整的管理脚本内容,逐字保留,未做任何修改。

bash 复制代码
#!/bin/bash
set -uo pipefail

# ===== 配置区 =====
SERVICE_NAME="hive-metastore"
PORT="9083"
WAIT_TIMEOUT=30          # 端口等待超时(秒)
SCRIPT_PATH="$(readlink -f "$0")"

# ===== 颜色 =====
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'  # No Color

ok()   { echo -e "${GREEN}[✔]${NC} $*"; }
fail() { echo -e "${RED}[✘]${NC} $*"; }
info() { echo -e "${CYAN}[ℹ]${NC} $*"; }
warn() { echo -e "${YELLOW}[⚠]${NC} $*"; }

# ===== 权限检查 =====
require_root() {
    if [[ $EUID -ne 0 ]]; then
        fail "此操作需要 root 权限,请使用 sudo 或以 root 身份运行"
        exit 1
    fi
}

# ===== 端口等待 =====
wait_for_port() {
    local timeout=$1
    local elapsed=0
    info "等待端口 ${PORT} 就绪(超时 ${timeout}s)..."
    while (( elapsed < timeout )); do
        if ss -lntp 2>/dev/null | grep -q ":${PORT} "; then
            ok "端口 ${PORT} 已就绪(耗时 ${elapsed}s)"
            return 0
        fi
        sleep 1
        (( elapsed++ ))
    done
    fail "端口 ${PORT} 在 ${timeout}s 内未就绪"
    return 1
}

# ===== 二次确认 =====
confirm_action() {
    local action="$1"
    if [[ "${FORCE:-}" == "true" ]]; then
        return 0
    fi
    if [ ! -t 0 ]; then
        # 非交互终端,跳过确认
        return 0
    fi
    read -r -p "确认要${action} ${SERVICE_NAME}?[y/N] " answer
    case "$answer" in
        [yY]|[yY][eE][sS]) return 0 ;;
        *) info "已取消"; exit 0 ;;
    esac
}

# ===== 标题 =====
show_title() {
    echo -e "${BOLD}========================================"
    echo " Hive Metastore 管理脚本"
    echo " 服务名: ${SERVICE_NAME}"
    echo " 端口:   ${PORT}"
    echo -e "========================================${NC}"
}

# ===== 状态 =====
show_status() {
    echo
    info "服务状态:"
    if systemctl is-active --quiet "${SERVICE_NAME}"; then
        ok "${SERVICE_NAME} 正在运行"
    else
        fail "${SERVICE_NAME} 未运行"
    fi

    # 详细状态
    systemctl status "${SERVICE_NAME}" --no-pager -l 2>/dev/null || true
    echo

    info "端口 ${PORT} 监听情况:"
    local port_info
    port_info=$(ss -lntp 2>/dev/null | grep ":${PORT} ")
    if [[ -n "$port_info" ]]; then
        ok "端口 ${PORT} 正在监听"
        echo "$port_info"
    else
        warn "未发现端口 ${PORT} 监听"
    fi

    echo
    info "开机自启:"
    if systemctl is-enabled --quiet "${SERVICE_NAME}" 2>/dev/null; then
        ok "已启用开机自启"
    else
        warn "未启用开机自启"
    fi
}

# ===== 启动 =====
start_service() {
    require_root
    if systemctl is-active --quiet "${SERVICE_NAME}"; then
        warn "${SERVICE_NAME} 已经在运行中,无需重复启动"
        show_status
        return 0
    fi
    info "正在启动 ${SERVICE_NAME} ..."
    if systemctl start "${SERVICE_NAME}"; then
        wait_for_port "${WAIT_TIMEOUT}"
        ok "启动命令已执行"
    else
        fail "启动失败,请检查日志: journalctl -u ${SERVICE_NAME} -n 50"
        return 1
    fi
    show_status
}

# ===== 停止 =====
stop_service() {
    require_root
    confirm_action "停止"
    if ! systemctl is-active --quiet "${SERVICE_NAME}"; then
        warn "${SERVICE_NAME} 当前未运行"
        return 0
    fi
    info "正在停止 ${SERVICE_NAME} ..."
    if systemctl stop "${SERVICE_NAME}"; then
        ok "服务已停止"
    else
        fail "停止失败"
        return 1
    fi
    systemctl status "${SERVICE_NAME}" --no-pager -l 2>/dev/null || true
}

# ===== 重启 =====
restart_service() {
    require_root
    confirm_action "重启"
    info "正在重启 ${SERVICE_NAME} ..."
    if systemctl restart "${SERVICE_NAME}"; then
        wait_for_port "${WAIT_TIMEOUT}"
        ok "重启命令已执行"
    else
        fail "重启失败,请检查日志: journalctl -u ${SERVICE_NAME} -n 50"
        return 1
    fi
    show_status
}

# ===== 日志 =====
follow_logs() {
    info "实时查看 ${SERVICE_NAME} 日志,按 Ctrl+C 退出"
    journalctl -u "${SERVICE_NAME}" -f
}

recent_logs() {
    local lines="${1:-50}"
    info "最近 ${lines} 行日志"
    journalctl -u "${SERVICE_NAME}" -n "${lines}" --no-pager
}

# ===== 版本 =====
show_version() {
    info "Hive 版本信息"
    if command -v hive &>/dev/null; then
        hive --version
    else
        fail "未找到 hive 命令,请检查 PATH 或 Hive 安装"
    fi
}

# ===== 端口 =====
show_port() {
    info "端口 ${PORT} 监听情况"
    local port_info
    port_info=$(ss -lntp 2>/dev/null | grep ":${PORT} ")
    if [[ -n "$port_info" ]]; then
        ok "端口 ${PORT} 正在监听"
        echo "$port_info"
    else
        warn "未发现端口 ${PORT} 监听"
    fi
}

# ===== 开机自启管理 =====
enable_service() {
    require_root
    if systemctl enable "${SERVICE_NAME}" 2>/dev/null; then
        ok "已启用 ${SERVICE_NAME} 开机自启"
    else
        fail "启用开机自启失败"
    fi
}

disable_service() {
    require_root
    confirm_action "禁用开机自启"
    if systemctl disable "${SERVICE_NAME}" 2>/dev/null; then
        ok "已禁用 ${SERVICE_NAME} 开机自启"
    else
        fail "禁用开机自启失败"
    fi
}

# ===== 环境信息 =====
show_env() {
    info "环境信息"
    echo "  脚本路径:  ${SCRIPT_PATH}"
    echo "  当前用户:  $(whoami)"
    echo "  主机名:    $(hostname)"
    echo "  系统时间:  $(date '+%Y-%m-%d %H:%M:%S %Z')"
    echo

    if command -v java &>/dev/null; then
        echo "  Java 版本:"
        java -version 2>&1 | sed 's/^/    /'
    else
        warn "  未找到 java 命令"
    fi
    echo

    if [[ -n "${HIVE_HOME:-}" ]]; then
        echo "  HIVE_HOME: ${HIVE_HOME}"
    else
        warn "  HIVE_HOME 未设置"
    fi

    if [[ -n "${HADOOP_HOME:-}" ]]; then
        echo "  HADOOP_HOME: ${HADOOP_HOME}"
    else
        warn "  HADOOP_HOME 未设置"
    fi
}

# ===== 帮助 =====
show_help() {
    cat <<USAGE
用法:
  ${SCRIPT_PATH} 启动          启动服务
  ${SCRIPT_PATH} 停止          停止服务(会二次确认)
  ${SCRIPT_PATH} 重启          重启服务(会二次确认)
  ${SCRIPT_PATH} 状态          查看服务状态(默认)
  ${SCRIPT_PATH} 日志          实时查看日志
  ${SCRIPT_PATH} 最近日志 [N]  查看最近 N 行日志(默认 50)
  ${SCRIPT_PATH} 端口          查看端口监听
  ${SCRIPT_PATH} 版本          查看 Hive 版本
  ${SCRIPT_PATH} 自启          启用开机自启
  ${SCRIPT_PATH} 禁自启        禁用开机自启
  ${SCRIPT_PATH} 环境          查看环境信息
  ${SCRIPT_PATH} 菜单          进入交互菜单
  ${SCRIPT_PATH} 帮助          显示此帮助

也支持英文参数:
  start | stop | restart | status | log | logs [N]
  port | version | enable | disable | env | menu | help

选项:
  -f, --force    跳过二次确认(停止/重启时)

说明:
  不带参数时,默认执行"状态"。
  只有传"菜单"或"menu"时才进入交互菜单。
USAGE
}

# ===== 菜单 =====
show_menu() {
    if [ ! -t 0 ]; then
        fail "当前不是可交互终端,不能进入菜单模式。"
        info "请改用参数方式,例如:${SCRIPT_PATH} 状态"
        exit 1
    fi

    # 菜单内操作跳过确认
    FORCE="true"

    while true; do
        echo
        show_title
        echo -e " ${GREEN}1${NC}. 启动服务"
        echo -e " ${RED}2${NC}. 停止服务"
        echo -e " ${YELLOW}3${NC}. 重启服务"
        echo -e " ${CYAN}4${NC}. 查看状态"
        echo -e " ${CYAN}5${NC}. 查看实时日志"
        echo -e " ${CYAN}6${NC}. 查看最近日志"
        echo -e " ${CYAN}7${NC}. 查看端口"
        echo -e " ${CYAN}8${NC}. 查看 Hive 版本"
        echo -e " ${CYAN}9${NC}. 查看环境信息"
        echo -e " ${GREEN}a${NC}. 启用开机自启"
        echo -e " ${RED}b${NC}. 禁用开机自启"
        echo -e " ${BOLD}0${NC}. 退出"
        echo "----------------------------------------"

        if ! read -r -p "请输入编号: " choice; then
            echo
            warn "输入流已结束,退出菜单。"
            exit 1
        fi

        case "$choice" in
            1) start_service ;;
            2) stop_service ;;
            3) restart_service ;;
            4) show_status ;;
            5) follow_logs ;;
            6) recent_logs ;;
            7) show_port ;;
            8) show_version ;;
            9) show_env ;;
            a|A) enable_service ;;
            b|B) disable_service ;;
            0) echo "退出"; exit 0 ;;
            *) warn "无效输入,请重新选择" ;;
        esac

        echo
        echo -e "${CYAN}按回车继续...${NC}"
        read -r
    done
}

# ===== 解析 -f / --force =====
FORCE="false"
ARGS=()
for arg in "$@"; do
    case "$arg" in
        -f|--force) FORCE="true" ;;
        *) ARGS+=("$arg") ;;
    esac
done
set -- "${ARGS[@]+"${ARGS[@]}"}"


# ===== 主入口 =====
case "${1:-}" in
    "")
        # 无参数:显示状态 + 快捷提示
        show_status
        echo
        echo -e "${BOLD}快捷操作(中/英参数二选一):${NC}"
        echo -e "  $0 ${GREEN}启动${NC} | ${GREEN}start${NC}       启动服务"
        echo -e "  $0 ${RED}停止${NC} | ${RED}stop${NC}         停止服务"
        echo -e "  $0 ${YELLOW}重启${NC} | ${YELLOW}restart${NC}      重启服务"
        echo -e "  $0 ${CYAN}日志${NC} | ${CYAN}log${NC}          查看实时日志"
        echo -e "  $0 ${CYAN}菜单${NC} | ${CYAN}menu${NC}         进入交互菜单"
        echo -e "  $0 ${CYAN}帮助${NC} | ${CYAN}help${NC}         查看全部命令"
        ;;
    启动|start)       start_service ;;
    停止|stop)        stop_service ;;
    重启|restart)     restart_service ;;
    状态|status)      show_status ;;
    日志|log)         follow_logs ;;
    最近日志|logs)    recent_logs "${2:-50}" ;;
    端口|port)        show_port ;;
    版本|version)     show_version ;;
    自启|enable)      enable_service ;;
    禁自启|disable)   disable_service ;;
    环境|env)         show_env ;;
    菜单|menu)        show_menu ;;
    帮助|help|-h|--help) show_help ;;
    *)
        fail "不支持的参数: $1"
        echo
        show_help
        exit 1
        ;;
esac
相关推荐
SelectDB技术团队4 小时前
易车 × Apache Doris:构建湖仓一体新架构,加速 AI 业务融合实践
数据仓库·人工智能·数据分析·agent·apache doris·mcp·易车
王的宝库6 小时前
MapReduce / Hive / Pig :从底层批处理到 SQL/脚本落地
hive·hadoop·sql·mapreduce
IT从业者张某某7 小时前
Docker部署Hadoop-01-Docker安装
hadoop·docker·eureka
小哥哥咯8 小时前
数据仓库维度建模思维导图—— 基于《The Data Warehouse Toolkit, 3rd Edition》(第三版修订版)
大数据·数据仓库
forever_ai8 小时前
数据仓库ods层文档模版
数据仓库
IT从业者张某某8 小时前
Docker部署Hadoop-04-把Hadoop容器导出导入并使用
java·hadoop·docker
升职佳兴9 小时前
Hadoop 三节点集群环境变量工程化:从 /etc/profile 迁移到 /etc/profile.d/ 全过程记录
大数据·hadoop·分布式
IT从业者张某某9 小时前
Docker部署Hadoop-05-配置Docker容器的命名卷和挂载卷
hadoop·docker·容器
网络工程小王18 小时前
【大数据技术详解】——HIVE技术(学习笔记)
大数据·hive·hadoop