Linux下ClickHouse数据仓库故障系统化诊断脚本

bash 复制代码
#!/bin/bash
# =============================================================================
# ClickHouse 故障系统化诊断脚本【增强可手工单段复制版】
# 适配:CentOS/RHEL / Ubuntu Debian Linux
# 执行用户:root 最高权限(推荐)/ clickhouse 普通用户
# 核心优化:每一段代码可单独复制到终端运行,关键PID/分区/磁盘ID高亮输出、单独存文件方便复制留存
# 目标:快速摸清"如何死""谁干的",同时保留现场,为事后根因分析提供完整拼图。
# 哲学指导:
#   本体论  - 准确区分故障实体(Killed/Exited/不可中断睡眠)。
#   奥卡姆剃刀 - 从最可能的根因切入,不铺张浪费。
#   实用主义 - 先保护现场,再采集一切可能有用的信息。
#   整体论   - 提示关联上游、重试风暴、全链路视角。
#   无为而治 - 检查系统是否已具备自愈或隔离能力。
# =============================================================================
set -uo pipefail

# ====================== 全局常量定义【可单独复制修改】 ======================
# 【单块可复制终端执行】
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
WHITE='\033[1;37m'
NC='\033[0m'

# ClickHouse 路径(按需自行修改)
CK_LOG_DIR="/var/log/clickhouse-server"
CK_DATA_DIR="/var/lib/clickhouse"
CK_CONFIG_DIR="/etc/clickhouse-server"
CK_ERR_LOG="${CK_LOG_DIR}/clickhouse-server.err.log"
CK_MAIN_LOG="${CK_LOG_DIR}/clickhouse-server.log"
CK_QUERY_LOG="${CK_LOG_DIR}/clickhouse-server-query.log"
CK_SERVICE="clickhouse-server"
CK_PORT=8123
CK_TCP_PORT=9000

# 诊断存档目录
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
REPORT_DIR="/tmp/ck_diag_${TIMESTAMP}"
mkdir -p "$REPORT_DIR"
# 专门存放可复制的ID/名称文件
KEY_INFO_FILE="${REPORT_DIR}/key_copy_info.txt"
> "$KEY_INFO_FILE"
echo "===== 所有可复制关键信息汇总 =====" > "$KEY_INFO_FILE"

# ====================== 通用工具函数【可单独复制】 ======================
# 【单块可复制终端执行】
section() {
    echo -e "\n${CYAN}========== $1 ==========${NC}"
    echo "【模块标题】$1" >> "$KEY_INFO_FILE"
}
warn() {
    echo -e "${YELLOW}[!] $1${NC}"
    echo "[警告] $1" >> "$KEY_INFO_FILE"
}
ok() {
    echo -e "${GREEN}[✓] $1${NC}"
}
fail() {
    echo -e "${RED}[✗] $1${NC}"
    echo "[严重异常] $1" >> "$KEY_INFO_FILE"
}
# 打印可复制关键值(高亮,单独存入汇总文件)
print_copy_val() {
    local label=$1
    local val=$2
    echo -e "${WHITE}【可复制 $label 】: ${RED}$val${NC}"
    echo "$label = $val" >> "$KEY_INFO_FILE"
}
# 手工检查引导
manual_check() {
    echo -e "${YELLOW}[→ 手工检查点] $1${NC}"
    echo "手工操作指引:$1" >> "$KEY_INFO_FILE"
}

# ====================== 初始化输出【可单独复制】 ======================
# 【单块可复制终端执行】
echo -e "${GREEN}=== ClickHouse 故障诊断开始 [${TIMESTAMP}] ===${NC}"
HOSTNAME_VAL=$(hostname)
print_copy_val "主机名" "$HOSTNAME_VAL"
echo "诊断存档目录:$REPORT_DIR"
print_copy_val "诊断包路径" "$REPORT_DIR"

