提交 68c8b3c8 authored 作者: PGY's avatar PGY

feat(monitor): 添加外部API服务监测与自愈脚本V2.1,支持严格匹配进程

上级 0f872363
......@@ -77,7 +77,7 @@ start_service() {
nohup "./$(basename "$script_path")" > startup.log 2>&1 &
# 等待一小段时间,让进程有机会启动
sleep 3
sleep 600
# 再次检查进程是否启动成功
if is_process_running "$service_name"; then
......
......@@ -105,7 +105,7 @@ start_service() {
nohup "./$(basename "$script_path")" > startup.log 2>&1 &
# 等待一小段时间,让进程有机会启动
sleep 3
sleep 600
# 再次检查进程是否启动成功
if is_process_running "$service_name"; then
......
#!/bin/bash
#===============================================================================
# 脚本名称:monitor_external_api_services.sh
# 功能描述:外部 API 服务监测与自愈脚本 (严格精确匹配版)
# 版本:V2.1
# 创建日期:2026-03-05
#
# 核心策略:
# 1. Java Jar 包:精确匹配命令行中包含特定 Jar 文件名的 Java 进程。
# 2. 普通进程:严格使用 pgrep -x 进行全名匹配,拒绝模糊搜索。
# 3. 启动验证:轮询检测,确保进程真正拉起。
#
# 使用方法:
# chmod +x monitor_external_api_services.sh
# */5 * * * * /opt/scripts/monitor_external_api_services.sh >> /var/log/cron_monitor.log 2>&1
#===============================================================================
# --- 配置区域 ---
set -euo pipefail
# 日志配置
LOG_DIR="/var/log/scripts"
LOG_FILE="${LOG_DIR}/monitor_external_api_services.log"
# 监控参数
STARTUP_TIMEOUT=600 # 最大等待启动时间 (秒)
POLL_INTERVAL=3 # 状态轮询间隔 (秒)
STOP_WAIT_TIME=30 # 停止残留进程的等待时间 (秒)
# 定义要监控的服务
# 格式: "进程标识:目录路径:启动脚本路径"
# 注意:
# - 对于 Jar 包,'进程标识' 必须是 Jar 文件的完整名称 (如 app-1.0.jar)
# - 对于普通程序,'进程标识' 必须是准确的进程名 (如 nginx, malan)
SERVICES=(
"ubains-meeting-api-1.0-SNAPSHOT.jar:/var/www/java/external-meeting-api:/var/www/java/external-meeting-api/run.sh"
"malan:/var/www/malan:/var/www/malan/run.sh"
)
# --- 函数定义 ---
log_message() {
local level="$1"
local message="$2"
local ts
ts=$(date '+%Y-%m-%d %H:%M:%S')
[ ! -d "$LOG_DIR" ] && mkdir -p "$LOG_DIR" 2>/dev/null || true
echo "${ts} [${level}] ${message}" >> "$LOG_FILE"
if [[ "$level" == "ERROR" || "$level" == "FATAL" ]]; then
echo "${ts} [${level}] ${message}" >&2
fi
}
# 获取进程 PID (严格模式)
get_process_pid() {
local identifier="$1"
local pid=""
if [[ "$identifier" == *.jar ]]; then
# 【Java 策略】
# 必须匹配到 'java' 命令且命令行参数中包含该具体的 jar 文件名
# 使用 -f 是因为 java 进程的 cmdline 包含 jar 路径,但我们要确保 jar 名完全匹配
# 这里的正则确保 jar 文件名前后有边界 (空格或结束符),防止部分匹配
pid=$(pgrep -f "java.*${identifier}" | head -n 1)
else
# 【普通进程策略】
# 严格使用 -x (exact match),只匹配进程名完全等于 identifier 的进程
# 绝不进行模糊搜索
pid=$(pgrep -x "$identifier" | head -n 1)
fi
echo "$pid"
}
# 检查进程是否存在
is_process_running() {
local identifier="$1"
local pid
pid=$(get_process_pid "$identifier")
[ -n "$pid" ]
}
# 清理残留进程
cleanup_stale_process() {
local identifier="$1"
local pid
pid=$(get_process_pid "$identifier")
if [ -n "$pid" ]; then
log_message "WARN" "发现残留进程 '${identifier}' (PID: ${pid}),正在清理..."
# 尝试优雅停止
kill "$pid" 2>/dev/null || true
local count=0
while kill -0 "$pid" 2>/dev/null && [ $count -lt $STOP_WAIT_TIME ]; do
sleep 1
((count++))
done
# 强制杀死
if kill -0 "$pid" 2>/dev/null; then
log_message "WARN" "进程未响应,强制杀死 (PID: ${pid})"
kill -9 "$pid" 2>/dev/null || true
sleep 1
fi
log_message "INFO" "残留进程已清理完毕。"
fi
}
# 启动服务并验证
start_service() {
local name="$1"
local dir="$2"
local script="$3"
log_message "INFO" ">>> 开始启动服务: ${name}"
# 1. 环境校验
if [ ! -d "$dir" ]; then
log_message "ERROR" "启动失败:目录不存在 -> ${dir}"
return 1
fi
if [ ! -x "$script" ]; then
log_message "ERROR" "启动失败:脚本不可执行 -> ${script}"
return 1
fi
# 2. 清理可能占用的旧进程
cleanup_stale_process "$name"
# 3. 执行启动
cd "$dir" || { log_message "ERROR" "无法切换目录: ${dir}"; return 1; }
local startup_log="${dir}/startup_last.log"
# 使用 setsid 独立运行
setsid "$script" > "$startup_log" 2>&1 &
log_message "INFO" "启动命令已发送,进入状态轮询 (最多 ${STARTUP_TIMEOUT}s)..."
# 4. 轮询验证 (严格匹配)
local elapsed=0
while [ $elapsed -lt $STARTUP_TIMEOUT ]; do
sleep $POLL_INTERVAL
elapsed=$((elapsed + POLL_INTERVAL))
if is_process_running "$name"; then
local running_pid
running_pid=$(get_process_pid "$name")
log_message "INFO" "✅ 服务 '${name}' 启动成功 (PID: ${running_pid})"
return 0
fi
done
log_message "ERROR" "❌ 服务 '${name}' 启动超时!请检查日志: ${startup_log}"
return 1
}
# --- 主逻辑 ---
main() {
log_message "INFO" "========== 监控周期开始 =========="
for entry in "${SERVICES[@]}"; do
IFS=':' read -r identifier work_dir start_script <<< "$entry"
# 去除空格
identifier=$(echo "$identifier" | xargs)
work_dir=$(echo "$work_dir" | xargs)
start_script=$(echo "$start_script" | xargs)
[ -z "$identifier" ] && continue
log_message "DEBUG" "检测目标: ${identifier}"
if is_process_running "$identifier"; then
local pid
pid=$(get_process_pid "$identifier")
log_message "INFO" "正常: ${identifier} (PID: ${pid})"
else
log_message "WARN" "异常: ${identifier} 未运行,触发自愈..."
if ! start_service "$identifier" "$work_dir" "$start_script"; then
log_message "ERROR" "自愈失败: ${identifier},等待下一周期。"
# TODO: 此处可集成钉钉/邮件报警
fi
fi
done
log_message "INFO" "========== 监控周期结束 =========="
}
main || log_message "FATAL" "脚本执行异常退出"
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论