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}"
相关推荐
曦尧6 分钟前
Microsoft VibeVoice:从「分块识别」到「整段理解」的语音 AI 范式跃迁
ai·自动化
江湖有缘20 分钟前
Docker实战 | 使用Docker部署Donetick任务与家务管理应用
运维·docker·容器
实心儿儿29 分钟前
Linux —— 应用层协议HTTP
linux·网络·http
流星白龙33 分钟前
【Docker】4.NameSpace空间隔离实战
java·运维·docker
数字融合1 小时前
黎阳之光一数字孪生多能源可视化系统管理技术
人工智能·自动化·virtualenv
有续技术1 小时前
哈斯 (Haas) 机床 IP、端口号配置内容总结
运维·服务器
小张同学a.2 小时前
LAMP架构2
linux·运维·网络·架构·负载均衡
千河殇 多想和她2 小时前
如何在Reaonix中使用CodeGraph以及CodeGraph效果实测经验分享
linux·运维·ubuntu
VortMall2 小时前
出海引流运维双升级|VortMall微服务商城系统v1.3.12 升级更新
运维·微服务·facebook
心念枕惊2 小时前
新写了个直播录制工具,可录制抖音快手斗鱼直播
运维·服务器·数据库