提交 10a32523 authored 作者: 陈泽健's avatar 陈泽健

Merge remote-tracking branch 'origin/develop' into develop

...@@ -7,6 +7,8 @@ LOG_FILE="/var/log/scripts/auto_clean_deleted_ubains.log" ...@@ -7,6 +7,8 @@ LOG_FILE="/var/log/scripts/auto_clean_deleted_ubains.log"
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制 MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=7 # 日志保留天数 LOG_RETENTION_DAYS=7 # 日志保留天数
CONTAINER_NAME="ujava2" # 容器名称 CONTAINER_NAME="ujava2" # 容器名称
APP_PATH="/var/www/java/external-meeting-api" # 特定应用路径
APP_START_SCRIPT="${APP_PATH}/run.sh" # 特定应用启动脚本
# =========================================== # ===========================================
# 日志函数 # 日志函数
...@@ -24,8 +26,7 @@ rotate_logs() { ...@@ -24,8 +26,7 @@ rotate_logs() {
log "日志文件超过 5MB,已自动轮转。" log "日志文件超过 5MB,已自动轮转。"
fi fi
fi fi
# 修改为在日志文件所在目录查找备份文件 find "$(dirname "$0")" -name "auto_clean_deleted_ubains.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \;
find "$(dirname "$LOG_FILE")" -name "auto_clean_deleted_ubains.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \;
} }
rotate_logs rotate_logs
...@@ -38,6 +39,7 @@ log "===============================================" ...@@ -38,6 +39,7 @@ log "==============================================="
FOUND=0 FOUND=0
NEED_RESTART=0 NEED_RESTART=0
NEED_APP_START=0 # 新增:标记是否需要启动特定应用
# 遍历所有进程的 fd 目录,寻找匹配关键字且标记为 deleted 的文件 # 遍历所有进程的 fd 目录,寻找匹配关键字且标记为 deleted 的文件
for fd_path in /proc/[0-9]*/fd/*; do for fd_path in /proc/[0-9]*/fd/*; do
...@@ -73,6 +75,13 @@ for fd_path in /proc/[0-9]*/fd/*; do ...@@ -73,6 +75,13 @@ for fd_path in /proc/[0-9]*/fd/*; do
kill -9 "$pid" 2>/dev/null kill -9 "$pid" 2>/dev/null
sleep 1 sleep 1
NEED_RESTART=1 NEED_RESTART=1
# 获取进程的当前工作目录,判断是否为特定应用
proc_cwd=$(readlink /proc/$pid/cwd 2>/dev/null)
if [ "$proc_cwd" == "$APP_PATH" ]; then
log "检测到被杀死的进程属于特定应用:$APP_PATH,将在容器重启后尝试启动。"
NEED_APP_START=1
fi
else else
log "⏩ 文件不足 1GB,跳过。" log "⏩ 文件不足 1GB,跳过。"
fi fi
...@@ -93,6 +102,17 @@ if [ "$NEED_RESTART" -eq 1 ]; then ...@@ -93,6 +102,17 @@ if [ "$NEED_RESTART" -eq 1 ]; then
else else
log "❌ 错误: 未找到 docker 命令,请手动重启容器。" log "❌ 错误: 未找到 docker 命令,请手动重启容器。"
fi fi
# 如果需要启动特定应用,则执行启动脚本
if [ "$NEED_APP_START" -eq 1 ]; then
log "➡ 启动特定应用脚本:$APP_START_SCRIPT"
if [ -f "$APP_START_SCRIPT" ]; then
bash "$APP_START_SCRIPT" >> "$LOG_FILE" 2>&1
log "✔ 特定应用启动脚本执行完成。"
else
log "❌ 错误: 未找到特定应用启动脚本:$APP_START_SCRIPT。"
fi
fi
else else
log "无需重启 docker 容器。" log "无需重启 docker 容器。"
fi fi
......
#!/bin/bash
#===============================================================================
# 脚本名称:cleanup_deleted_files.sh
# 功能描述:清理被进程占用的已删除文件脚本
# 版本:V1.0
# 创建日期:2026-01-27
# 基于文档:_PRD_预定系统_已删除文件清理需求文档.md
#
# 清理对象:
# 1. 使用lsof +L1查找link count为0但仍被进程打开的文件
# 2. 向相关进程发送SIGHUP信号尝试释放文件句柄
# 3. 避免强制kill进程,防止服务中断
# 4. 适用于Nginx、rsyslog等支持reload的服务
#
# 使用方法:
# chmod +x cleanup_deleted_files.sh
# ./cleanup_deleted_files.sh
#
# 定时任务示例:
# 0 4 * * * /opt/scripts/cleanup_deleted_files.sh
#===============================================================================
# clear_deleted_files.sh - 定时清理被进程占用的已删除文件
LOG_FILE="/var/log/scripts/cleanup_deleted_files.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] 开始检查被占用的已删除文件..." >> "$LOG_FILE"
# 使用 lsof +L1 查找 link count 为 0(即已删除)但仍被打开的文件
DELETED_ENTRIES=$(lsof +L1 2>/dev/null | grep -v "COMMAND")
if [ -z "$DELETED_ENTRIES" ]; then
echo "[$DATE] 未发现被占用的已删除文件。" >> "$LOG_FILE"
exit 0
fi
echo "[$DATE] 发现以下被占用的已删除文件:" >> "$LOG_FILE"
echo "$DELETED_ENTRIES" >> "$LOG_FILE"
# 提取唯一 PID 列表
PIDS=$(echo "$DELETED_ENTRIES" | awk 'NR>1 {print $2}' | sort -u)
for PID in $PIDS; do
if ! kill -0 "$PID" 2>/dev/null; then
echo "[$DATE] PID $PID 已不存在,跳过。" >> "$LOG_FILE"
continue
fi
CMD=$(ps -p "$PID" -o comm= 2>/dev/null | tr -d ' ')
echo "[$DATE] 处理进程: PID=$PID, CMD=$CMD" >> "$LOG_FILE"
# 尝试发送 SIGHUP(适用于 Nginx、rsyslog 等支持 reload 的服务)
if kill -0 "$PID" 2>/dev/null; then
echo "[$DATE] 向 PID $PID 发送 SIGHUP 信号..." >> "$LOG_FILE"
kill -HUP "$PID" 2>/dev/null && continue
fi
# 若 SIGHUP 无效或不支持,记录警告(不强制 kill,避免服务中断)
echo "[$DATE] WARNING: 无法通过 SIGHUP 释放 PID $PID 的文件句柄。建议手动处理或配置 logrotate。" >> "$LOG_FILE"
done
echo "[$DATE] 检查完成。" >> "$LOG_FILE"
#!/bin/bash
#===============================================================================
# 脚本名称:monitor_external_api_services.sh
# 功能描述:外部API服务监测与自愈脚本
# 版本:V1.0
# 创建日期:2026-01-27
# 基于文档:_PRD_预定系统_外部API服务需求文档.md
#
# 监测对象:
# 1. ubains-meeting-api-1.0-SNAPSHOT.jar服务
# 2. malan服务
# 3. 宿主机进程运行状态
# 4. 服务目录和启动脚本存在性
#
# 恢复策略:
# 1. 检查进程是否运行(pgrep -f)
# 2. 验证服务目录是否存在
# 3. 检查启动脚本权限
# 4. 异常时自动启动服务
# 5. 启动后验证进程状态
#
# 使用方法:
# chmod +x monitor_external_api_services.sh
# ./monitor_external_api_services.sh
#
# 定时任务示例:
# */5 * * * * /opt/scripts/monitor_external_api_services.sh
#===============================================================================
# --- 配置区域 ---
# 日志文件路径
LOG_FILE="/var/log/scripts/monitor_external_api_services.log"
# 定义要监控的服务及其相关信息
# 格式: "进程名:目录路径:启动脚本路径"
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 message="$1"
echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> "$LOG_FILE"
}
# 检查进程是否运行
is_process_running() {
local process_name="$1"
# 使用 pgrep -f 搜索命令行中包含该名称的进程
# 对于 .jar 文件,通常搜索的是 java -jar ...<jar_name>
# 对于其他程序,搜索其进程名
if [[ "$process_name" == *.jar ]]; then
# 如果是 JAR 文件,搜索 java 进程中包含该 jar 名称的
pgrep -f "java.*$process_name" > /dev/null
else
# 否则直接搜索进程名
pgrep -x "$process_name" > /dev/null
fi
if [ $? -eq 0 ]; then
return 0 # 进程运行中
else
return 1 # 进程未运行
fi
}
# 检查目录是否存在
is_directory_exists() {
local dir_path="$1"
[ -d "$dir_path" ]
}
# 检查启动脚本是否存在且可执行
is_script_executable() {
local script_path="$1"
[ -x "$script_path" ]
}
# 启动服务
start_service() {
local service_name="$1"
local dir_path="$2"
local script_path="$3"
log_message "尝试启动服务 '$service_name'..."
# 切换到服务目录并执行启动脚本
# 使用子shell (cd ...) 以确保不影响当前脚本的工作目录
(
cd "$dir_path" || { log_message "错误: 无法切换到目录 '$dir_path'"; return 1; }
# 检查启动脚本是否存在且可执行
if ! is_script_executable "$script_path"; then
log_message "错误: 启动脚本 '$script_path' 不存在或不可执行。"
return 1
fi
# 执行启动脚本
# 使用 nohup 将进程与终端脱离,使其在后台持续运行
# 输出重定向到 startup.log 或 /dev/null
nohup "./$(basename "$script_path")" > startup.log 2>&1 &
# 等待一小段时间,让进程有机会启动
sleep 600
# 再次检查进程是否启动成功
if is_process_running "$service_name"; then
log_message "服务 '$service_name' 启动成功。"
return 0
else
log_message "错误: 服务 '$service_name' 启动失败。"
return 1
fi
)
}
# --- 主逻辑 ---
# 初始化日志
log_message "=== 服务监控脚本开始执行 ==="
# 遍历所有服务
for service_info in "${SERVICES[@]}"; do
# 解析服务信息
IFS=':' read -r service_name dir_path script_path <<< "$service_info"
# 去除可能的前后空格
service_name=$(echo "$service_name" | xargs)
dir_path=$(echo "$dir_path" | xargs)
script_path=$(echo "$script_path" | xargs)
log_message "检查服务: $service_name (目录: $dir_path, 启动脚本: $script_path)"
# 检查目录是否存在
if ! is_directory_exists "$dir_path"; then
log_message "跳过服务 '$service_name': 目录 '$dir_path' 不存在。"
continue # 跳过当前循环,检查下一个服务
fi
# 检查进程是否运行
if is_process_running "$service_name"; then
log_message "服务 '$service_name' 正在运行。"
else
log_message "服务 '$service_name' 未运行。"
# 尝试启动服务
start_service "$service_name" "$dir_path" "$script_path"
fi
done
log_message "=== 服务监控脚本执行完毕 ==="
#!/bin/bash #!/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_FILE="/var/log/scripts/monitor_external_api_services.log"
# 日志配置
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=( SERVICES=(
"ubains-meeting-api-1.0-SNAPSHOT.jar:/var/www/java/external-meeting-api:/var/www/java/external-meeting-api/run.sh" "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" "malan:/var/www/malan:/var/www/malan/run.sh"
) )
# API 检查配置
API_URL="https://127.0.0.1/exapi/system/v2/login"
API_DATA='{
"account": "test",
"password": "test"
}'
# --- 函数定义 --- # --- 函数定义 ---
# 记录日志的函数
log_message() { log_message() {
local level="$1" local message="$1"
local message="$2" echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> "$LOG_FILE"
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 (严格模式) # 检查服务是否正常(通过 API 请求)
get_process_pid() { is_service_healthy() {
local identifier="$1" local service_name="$1"
local pid=""
# 使用 curl 发送 POST 请求
if [[ "$identifier" == *.jar ]]; then # -k: 忽略 SSL 证书检查
# 【Java 策略】 # -s: 静默模式
# 必须匹配到 'java' 命令且命令行参数中包含该具体的 jar 文件名 # -o /dev/null: 不输出响应体
# 使用 -f 是因为 java 进程的 cmdline 包含 jar 路径,但我们要确保 jar 名完全匹配 # -w "%{http_code}": 只输出 HTTP 状态码
# 这里的正则确保 jar 文件名前后有边界 (空格或结束符),防止部分匹配 # --max-time 10: 设置超时时间为 10 秒
pid=$(pgrep -f "java.*${identifier}" | head -n 1) local http_code=$(curl -k -s -o /dev/null -w "%{http_code}" \
--location --request POST "$API_URL" \
--header 'Content-Type: application/json' \
--data-raw "$API_DATA" \
--max-time 10)
if [ "$http_code" -eq 200 ]; then
return 0 # 服务正常
else else
# 【普通进程策略】 log_message "警告: 服务 '$service_name' 接口返回状态码: $http_code"
# 严格使用 -x (exact match),只匹配进程名完全等于 identifier 的进程 return 1 # 服务异常
# 绝不进行模糊搜索
pid=$(pgrep -x "$identifier" | head -n 1)
fi fi
echo "$pid"
} }
# 检查进程是否存在 # 检查目录是否存在
is_process_running() { is_directory_exists() {
local identifier="$1" local dir_path="$1"
local pid [ -d "$dir_path" ]
pid=$(get_process_pid "$identifier")
[ -n "$pid" ]
} }
# 清理残留进程 # 检查启动脚本是否存在且可执行
cleanup_stale_process() { is_script_executable() {
local identifier="$1" local script_path="$1"
local pid [ -x "$script_path" ]
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() { start_service() {
local name="$1" local service_name="$1"
local dir="$2" local dir_path="$2"
local script="$3" local script_path="$3"
log_message "INFO" ">>> 开始启动服务: ${name}"
# 1. 环境校验 log_message "尝试启动服务 '$service_name'..."
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" (
cd "$dir_path" || { log_message "错误: 无法切换到目录 '$dir_path'"; return 1; }
# 3. 执行启动
cd "$dir" || { log_message "ERROR" "无法切换目录: ${dir}"; return 1; } # 检查启动脚本是否存在且可执行
if ! is_script_executable "$script_path"; then
local startup_log="${dir}/startup_last.log" log_message "错误: 启动脚本 '$script_path' 不存在或不可执行。"
return 1
# 使用 setsid 独立运行 fi
setsid "$script" > "$startup_log" 2>&1 &
log_message "INFO" "启动命令已发送,进入状态轮询 (最多 ${STARTUP_TIMEOUT}s)..."
# 4. 轮询验证 (严格匹配) # 执行启动脚本
local elapsed=0 nohup "./$(basename "$script_path")" > startup.log 2>&1 &
while [ $elapsed -lt $STARTUP_TIMEOUT ]; do
sleep $POLL_INTERVAL # 等待一段时间,让服务有足够时间启动并响应 API
elapsed=$((elapsed + POLL_INTERVAL)) log_message "等待服务启动 (300秒)..."
sleep 300
if is_process_running "$name"; then # 再次检查服务是否正常
local running_pid if is_service_healthy "$service_name"; then
running_pid=$(get_process_pid "$name") log_message "服务 '$service_name' 启动并验证成功。"
log_message "INFO" "✅ 服务 '${name}' 启动成功 (PID: ${running_pid})"
return 0 return 0
else
log_message "错误: 服务 '$service_name' 启动后接口验证仍失败。"
return 1
fi 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}" # 初始化日志
log_message "=== 服务监控脚本开始执行 (API 检查模式) ==="
if is_process_running "$identifier"; then # 遍历所有服务
local pid for service_info in "${SERVICES[@]}"; do
pid=$(get_process_pid "$identifier") # 解析服务信息
log_message "INFO" "正常: ${identifier} (PID: ${pid})" IFS=':' read -r service_name dir_path script_path <<< "$service_info"
else # 去除可能的前后空格
log_message "WARN" "异常: ${identifier} 未运行,触发自愈..." service_name=$(echo "$service_name" | xargs)
if ! start_service "$identifier" "$work_dir" "$start_script"; then dir_path=$(echo "$dir_path" | xargs)
log_message "ERROR" "自愈失败: ${identifier},等待下一周期。" script_path=$(echo "$script_path" | xargs)
# TODO: 此处可集成钉钉/邮件报警
fi log_message "检查服务: $service_name"
fi
done # 检查目录是否存在
if ! is_directory_exists "$dir_path"; then
log_message "INFO" "========== 监控周期结束 ==========" log_message "跳过服务 '$service_name': 目录 '$dir_path' 不存在。"
} continue
fi
# 检查服务是否正常
if is_service_healthy "$service_name"; then
log_message "服务 '$service_name' 运行正常 (HTTP 200)。"
else
log_message "服务 '$service_name' 响应异常或未运行。"
# 尝试启动服务
start_service "$service_name" "$dir_path" "$script_path"
fi
done
main || log_message "FATAL" "脚本执行异常退出" log_message "=== 服务监控脚本执行完毕 ==="
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论