# =============================================================================
# 1. 保护第一现场:备份日志、系统快照【单块可复制终端执行】
# =============================================================================
section "1. 保留现场:备份关键日志与系统快照"
# 日志备份
cp -f "$CK_ERR_LOG" "$REPORT_DIR/clickhouse-server.err.log.bak" 2>/dev/null && ok "已备份错误日志" || warn "备份错误日志失败"
cp -f "$CK_MAIN_LOG" "$REPORT_DIR/clickhouse-server.log.bak" 2>/dev/null && ok "已备份主日志" || warn "备份主日志失败"
cp -f "$CK_QUERY_LOG" "$REPORT_DIR/clickhouse-server-query.log.bak" 2>/dev/null && ok "已备份查询日志" || warn "查询日志不存在,跳过"

# 系统内核快照
dmesg -T > "$REPORT_DIR/dmesg_output.txt" 2>/dev/null && ok "已备份内核环形缓冲区" || warn "dmesg 备份失败"
top -b -n 1 > "$REPORT_DIR/top_snapshot.txt" 2>/dev/null
free -h > "$REPORT_DIR/mem_snapshot.txt"
vmstat 1 3 > "$REPORT_DIR/vmstat_io_sample.txt"

# 磁盘快照
df -h > "$REPORT_DIR/df_snapshot.txt"
df -i > "$REPORT_DIR/df_inode_snapshot.txt"

manual_check "请确认这些备份文件是否已复制到安全位置。**切勿在未保留现场的情况下执行重启**。"

# =============================================================================
# 2. 进程与服务状态识别,提取PID【单块可复制终端执行,重点留存PID】
# =============================================================================
section "2. 进程与服务状态(本体论:故障实体识别)"
PID=""
if pidof clickhouse-server >/dev/null; then
    PID=$(pidof clickhouse-server | awk '{print $1}')
    print_copy_val "ClickHouse 运行PID" "$PID"
    ok "进程存在,PID: $PID"
    
    # 进程状态
    STAT=$(ps -o state= -p "$PID" 2>/dev/null | tr -d ' ')
    print_copy_val "进程运行状态码" "$STAT"
    if [[ "$STAT" == "D" ]]; then
        fail "进程处于不可中断睡眠状态(D),通常意味着磁盘 I/O 严重阻塞。"
        manual_check "请用 iostat -x 1 观察磁盘读写,确认是否有硬件故障或存储卡死。"
    elif [[ "$STAT" == "Z" ]]; then
        fail "进程为僵尸状态(Z),已无法服务。"
    fi

    # 进程资源快照
    ps -efH | grep clickhouse > "$REPORT_DIR/ck_process_tree.txt"
    ls -lh /proc/$PID/fd/ | head -100 > "$REPORT_DIR/pid_fd_list.txt"
else
    fail "ClickHouse 进程未运行。PID为空"
    print_copy_val "ClickHouse 运行PID" "无运行进程"
fi

echo ""
echo "检查 systemd 服务状态(最关键的状态码和信号)..."
systemctl status "$CK_SERVICE" --no-pager -l | tee "$REPORT_DIR/systemctl_status.txt"
# 提取关键退出信息
EXIT_INFO=$(systemctl status "$CK_SERVICE" --no-pager -l 2>/dev/null | grep -E "Active:|Main PID:|signal=|code=exited" | head -5)
echo "$EXIT_INFO" > "$REPORT_DIR/service_exit_info.txt"
print_copy_val "服务退出关键信息" "$EXIT_INFO"

if echo "$EXIT_INFO" | grep -q "killed.*signal=KILL"; then
    fail "判定:进程被 SIGKILL 信号强制杀死(他杀)。"
    manual_check "常见原因:OOM Killer 或 systemd MemoryMax 限制。请立即查看第3节和第7节。"
elif echo "$EXIT_INFO" | grep -q "code=exited, status=1"; then
    fail "判定:进程主动退出(自杀),状态码1,一般伴随错误日志。"
    manual_check "请重点排查第5节日志中的 FATAL 或 ERROR。"
elif echo "$EXIT_INFO" | grep -q "active (running)"; then
    ok "服务状态显示 running,但进程可能无响应。"
    manual_check "可能是 hang 住,需检查第4节 CPU 和第6节系统限制,并尝试连接 8123 端口看是否响应。"
else
    warn "服务状态异常,请查看上面输出。"
fi

# 端口连通性检测(新增)
section "2.1 端口监听检测"
ss -tulnp | grep -E "$CK_PORT|$CK_TCP_PORT" > "$REPORT_DIR/port_listen.txt"
if grep -q "$CK_PORT" "$REPORT_DIR/port_listen.txt";then
    ok "HTTP 8123 端口正常监听"
else
    fail "HTTP 8123 端口无监听,服务未正常启动"
fi

# =============================================================================
# 3. 内存/OOM 排查,留存OOM杀死的PID【单块可复制终端执行】
# =============================================================================
section "3. 内存压力与 OOM 检查(奥卡姆剃刀:直击最大可能)"
echo "检查内核 OOM Killer 记录..."
dmesg -T | grep -i "out of memory\|killed process" | tail -10 | tee "$REPORT_DIR/oom_killer.txt"
OOM_RAW=$(dmesg -T | grep -i "killed process.*clickhouse" | tail -1)
echo "$OOM_RAW" > "$REPORT_DIR/oom_pid_list.txt"

if [[ -n "$OOM_RAW" ]]; then
    OOM_KILL_PID=$(echo "$OOM_RAW" | awk '{print $4}' | sed 's/,//')
    print_copy_val "OOM 杀死的进程PID" "$OOM_KILL_PID"
    fail "发现 OOM Killer 曾杀死 ClickHouse 进程!"
    manual_check "请确认被杀 PID 与之前记录的 PID 是否一致。配合 Grafana 内存趋势图,找到内存冲高的具体时间。"
else
    ok "未在 dmesg 中发现 clickhouse 被 OOM 杀死的关键字(可能被清理或非 OOM)。"
fi

# Swap内存检查
free -h | grep Swap >> "$REPORT_DIR/mem_snapshot.txt"
manual_check "Swap占用过高代表物理内存严重不足,极易触发OOM"

echo ""
echo "检查 ClickHouse 自身的内存限制配置..."
grep -r "max_server_memory_usage\|max_memory_usage\|max_memory_usage_for_all_queries" $CK_CONFIG_DIR/*.xml 2>/dev/null | grep -v '#' > "$REPORT_DIR/ck_memory_config.txt"
cat "$REPORT_DIR/ck_memory_config.txt"
manual_check "请确认上述参数值是否超过物理内存的 80%,或接近 systemd 的 MemoryMax。如果是,容易触发 OOM。建议 max_server_memory_usage 设为物理内存的 80%。"

echo ""
echo "检查 systemd 对服务的资源限制(可能直接发送 SIGKILL)..."
systemctl show "$CK_SERVICE" | grep -i "MemoryMax\|MemoryLimit\|MemoryHigh" | tee "$REPORT_DIR/systemd_memory.txt"

# =============================================================================
# 4. 磁盘空间、挂载、硬件IO检查【留存挂载盘名称】
# =============================================================================
section "4. 磁盘及存储检查"
echo "挂载点空间使用率..."
df -h | tee "$REPORT_DIR/df_h.txt"
echo ""
echo "inode 使用率..."
df -i | tee "$REPORT_DIR/df_i.txt"

# 提取ClickHouse数据目录对应磁盘
CK_DISK=$(df "$CK_DATA_DIR" | tail -1 | awk '{print $1}')
CK_MOUNT_POINT=$(df "$CK_DATA_DIR" | tail -1 | awk '{print $6}')
print_copy_val "ClickHouse数据盘设备" "$CK_DISK"
print_copy_val "数据目录挂载点" "$CK_MOUNT_POINT"
echo "磁盘挂载信息:$CK_DISK $CK_MOUNT_POINT" > "$REPORT_DIR/disk_mount_info.txt"

manual_check "请找到 ClickHouse 数据目录对应的挂载点。如果 Use% 达 100% 或 inode 满,服务会因 'No space left' 退出。"

echo ""
echo "数据目录读写权限与挂载状态..."
if mount | grep "$CK_DATA_DIR" | grep -q "ro,"; then
    fail "数据目录以只读(ro)挂载!这会导致所有写入失败,服务自杀。"
    manual_check "只读挂载通常是文件系统遇到严重错误后内核的保护动作。请立即检查 dmesg 中的 I/O 错误。"
else
    ok "数据目录未以只读挂载。"
fi

# 实时IO采样
iostat -x 1 2 > "$REPORT_DIR/disk_io_sample.txt"
ok "已采集2秒磁盘IO负载快照,查看%iowait判断IO阻塞"

echo ""
echo "检查磁盘硬件健康(SMART)..."
if command -v smartctl &>/dev/null; then
    ROOT_DISK=$(df / | tail -1 | awk '{print $1}' | sed 's/[0-9]*$//')
    smartctl -H "$ROOT_DISK" 2>/dev/null | tee "$REPORT_DIR/smartctl.txt"
    manual_check "查看 SMART 整体健康状况,若 FAILED 或大量重分配扇区,需立即更换硬盘。"
else
    warn "smartctl 未安装,跳过磁盘硬件检查。执行 yum install smartmontools / apt install smartmontools 安装"
fi

# =============================================================================
# 5. 日志错误速查,提取损坏分区名【留存异常分区/表名】
# =============================================================================
section "5. ClickHouse 日志错误摘要"
BROKEN_PART_RAW=""
if [ -f "$CK_ERR_LOG" ]; then
    echo "从错误日志尾部提取关键信息..."
    tail -200 "$CK_ERR_LOG" | grep -E "Error|Fatal|Exception|No space|Memory limit|Too many open files|Checksum mismatch|Corrupted|broken part" | tee "$REPORT_DIR/log_errors.txt"
    
    # 提取损坏分区信息
    BROKEN_PART_RAW=$(grep -E "Checksum mismatch|Corrupted data|parts.*broken" "$REPORT_DIR/log_errors.txt" | head -3)
    echo "$BROKEN_PART_RAW" > "$REPORT_DIR/broken_part.txt"
    if [[ -n "$BROKEN_PART_RAW" ]];then
        print_copy_val "损坏分区/表原始日志" "$BROKEN_PART_RAW"
    fi

    if grep -q "No space left on device" "$REPORT_DIR/log_errors.txt"; then
        fail "明确错误:磁盘空间不足,请返回第4节。"
    fi
    if grep -q "Memory limit" "$REPORT_DIR/log_errors.txt"; then
        fail "明确错误:超出内存限制,请返回第3节。"
    fi
    if grep -q "Too many open files" "$REPORT_DIR/log_errors.txt"; then
        fail "明确错误:文件描述符耗尽,请检查第6节。"
    fi
    if grep -q "Checksum mismatch\|Corrupted data" "$REPORT_DIR/log_errors.txt"; then
        fail "明确错误:数据文件损坏,请务必检查硬件,并从副本恢复或使用 DETACH 分区。"
        manual_check "需手工对损坏表执行 CHECK TABLE 或从健康副本同步。千万不要盲目启动后做全量合并。"
    fi
else
    warn "错误日志文件不存在。"
fi

# =============================================================================
# 6. 系统资源限制:文件句柄
# =============================================================================
section "6. 文件描述符与系统限制"
if [[ -n "${PID}" && -d "/proc/$PID" ]]; then
    echo "进程当前打开文件数上限:"
    FD_LIMIT=$(grep "open files" /proc/$PID/limits)
    echo "$FD_LIMIT" | tee "$REPORT_DIR/limits.txt"
    print_copy_val "进程文件句柄限制" "$FD_LIMIT"
else
    echo "进程不在运行,检查 systemd 服务配置的限制:"
    FD_LIMIT=$(systemctl show "$CK_SERVICE" | grep "LimitNOFILE")
    echo "$FD_LIMIT" | tee "$REPORT_DIR/limits.txt"
    print_copy_val "systemd 文件句柄限制" "$FD_LIMIT"
fi
manual_check "ClickHouse 建议 LimitNOFILE 至少 262144。若值过小,高负载下会因文件描述符耗尽而拒绝服务。"

echo ""
echo "检查系统中全局打开文件数..."
sysctl fs.file-nr | tee -a "$REPORT_DIR/limits.txt"
manual_check "输出为 (已分配 未使用 上限)。若已分配接近上限,整个系统都可能受到影响。"

# =============================================================================
# 7. CPU、内核软死锁、网络连接诊断
# =============================================================================
section "7. CPU 与内核异常、网络连接"
echo "查看系统负载..."
uptime
uptime > "$REPORT_DIR/loadavg.txt"
echo ""
echo "搜索内核软死锁(soft lockup)..."
dmesg -T | grep -i "soft lockup" | tail -5 | tee "$REPORT_DIR/soft_lockup.txt"
if [ -s "$REPORT_DIR/soft_lockup.txt" ]; then
    fail "检测到 CPU 软死锁,可能由极端查询或内核缺陷触发。"
    manual_check "需分析崩溃时的具体查询(查询日志),并排查硬件或内核版本。"
else
    ok "未发现软死锁。"
fi

# 新增网络连接统计
section "7.1 TCP 连接状态统计"
ss -s > "$REPORT_DIR/tcp_conn_stat.txt"
cat "$REPORT_DIR/tcp_conn_stat.txt"
manual_check "TIME_WAIT 数量巨大代表上游重试风暴,需要应用增加连接池复用、退避重试"

# =============================================================================
# 8. 集群/副本/分区健康检查(单机可跳过,集群必执行)
# =============================================================================
section "8. 集群复制、分区完整性检测"
# 生成可执行SQL文件,手工登录clickhouse-client执行
cat > "$REPORT_DIR/ck_check_sql.sql" <<EOF
-- 复制队列积压查看
SELECT database, table, partition, name, is_detached, reason FROM system.replication_queue LIMIT 100;
-- 损坏分区查询
SELECT database, table, partition, name FROM system.parts WHERE active=0 AND broken=1;
-- 当前长查询
SELECT query_id, user, database, query, elapsed FROM system.processes ORDER BY elapsed DESC LIMIT 20;
EOF
ok "已生成集群诊断SQL:$REPORT_DIR/ck_check_sql.sql"
manual_check "执行命令复制:clickhouse-client -d default -m < $REPORT_DIR/ck_check_sql.sql,提取broken分区名称留存"

# =============================================================================
# 9. 上下游重试风暴、全链路人工排查指引
# =============================================================================
section "9. 整体论:关联上下游与重试风暴检查"
manual_check "**整体论排查建议(需人工执行)**:"
echo "  1. 检查上游应用或网关的连接池、重试策略。若无指数退避和最大重试限制,可能造成重试风暴,直接压垮刚恢复的 ClickHouse。"
echo "  2. 登录监控系统(如Grafana),将 ClickHouse 中断时间点与上游 QPS、应用错误率、网络流量进行联动对比。"
echo "  3. 若为集群节点,执行 SELECT * FROM system.replication_queue 查看积压情况,避免恢复后合并风暴。"
echo "  4. 查看 system.query_log 或应用侧慢查询记录,定位中断前执行的最后几个重量级查询。"

# =============================================================================
# 10. 自愈、资源隔离配置检查
# =============================================================================
section "10. 无为而治:自愈与保护机制检查"
MEM_CFG_COUNT=$(grep -r "max_server_memory_usage" $CK_CONFIG_DIR/*.xml 2>/dev/null | grep -v '#' | wc -l)
DISK_CFG_COUNT=$(grep -r "storage_configuration\|disks" $CK_CONFIG_DIR/config.xml 2>/dev/null | wc -l)
RESTART_POLICY=$(systemctl show "$CK_SERVICE" -p Restart | awk -F= '{print $2}')

echo -n "  max_server_memory_usage: "
if [ $MEM_CFG_COUNT -gt 0 ]; then echo "已配置"; else echo "未配置 (危险)"; fi
echo -n "  磁盘分层存储storage_configuration: "
if [ $DISK_CFG_COUNT -gt 0 ]; then echo "已配置,可实现故障隔离"; else echo "未配置"; fi
print_copy_val "systemd服务自动重启策略" "$RESTART_POLICY"

manual_check "建议 Restart 设为 on-failure,并搭配合理的 RestartSec。但请注意,盲目自动重启可能在数据损坏时反复失败。最好配置健康检查和故障转移。"

# =============================================================================
# 最终汇总输出
# =============================================================================
section "诊断总结"
echo -e "${GREEN}==================== 诊断完成 ====================${NC}"
echo "1. 完整诊断数据包目录:$REPORT_DIR"
print_copy_val "关键信息汇总文件(直接复制所有ID)" "$KEY_INFO_FILE"
echo "2. 核心可复制信息清单文件:"
echo "   - PID列表:$REPORT_DIR/pid_info.txt"
echo "   - OOM杀死进程:$REPORT_DIR/oom_pid_list.txt"
echo "   - 数据磁盘信息:$REPORT_DIR/disk_mount_info.txt"
echo "   - 损坏分区记录:$REPORT_DIR/broken_part.txt"
echo "   - 集群检测SQL:$REPORT_DIR/ck_check_sql.sql"
echo ""
echo -e "${YELLOW}故障处理优先级顺序:${NC}"
echo "① 磁盘100%满 → 清理/扩容后重启"
echo "② OOM被系统杀死 → 调整内存参数、优化大查询"
echo "③ 数据分区损坏 → DETACH损坏分区,从副本同步,禁止暴力重启"
echo "④ 文件句柄耗尽 → 修改LimitNOFILE=262144+ 重启服务"
echo "⑤ IO阻塞D状态进程 → 检查磁盘硬件/扩容IO带宽"
echo ""
echo -e "${CYAN}快速提取关键ID命令(终端直接复制执行):cat $KEY_INFO_FILE${NC}"
echo -e "${GREEN}=================================================${NC}"
相关推荐
RisunJan12 小时前
Linux命令-rlogin(远程登录)
linux·运维
深圳恒讯13 小时前
菲律宾云服务器与传统VPS的架构差异
运维·服务器·架构
蓝天下的守望者13 小时前
svt_apb_if里的宏定义问题
运维·服务器·数据库
小林ixn13 小时前
从“玩具工具”到“跨语言利器”:MCP 协议实战解析
运维·服务器·网络
小顿的企业观察14 小时前
中企出海战略规划,正在从“走出去”转向“走进去”
大数据·运维·人工智能·产品运营·制造
数智化转型推荐官14 小时前
2026统一身份管理系统五大发展趋势解读
java·运维·微服务
看菜鸡互14 小时前
探索用 SlideML 让大模型生成 PPT 的实验方法
java·运维
一孤程14 小时前
Airtest自动化测试第五篇:小程序与Web测试——跨平台自动化全覆盖
前端·自动化测试·小程序·自动化·测试·airtest
大飞记Python16 小时前
Linux命令速查手册(测试开发4年实战总结,附PDF)
linux·网络·pdf