提交 0396ae7f authored 作者: PGY's avatar PGY

feat(自动化部署脚本):重构成都太行 Redis 与 Java 服务监控脚本,提升系统可靠性与可维护性

- Redis 监控脚本 (monitor_redis_service.sh):
  - 增强日志记录与错误处理机制。
  - 移除重启失败时的高风险数据清理逻辑。
  - 引入 PID 锁机制,防止脚本并发重叠执行。
  - 采用动态轮询替代固定等待,用于检测服务恢复状态。
  - 优化密码处理逻辑,避免硬编码。
  - 新增日志轮转功能,以控制日志文件大小。

- Java 服务监控脚本 (monitor_ujava_service.sh):
  - 重构服务检查逻辑,简化进程检测流程。
  - 引入动态轮询机制,用于检测服务恢复状态。
  - 使用 flock 命令实现并发控制。
  - 整合服务定义,降低后续维护难度。
  - 完善错误处理与日志记录,提升故障诊断能力。
  - 优化容器与服务的重启逻辑。
上级 f3f26dc1
......@@ -4,7 +4,7 @@
CONTAINER_NAME="umysql"
DB_USER="root"
HOST_BACKUP_DIR="/opt/mysql" # 宿主机备份目录
LOG_FILE="/var/log/backup_mysql_databases.log"
LOG_FILE="/var/log/scripts/backup_mysql_databases.log"
RETENTION_DAYS=30
TARGET_DBS=("devops" "devops_voice" "huazhao2" "nacos_mysql" "offline" "ubains" "wifi" "voice" "ubains_nacos_config" "ubains_sso")
......
#!/bin/bash
# clear_deleted_files.sh - 定时清理被进程占用的已删除文件
LOG_FILE="/var/log/cleanup_deleted_files.log"
LOG_FILE="/var/log/scripts/cleanup_deleted_files.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$DATE] 开始检查被占用的已删除文件..." >> "$LOG_FILE"
......
#!/bin/bash
# 宿主机上的脚本:检查 EMQX,如果容器未运行则重启 uemqx 容器
LOG_FILE="/var/log/monitor_emqx_service.log"
#===============================================================================
# 脚本名称:monitor_emqx_service.sh
# 功能描述:EMQX 服务监测与自愈脚本 (增强版,兼容 EMQX 4.4.x)
# 版本:V2.4
# 创建日期:2026-01-27
# 修改日期:2026-08-13
# 基于文档:_PRD_预定系统_EMQX 服务监控需求文档.md V2.1
# 变更历史:
# V2.0 (2026-03-30): 增加连续失败计数、重启冷却、多维度健康检查、状态持久化
# V2.2 (2026-06-29): 端口检测改为宿主机侧,避免 5.x 镜像无 netstat 导致误判
# V2.3 (2026-06-29): 优化注释,状态命令兼容 4.x(emqx_ctl) 与 5.x/6.x(emqx ctl)
# V2.4 (2026-08-13): 成都太行环境适配
# - 状态检查命令改为 4.4 优先: emqx_ctl status (4.x) → emqx ctl status (5.x/6.x)
# - 状态输出匹配兼容 "is started"(4.x) 与 "is running"(6.x)
# - 重启后验证改为动态轮询,替代固定等待
# - 为 docker exec 增加 timeout 保护,避免 4.4 节点未启动时 emqx_ctl 挂起
# - 配置项统一收敛到文件顶部
#
# 监测对象:
# 1. uemqx 容器运行状态
# 2. 容器内 EMQX 进程状态
# 3. EMQX 核心端口监听状态(宿主机侧检测)
# 4. EMQX 服务可用性 (emqx_ctl status / emqx ctl status)
# 5. 自动重启异常容器
#
# 智能恢复策略:
# 1. 连续失败检测:连续 3 次检测失败才触发重启
# 2. 多重验证机制:进程检查 + 端口检查 + 命令响应
# 3. 重启冷却时间:30 分钟内不重复重启同一容器
#
# 使用方法:
# chmod +x monitor_emqx_service.sh
# ./monitor_emqx_service.sh
#
# 定时任务示例:
# */5 * * * * /data/services/scripts/monitor_emqx_service.sh
#===============================================================================
# ==================== 配置区(修改配置请在此处调整) ====================
CONTAINER_NAME="uemqx" # EMQX 容器名称
LOG_FILE="/var/log/scripts/monitor_emqx_service.log"
STATE_FILE="/var/log/scripts/.emqx_monitor_state"
PID_FILE="/var/log/scripts/.emqx_monitor.pid"
MAX_LOG_SIZE=$((5*1024*1024)) # 日志轮转阈值(5MB)
LOG_RETENTION_DAYS=30 # 旧日志保留天数
MAX_FAILURES=3 # 最大连续失败次数(触发重启的阈值)
COOLDOWN_PERIOD=1800 # 重启冷却时间(秒,30分钟)
EMQX_PORTS=(1883 8083 8883) # EMQX 核心端口(MQTT/WS/MQTT-SSL,宿主机侧检测)
EMQX_MAIN_PORT=1883 # 必须监听的端口(MQTT 核心端口)
MIN_PORTS_LISTENING=2 # 至少监听端口数
CHECK_TIMEOUT=10 # docker exec 命令超时(秒),防止 emqx_ctl 挂起
POLL_INTERVAL=10 # 重启后动态轮询检查间隔(秒)
POLL_TIMEOUT_MINUTES=3 # 重启后动态轮询最长等待时间(分钟)
# ========================================================================
#===============================================================================
# 函数定义
#===============================================================================
# 获取锁函数(防止并发执行)
acquire_lock() {
# 检查 PID 文件是否存在
if [ -f "$PID_FILE" ]; then
local old_pid
old_pid=$(cat "$PID_FILE" 2>/dev/null)
# 检查该进程是否还在运行
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
echo "$(date '+%Y-%m-%d %H:%M:%S') - 警告: 上一次脚本执行仍在运行 (PID: $old_pid),本次跳过执行" | tee -a "$LOG_FILE"
exit 1
else
# 进程不存在,清理旧的 PID 文件
rm -f "$PID_FILE"
fi
fi
# 确保 PID 文件目录存在
local pid_dir
pid_dir=$(dirname "$PID_FILE")
if [ ! -d "$pid_dir" ]; then
mkdir -p "$pid_dir" 2>/dev/null || true
fi
# 写入当前进程 PID
echo $$ > "$PID_FILE"
# 设置退出时清理 PID 文件
trap 'rm -f "$PID_FILE"; exit' EXIT INT TERM HUP
}
# 日志轮转函数
rotate_logs() {
if [ -f "$LOG_FILE" ]; then
FILE_SIZE=$(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)
if [ "$FILE_SIZE" -ge "$MAX_LOG_SIZE" ]; then
mv "$LOG_FILE" "$LOG_FILE.$(date '+%Y%m%d%H%M%S')"
touch "$LOG_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S') - 日志文件超过 5MB,已自动轮转。" | tee -a "$LOG_FILE"
fi
fi
# 清理超过保留天数的旧日志文件
find "$(dirname "$LOG_FILE")" -name "$(basename "$LOG_FILE").*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# 日志记录函数
log() {
local message
message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
......@@ -11,113 +115,255 @@ log() {
echo "$message" >> "$LOG_FILE"
}
check_emqx() {
# 检查 uemqx 容器是否正在运行
if docker ps --format '{{.Names}}' | grep -Fxq "uemqx"; then
# 通过 docker exec 检查 EMQX 进程是否在容器内运行
if docker exec uemqx pgrep -f "emqx" >/dev/null 2>&1; then
# 检查EMQX是否在运行状态(使用EMQX自带的命令)
if docker exec uemqx emqx_ctl status >/dev/null 2>&1; then
# 额外验证EMQX是否真正可用
status_output=$(docker exec uemqx emqx_ctl status 2>&1)
if echo "$status_output" | grep -q "is started"; then
log "EMQX 服务状态正常"
return 0
else
log "警告: emqx_ctl 命令执行成功,但返回意外状态"
return 1
fi
else
log "警告: 容器正在运行,EMQX 进程存在,但 emqx_ctl 命令执行失败"
return 1
fi
else
log "警告: 容器正在运行,但在容器内未找到 EMQX 进程"
return 1
# 读取状态文件
read_state_file() {
if [ -f "$STATE_FILE" ]; then
source "$STATE_FILE"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
# 写入状态文件
write_state_file() {
local count=$1
local restart_time=$2
cat > "$STATE_FILE" << EOF
FAILURE_COUNT=$count
LAST_RESTART_TIME=$restart_time
LAST_CHECK_TIME=$(date +%s)
EOF
}
# 获取上次重启时间
get_last_restart_time() {
if [ -f "$STATE_FILE" ]; then
source "$STATE_FILE"
echo "${LAST_RESTART_TIME:-0}"
else
echo "0"
fi
}
# 获取 EMQX 状态命令输出(4.4 优先,兼容 5.x/6.x)
# EMQX 4.x 使用 emqx_ctl status;EMQX 5.x/6.x 使用 emqx ctl status
get_emqx_status() {
local output
# 优先 4.x 命令
output=$(timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" emqx_ctl status 2>&1)
if [ $? -eq 0 ]; then
echo "$output"
return 0
fi
# 回退 5.x/6.x 命令(为将来升级预留兼容)
output=$(timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" emqx ctl status 2>&1)
echo "$output"
return $?
}
# 判断指定端口是否在【宿主机侧】监听 (兼容 ss / netstat / bash内建 /dev/tcp)
# 说明:EMQX 官方镜像可能不含 netstat/ss,端口检测必须在宿主机执行,
# 否则容器内 netstat 命令缺失会导致端口永远"未监听"的误判。
_emqx_port_listening() {
local port="$1"
# 优先使用 ss
if ss -tln 2>/dev/null | grep -qE ":${port}([^0-9]|$)"; then
return 0
fi
# 回退 netstat
if netstat -tln 2>/dev/null | grep -qE ":${port}([^0-9]|$)"; then
return 0
fi
# 最后兜底:bash 内建 /dev/tcp 建立 TCP 连接探测 (无需任何外部工具)
if (exec 3<>"/dev/tcp/127.0.0.1/${port}") 2>/dev/null; then
return 0
fi
return 1
}
# 检查 EMQX 端口是否监听 (宿主机侧检测)
check_emqx_ports() {
local listening_count=0
local port
for port in "${EMQX_PORTS[@]}"; do
if _emqx_port_listening "$port"; then
((listening_count++))
fi
done
# 至少 2 个核心端口监听认为正常,且核心端口(MQTT)必须监听
if [ "$listening_count" -ge "$MIN_PORTS_LISTENING" ] && _emqx_port_listening "$EMQX_MAIN_PORT"; then
return 0
else
log "信息: uemqx 容器未运行"
return 1
fi
}
restart_emqx_container() {
log "EMQX 未运行。正在尝试重启容器 'uemqx'..."
# 检查容器是否存在(不仅仅是运行状态)
if docker ps -a --format '{{.Names}}' | grep -Fxq "uemqx"; then
# 停止可能挂起的容器
docker stop uemqx >/dev/null 2>&1 || true
# 容器存在但未运行,尝试启动
if docker start uemqx; then
log "成功: EMQX 容器已成功启动。"
# 等待几秒让服务启动
log "信息: 等待 30 秒让 EMQX 服务完全启动..."
for i in {1..30}; do
echo -n "." >> "$LOG_FILE"
sleep 1
done
echo "" >> "$LOG_FILE"
# 检查启动后容器是否仍在运行
if docker ps --format '{{.Names}}' | grep -Fxq "uemqx"; then
log "信息: EMQX 容器现在正在运行。"
# 再次检查EMQX进程是否已启动
if docker exec uemqx pgrep -f "emqx" >/dev/null 2>&1; then
log "信息: EMQX 进程在容器内正在运行。"
# 等待一段时间后再次检查服务状态
log "信息: 等待 10 秒后检查服务状态..."
sleep 10
if docker exec uemqx emqx_ctl status >/dev/null 2>&1; then
status_output=$(docker exec uemqx emqx_ctl status 2>&1)
if echo "$status_output" | grep -q "is started"; then
log "信息: EMQX 服务状态正常。"
return 0
else
log "错误: EMQX 进程运行中,但服务状态异常。"
return 1
fi
else
log "错误: EMQX 进程运行中,但服务状态检查失败。"
return 1
fi
else
log "错误: EMQX 进程在容器内未运行。"
return 1
fi
else
log "错误: EMQX 容器在启动后不久就停止了。"
return 1
fi
else
log "错误: 无法启动 EMQX 容器。"
return 1
# 综合健康检查
comprehensive_health_check() {
# 检查 1: 容器是否运行
if ! docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "检查失败[1/4]: 容器 $CONTAINER_NAME 未运行"
return 1
fi
# 检查 2: EMQX 进程是否存在
if ! timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" pgrep -f "emqx" >/dev/null 2>&1; then
log "检查失败[2/4]: 容器内未找到 EMQX 进程"
return 1
fi
# 检查 3: 核心端口是否监听(宿主机侧)
if ! check_emqx_ports; then
log "检查失败[3/4]: EMQX 核心端口未监听 (${EMQX_PORTS[*]})"
return 1
fi
# 检查 4: EMQX 状态命令响应 (兼容 4.x "is started" 与 5.x/6.x "is running")
local status_output
status_output=$(get_emqx_status)
if ! echo "$status_output" | grep -qE "is started|is running"; then
log "检查失败[4/4]: EMQX 状态命令未返回正常状态: $status_output"
return 1
fi
return 0
}
# 动态轮询等待 EMQX 服务恢复(重启后调用)
wait_for_emqx() {
local timeout_minutes="${1:-$POLL_TIMEOUT_MINUTES}" # 最长等待分钟数
local max_attempts=$((timeout_minutes * 60 / POLL_INTERVAL))
local attempt=0
while [ $attempt -lt $max_attempts ]; do
attempt=$((attempt + 1))
if comprehensive_health_check >/dev/null 2>&1; then
log "EMQX 服务已恢复(耗时约 $((attempt * POLL_INTERVAL / 60))$((attempt * POLL_INTERVAL % 60)) 秒)"
return 0
fi
sleep "$POLL_INTERVAL"
done
log "EMQX 服务在 ${timeout_minutes} 分钟内未恢复,已超时"
return 1
}
# 重启EMQX容器
restart_emqx_container() {
local current_time
current_time=$(date +%s)
log "开始执行 EMQX 容器重启流程"
# 检查容器是否存在
if ! docker ps -a --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "错误: 容器 '$CONTAINER_NAME' 不存在!"
return 1
fi
# 停止容器(注意:docker stop 会绕过 restart=always 策略,启动后需确保成功)
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
sleep 2
# 启动容器
if ! docker start "$CONTAINER_NAME"; then
log "错误: 无法启动容器 '$CONTAINER_NAME'"
return 1
fi
log "容器启动命令执行成功,动态轮询等待服务恢复..."
# 动态轮询验证服务恢复
if wait_for_emqx; then
log "EMQX 容器重启成功"
write_state_file 0 "$current_time"
return 0
else
log "错误: 容器 'uemqx' 不存在!"
log "信息: 您可能需要手动重新创建 EMQX 容器。"
log "错误: EMQX 容器重启后服务未恢复,请手动检查: docker logs $CONTAINER_NAME"
write_state_file 0 "$current_time"
return 1
fi
}
#===============================================================================
# 主逻辑
log "开始检查 EMQX 服务状态..."
if check_emqx; then
log "EMQX 正在运行且状态正常。"
else
log "EMQX 无响应或容器未运行。"
restart_emqx_container
# 检查重启后是否正常工作
if check_emqx; then
log "EMQX 已成功重启,现在状态正常。"
#===============================================================================
# 主函数
main() {
# 获取锁(防止并发执行)
acquire_lock
# 执行日志轮转
rotate_logs
log "==========================================="
log "EMQX 服务健康检查开始"
log "==========================================="
log "配置参数:"
log " - 最大失败次数:${MAX_FAILURES}"
log " - 重启冷却时间:${COOLDOWN_PERIOD}秒 ($(($COOLDOWN_PERIOD / 60))分钟)"
log "==========================================="
# 确保日志目录存在
local log_dir
log_dir=$(dirname "$LOG_FILE")
if [ ! -d "$log_dir" ]; then
mkdir -p "$log_dir" 2>/dev/null || true
fi
# 读取当前失败次数
local failure_count
failure_count=$(read_state_file)
local last_restart_time
last_restart_time=$(get_last_restart_time)
log "读取状态文件: 当前失败次数 = $failure_count, 距上次重启 = $((($(date +%s) - last_restart_time)))秒"
# 执行健康检查
if comprehensive_health_check; then
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_state_file 0 "$last_restart_time"
log "检查成功,重置失败计数器为 0"
fi
log "EMQX 服务状态正常"
else
log "错误: 重启尝试后 EMQX 仍无响应。"
log "信息: 请使用以下命令检查容器日志: docker logs uemqx"
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_state_file "$failure_count" "$last_restart_time"
log "警告: EMQX 服务状态异常"
log "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "错误: 连续失败达到阈值($MAX_FAILURES次),触发容器重启"
# 检查冷却期
local current_time
current_time=$(date +%s)
local time_since_restart=$((current_time - last_restart_time))
if [ $last_restart_time -gt 0 ] && [ $time_since_restart -lt $COOLDOWN_PERIOD ]; then
log "警告: 距上次重启仅 ${time_since_restart} 秒,冷却期未过(${COOLDOWN_PERIOD}秒),跳过本次重启"
else
# 执行重启
if restart_emqx_container; then
log "EMQX 容器重启成功"
else
log "错误: EMQX 容器重启失败"
fi
fi
else
log "警告: 未达到重启阈值,等待下次检查确认"
fi
fi
fi
\ No newline at end of file
log "==========================================="
log "EMQX 服务健康检查完成"
log "==========================================="
}
# 执行主逻辑
main
\ No newline at end of file
#!/bin/bash
# 宿主机上的脚本:检查 MySQL,如果容器未运行则重启 umysql 容器
LOG_FILE="/var/log/monitor_mysql_service.log"
#===============================================================================
# 脚本名称:monitor_mysql_service.sh
# 功能描述:监测 MySQL 容器(umysql)是否正常,不正常则自动拉起容器
# 版本:V2.0
# 修改日期:2026-08-13
# 变更历史:
# V1.0: 初始版本
# V2.0: 优化
# - 修复注释与代码不一致(原注释"等待20秒"实际sleep 60)
# - 固定等待改为动态轮询,避免启动慢时误判超时、启动快时白等
# - 增加 PID 并发锁,防止 cron 重叠执行
# - 精简冗余代码
#
# 监测逻辑:
# 1. umysql 容器是否在运行
# 2. mysqladmin ping 是否成功
# 3. 执行 SELECT 1 确认可执行 SQL
# 4. 容器异常则自动拉起,并动态轮询验证恢复
#
# 定时任务示例:
# */5 * * * * /data/services/scripts/monitor_mysql_service.sh
#===============================================================================
# ==================== 配置区(修改配置请在此处调整) ====================
CONTAINER_NAME="umysql" # MySQL 容器名称
DB_USER="root" # 数据库用户
LOG_FILE="/var/log/scripts/monitor_mysql_service.log"
PID_FILE="/var/log/scripts/.mysql_monitor.pid"
CHECK_TIMEOUT=10 # docker exec 命令超时(秒)
POLL_INTERVAL=10 # 重启后轮询检查间隔(秒)
POLL_TIMEOUT_MINUTES=5 # 重启后轮询最长等待时间(分钟)
# ========================================================================
# ==================== 日志函数 ====================
log() {
local message
message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
echo "$message"
# 同时写入日志文件
echo "$message" >> "$LOG_FILE"
}
# PID 并发锁:防止 cron 周期内脚本未执行完又被触发
acquire_lock() {
if [ -f "$PID_FILE" ]; then
local old_pid
old_pid=$(cat "$PID_FILE" 2>/dev/null)
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
log "警告: 上一次脚本执行仍在运行 (PID: $old_pid),本次跳过"
exit 1
fi
rm -f "$PID_FILE"
fi
echo $$ > "$PID_FILE"
trap 'rm -f "$PID_FILE"; exit' EXIT INT TERM HUP
}
# 获取 MySQL 密码:从容器环境变量读取(避免明文硬编码)
get_mysql_password() {
docker exec umysql printenv MYSQL_ROOT_PASSWORD 2>/dev/null
timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" printenv MYSQL_ROOT_PASSWORD 2>/dev/null
}
# 检查 MySQL 是否正常(返回 0 表示正常)
check_mysql() {
if docker ps --format '{{.Names}}' | grep -Fxq "umysql"; then
local mysql_password
mysql_password=$(get_mysql_password)
if [ -z "$mysql_password" ]; then
return 1
fi
# 尝试连接数据库
if docker exec umysql mysqladmin ping -h localhost -u root -p"$mysql_password" 2>/dev/null; then
# 尝试执行一个简单的SQL查询来确认数据库功能正常
if docker exec umysql mysql -u root -p"$mysql_password" -e "SELECT 1;" >/dev/null 2>&1; then
log "MySQL 服务连接正常,可以执行SQL查询"
return 0
else
log "警告: MySQL 服务可以ping通,但无法执行SQL查询"
return 1
fi
else
log "警告: 无法连接到 MySQL 服务"
return 1
fi
# 1. 容器是否运行
if ! docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "检查失败[1/3]: 容器 $CONTAINER_NAME 未运行"
return 1
fi
# 2. 获取密码
local mysql_password
mysql_password=$(get_mysql_password)
if [ -z "$mysql_password" ]; then
log "检查失败[2/3]: 无法从容器获取 MySQL 密码"
return 1
fi
# 3. ping 检查 + SELECT 1 验证可执行 SQL
if timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" mysqladmin ping -h localhost -u "$DB_USER" -p"$mysql_password" >/dev/null 2>&1 &&
timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" mysql -u "$DB_USER" -p"$mysql_password" -e "SELECT 1;" >/dev/null 2>&1; then
log "MySQL 服务连接正常,可以执行 SQL 查询"
return 0
else
log "信息: umysql 容器未运行"
log "检查失败[3/3]: MySQL 连接或 SQL 执行失败"
return 1
fi
}
# 动态轮询等待 MySQL 恢复(拉起容器后调用)
wait_for_mysql() {
local timeout_minutes="${1:-$POLL_TIMEOUT_MINUTES}"
local max_attempts=$((timeout_minutes * 60 / POLL_INTERVAL))
local attempt=0
while [ $attempt -lt $max_attempts ]; do
attempt=$((attempt + 1))
if check_mysql >/dev/null 2>&1; then
log "MySQL 服务已恢复(耗时约 $((attempt * POLL_INTERVAL / 60))$((attempt * POLL_INTERVAL % 60)) 秒)"
return 0
fi
sleep "$POLL_INTERVAL"
done
log "MySQL 服务在 ${timeout_minutes} 分钟内未恢复,已超时"
return 1
}
# 拉起 MySQL 容器(容器存在但未运行则启动,服务异常则重启)
restart_mysql_container() {
log "MySQL 未运行。正在尝试重启容器 'umysql'..."
# 检查容器是否存在(不仅仅是运行状态)
if docker ps -a --format '{{.Names}}' | grep -Fxq "umysql"; then
# 停止可能挂起的容器
docker stop umysql >/dev/null 2>&1 || true
# 容器存在但未运行,尝试启动
if docker start umysql; then
log "成功: MySQL 容器已成功启动。"
# 等待几秒让服务启动
log "信息: 等待 60 秒让 MySQL 服务完全启动..."
for i in {1..60}; do
echo -n "." >> "$LOG_FILE"
sleep 1
done
echo "" >> "$LOG_FILE"
# 检查启动后容器是否仍在运行
if docker ps --format '{{.Names}}' | grep -Fxq "umysql"; then
log "信息: MySQL 容器现在正在运行。"
# 等待一段时间后检查服务状态
log "信息: 等待 20 秒后检查服务状态..."
sleep 60
local mysql_password
mysql_password=$(get_mysql_password)
if [ -z "$mysql_password" ]; then
return 1
fi
if docker exec umysql mysqladmin ping -h localhost -u root -p"$mysql_password" >/dev/null 2>&1; then
if docker exec umysql mysql -u root -p"$mysql_password" -e "SELECT 1;" >/dev/null 2>&1; then
log "信息: MySQL 服务状态正常。"
return 0
else
log "错误: MySQL 服务运行中,但无法执行SQL查询。"
return 1
fi
else
log "错误: MySQL 服务状态检查失败。"
return 1
fi
else
log "错误: MySQL 容器在启动后不久就停止了。"
return 1
fi
log "MySQL 异常,正在拉起容器 '$CONTAINER_NAME'..."
# 容器是否存在(含已停止的)
if ! docker ps -a --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "错误: 容器 '$CONTAINER_NAME' 不存在,请手动检查容器状态"
return 1
fi
# 容器在运行但服务异常,先停止再启动(容器未运行则直接启动)
if docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "信息: 容器已在运行但服务异常,尝试重启容器..."
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
sleep 2
fi
if docker start "$CONTAINER_NAME"; then
log "容器 '$CONTAINER_NAME' 已启动,动态轮询等待 MySQL 恢复..."
if wait_for_mysql; then
log "MySQL 容器拉起成功,服务状态正常"
return 0
else
log "错误: 无法启动 MySQL 容器。"
log "错误: 容器已启动但 MySQL 服务未恢复,请手动检查: docker logs $CONTAINER_NAME"
return 1
fi
else
log "错误: 容器 'umysql' 不存在!"
log "信息: 您可能需要手动重新创建 MySQL 容器。"
log "错误: 无法启动容器 '$CONTAINER_NAME',请手动检查: docker logs $CONTAINER_NAME"
return 1
fi
}
# 主逻辑
log "开始检查 MySQL 服务状态..."
if check_mysql; then
log "MySQL 正在运行且状态正常。"
else
log "MySQL 无响应或容器未运行。"
restart_mysql_container
# 检查重启后是否正常工作
# ==================== 主逻辑 ====================
main() {
# 获取锁
acquire_lock
log "==========================================="
log "MySQL 服务健康检查开始"
log "==========================================="
if check_mysql; then
log "MySQL 已成功重启,现在状态正常。"
log "MySQL 容器运行正常,无需处理"
else
log "错误: 重启尝试后 MySQL 仍无响应。"
log "信息: 请使用以下命令检查容器日志: docker logs umysql"
log "MySQL 异常,自动拉起容器..."
if restart_mysql_container; then
log "MySQL 已成功恢复"
else
log "错误: 自动拉起失败,请手动排查: docker ps -a && docker logs $CONTAINER_NAME"
fi
fi
fi
\ No newline at end of file
log "==========================================="
log "MySQL 服务健康检查完成"
log "==========================================="
}
# 执行主逻辑
main
\ No newline at end of file
#!/bin/bash
# 宿主机上的脚本:检查 Nacos,失败则重启 ujava2 容器中的服务
LOG_FILE="/var/log/monitor_nacos_service.log"
# ==================== 配置区(修改配置请在此处调整) ====================
CONTAINER_NAME="ujava2" # 需要监控的容器名称(Nacos 运行在此容器内)
NACOS_HEALTH_URL="http://127.0.0.1:8848/nacos/v1/console/health/readiness" # Nacos 健康检查接口
START_SCRIPT="/var/www/java/start.sh" # 容器内服务启动脚本路径
CHECK_RETRIES=3 # 服务不可用的重试次数
CHECK_RETRY_INTERVAL=5 # 每次重试间隔(秒)
CHECK_TIMEOUT=5 # curl 连接超时(秒)
POLL_INTERVAL=10 # 重启后轮询检查间隔(秒)
# 注意:start.sh 需等待 120s 才通知 Nacos 启动,Nacos 收到指令后再等 300s 才执行启动,
# 即重启后最快也要 7 分钟才开始真正启动,所以等待时限必须 >= 10 分钟,否则会误报超时。
POLL_TIMEOUT_MINUTES=12 # 重启后轮询最长等待时间(分钟)
CONTAINER_START_WAIT=30 # 容器启动后初始等待时间(秒)
LOG_FILE="/var/log/scripts/monitor_nacos_service.log"
# ========================================================================
# */15 * * * * /data/services/scripts/monitor_nacos_service.sh
# ==================== 日志函数 ====================
log() {
local message
message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
......@@ -11,37 +28,89 @@ log() {
echo "$message" >> "$LOG_FILE"
}
# ==================== 健康检查 ====================
check_nacos() {
# 确保 127.0.0.1:8848 可访问(Nacos 端口已映射到宿主机)
if curl -sf http://127.0.0.1:8848/nacos/v1/console/health/readiness >/dev/null; then
return 0
else
return 1
fi
local i
# 重试多次,避免网络抖动导致误判
for i in $(seq 1 $CHECK_RETRIES); do
if curl -sf --max-time "$CHECK_TIMEOUT" "$NACOS_HEALTH_URL" >/dev/null; then
return 0
fi
if [ $i -lt $CHECK_RETRIES ]; then
log "Nacos 检查失败(第${i}次),${CHECK_RETRY_INTERVAL} 秒后重试..."
sleep "$CHECK_RETRY_INTERVAL"
fi
done
log "Nacos 检查失败,已连续重试 ${CHECK_RETRIES} 次,确认服务不可用"
return 1
}
# ==================== 重启容器内服务 ====================
restart_in_container() {
log "Nacos NOT READY. Restarting services in container 'ujava2'..."
log "Nacos 不可用,正在重启容器 '$CONTAINER_NAME' 内的服务..."
# 检查容器是否正在运行(精确匹配名称)
if ! docker ps --format '{{.Names}}' | grep -Fxq "ujava2"; then
log "ERROR: Container 'ujava2' is not running!"
return 1
if ! docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
# 容器未运行,尝试启动容器
if docker ps -a --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "容器 $CONTAINER_NAME 未运行,尝试启动容器..."
if docker start "$CONTAINER_NAME"; then
log "容器 $CONTAINER_NAME 已成功启动,等待 ${CONTAINER_START_WAIT} 秒让服务初始化..."
sleep "$CONTAINER_START_WAIT"
else
log "错误: 无法启动容器 $CONTAINER_NAME,请检查容器状态"
return 1
fi
else
log "错误: 容器 '$CONTAINER_NAME' 不存在,请手动检查容器状态"
return 1
fi
fi
# 在容器内执行 start.sh
if docker exec ujava2 /var/www/java/start.sh; then
log "SUCCESS: Services restarted in ujava2."
else
log "ERROR: Failed to run start.sh in ujava2."
# 容器运行中,执行启动脚本重启服务
if ! docker exec "$CONTAINER_NAME" "$START_SCRIPT"; then
log "错误: 在 $CONTAINER_NAME 容器内执行 $START_SCRIPT 失败"
return 1
fi
log "$START_SCRIPT 执行成功,开始动态轮询等待 Nacos 启动..."
return 0
}
# ==================== 动态等待服务启动 ====================
wait_for_nacos() {
local timeout_minutes="${1:-$POLL_TIMEOUT_MINUTES}" # 最长等待分钟数
local max_attempts=$((timeout_minutes * 60 / POLL_INTERVAL))
local attempt=0
while [ $attempt -lt $max_attempts ]; do
attempt=$((attempt + 1))
if curl -sf --max-time "$CHECK_TIMEOUT" "$NACOS_HEALTH_URL" >/dev/null 2>&1; then
log "Nacos 服务已启动(耗时约 $((attempt * POLL_INTERVAL / 60))$((attempt * POLL_INTERVAL % 60)) 秒)"
return 0
fi
sleep "$POLL_INTERVAL"
done
log "Nacos 服务在 ${timeout_minutes} 分钟内未启动,已超时"
return 1
}
# 主逻辑
# ==================== 主逻辑 ====================
log "开始检查 Nacos 服务状态..."
if check_nacos; then
log "Nacos is healthy."
log "Nacos 服务状态正常"
else
log "Nacos is not responding."
restart_in_container
log "Nacos 服务不可用,正在尝试恢复..."
if restart_in_container; then
# 动态轮询等待 Nacos 启动
if wait_for_nacos; then
log "Nacos 服务已成功恢复,当前状态正常"
else
log "错误: 重启后 Nacos 服务在 ${POLL_TIMEOUT_MINUTES} 分钟内仍未恢复,请手动检查容器日志: docker logs $CONTAINER_NAME"
fi
else
log "错误: 服务恢复失败,请手动检查容器状态: docker ps -a"
fi
fi
\ No newline at end of file
#!/bin/bash
# 宿主机上的脚本:检查 Redis,如果容器未运行则重启 uredis 容器
LOG_FILE="/var/log/monitor_redis_service.log"
#===============================================================================
# 脚本名称:monitor_redis_service.sh
# 功能描述:监测 Redis 容器(uredis)是否正常,不正常则自动拉起容器
# 版本:V2.0
# 修改日期:2026-08-13
# 变更历史:
# V1.0: 初始版本
# V2.0: 精简优化
# - 移除启动失败时清空数据目录的 rm -rf 逻辑(高风险,误触发会导致数据丢失)
# - 移除冗余的重复重启代码(原 219 行精简为 ~150 行)
# - 增加 PID 并发锁,防止 cron 重叠执行
# - 增加日志轮转(5MB),防止日志撑爆磁盘
# - 增加动态轮询验证,替代固定等待
# - 密码改为配置区变量,避免散落硬编码
#
# 监测逻辑:
# 1. uredis 容器是否在运行
# 2. redis-cli ping 是否返回 PONG(自动适配有无密码)
# 3. 容器异常则自动 docker start 拉起,并动态轮询验证恢复
#
# 定时任务示例:
# */5 * * * * /data/services/scripts/monitor_redis_service.sh
#===============================================================================
# ==================== 配置区(修改配置请在此处调整) ====================
CONTAINER_NAME="uredis" # Redis 容器名称
LOG_FILE="/var/log/scripts/monitor_redis_service.log"
PID_FILE="/var/log/scripts/.redis_monitor.pid"
# Redis 密码:优先使用容器环境变量 REDIS_PASSWORD,若容器未配置则使用此处密码
# 安全提示:建议在容器启动时通过环境变量注入密码,避免明文写入脚本
REDIS_PASSWORD="dNrprU&2S"
MAX_LOG_SIZE=$((5*1024*1024)) # 日志轮转阈值(5MB)
LOG_RETENTION_DAYS=30 # 旧日志保留天数
CHECK_TIMEOUT=10 # docker exec 命令超时(秒)
POLL_INTERVAL=10 # 重启后轮询检查间隔(秒)
POLL_TIMEOUT_MINUTES=3 # 重启后轮询最长等待时间(分钟)
# ========================================================================
# ==================== 日志函数 ====================
log() {
local message
message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
echo "$message"
# 同时写入日志文件
echo "$message" >> "$LOG_FILE"
}
# 直接在脚本中定义 Redis 密码
REDIS_PASSWORD="dNrprU&2S"
# 日志轮转:超过 5MB 归档,清理 30 天前的旧日志
rotate_logs() {
if [ -f "$LOG_FILE" ]; then
local file_size
file_size=$(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)
if [ "$file_size" -ge "$MAX_LOG_SIZE" ]; then
mv "$LOG_FILE" "$LOG_FILE.$(date '+%Y%m%d%H%M%S')"
touch "$LOG_FILE"
log "日志文件超过 5MB,已自动轮转"
fi
fi
find "$(dirname "$LOG_FILE")" -name "$(basename "$LOG_FILE").*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# PID 并发锁:防止 cron 周期内脚本未执行完又被触发
acquire_lock() {
if [ -f "$PID_FILE" ]; then
local old_pid
old_pid=$(cat "$PID_FILE" 2>/dev/null)
if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
log "警告: 上一次脚本执行仍在运行 (PID: $old_pid),本次跳过"
exit 1
fi
rm -f "$PID_FILE"
fi
echo $$ > "$PID_FILE"
trap 'rm -f "$PID_FILE"; exit' EXIT INT TERM HUP
}
# 获取 Redis 密码:优先容器环境变量,其次配置区
get_redis_password() {
local env_pwd
env_pwd=$(timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" printenv REDIS_PASSWORD 2>/dev/null)
if [ -n "$env_pwd" ]; then
echo "$env_pwd"
else
echo "$REDIS_PASSWORD"
fi
}
# 测试 Redis 连接(返回 0 表示正常)
# 策略:先无密码 ping,若需认证则用密码 ping,最终以返回 PONG 为准
check_redis() {
# 检查 uredis 容器是否正在运行
if docker ps --format '{{.Names}}' | grep -Fxq "uredis"; then
# 首先尝试不使用密码的 ping 命令
if docker exec uredis redis-cli ping >/dev/null 2>&1; then
# 无密码情况下能 ping 通
log "Redis 服务状态正常"
# 1. 容器是否运行
if ! docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "检查失败[1/3]: 容器 $CONTAINER_NAME 未运行"
return 1
fi
# 2. 先尝试无密码 ping
local output
output=$(timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" redis-cli ping 2>&1)
if [ "$output" = "PONG" ]; then
log "Redis 服务状态正常(无密码)"
return 0
fi
# 3. 需要认证,用密码重试
if [[ "$output" == *"NOAUTH"* ]] || [[ "$output" == *"Authentication required"* ]]; then
local pwd
pwd=$(get_redis_password)
local ping_output
ping_output=$(timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" redis-cli -a "$pwd" --no-auth-warning ping 2>&1)
if [ "$ping_output" = "PONG" ]; then
log "Redis 服务状态正常(已通过密码认证)"
return 0
else
# 检查是否是认证问题
auth_output=$(docker exec uredis redis-cli ping 2>&1)
if [[ "$auth_output" == *"NOAUTH"* ]] || [[ "$auth_output" == *"Authentication required"* ]]; then
log "检测到需要认证的 Redis 服务,正在尝试使用密码..."
# 使用硬编码的密码进行认证测试
if docker exec uredis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping >/dev/null 2>&1; then
ping_output=$(docker exec uredis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping 2>&1)
if [ "$ping_output" = "PONG" ]; then
log "Redis 服务状态正常(已通过密码认证)"
return 0
else
log "警告: 使用密码认证成功,但 ping 返回意外状态: $ping_output"
return 1
fi
else
log "警告: 需要认证,但无法使用预设密码连接到 Redis"
return 1
fi
else
# 不是认证问题,可能是其他错误
log "警告: Redis 连接失败,错误信息: $auth_output"
return 1
fi
log "检查失败[2/3]: 密码认证失败,无法连接 Redis: $ping_output"
return 1
fi
else
log "信息: uredis 容器未运行"
log "检查失败[3/3]: Redis 连接异常: $output"
return 1
fi
}
# 动态轮询等待 Redis 恢复(拉起容器后调用)
wait_for_redis() {
local timeout_minutes="${1:-$POLL_TIMEOUT_MINUTES}"
local max_attempts=$((timeout_minutes * 60 / POLL_INTERVAL))
local attempt=0
while [ $attempt -lt $max_attempts ]; do
attempt=$((attempt + 1))
if check_redis >/dev/null 2>&1; then
log "Redis 服务已恢复(耗时约 $((attempt * POLL_INTERVAL / 60))$((attempt * POLL_INTERVAL % 60)) 秒)"
return 0
fi
sleep "$POLL_INTERVAL"
done
log "Redis 服务在 ${timeout_minutes} 分钟内未恢复,已超时"
return 1
}
# 拉起 Redis 容器(容器存在但未运行则启动,不存在则报错)
restart_redis_container() {
log "Redis 未运行。正在尝试重启容器 'uredis'..."
# 检查容器是否存在(不仅仅是运行状态)
if docker ps -a --format '{{.Names}}' | grep -Fxq "uredis"; then
# 停止可能挂起的容器
docker stop uredis >/dev/null 2>&1 || true
# 容器存在但未运行,尝试启动
if docker start uredis; then
log "成功: Redis 容器已成功启动。"
# 等待几秒让服务启动
log "信息: 等待 20 秒让 Redis 服务完全启动..."
for i in {1..20}; do
echo -n "." >> "$LOG_FILE"
sleep 1
done
echo "" >> "$LOG_FILE"
# 检查启动后容器是否仍在运行
if docker ps --format '{{.Names}}' | grep -Fxq "uredis"; then
log "信息: Redis 容器现在正在运行。"
# 等待一段时间后再次检查服务状态
log "信息: 等待 10 秒后检查服务状态..."
sleep 10
# 再次检查是否需要认证
if docker exec uredis redis-cli ping >/dev/null 2>&1; then
# 不需要认证
if docker exec uredis redis-cli ping >/dev/null 2>&1; then
ping_output=$(docker exec uredis redis-cli ping 2>&1)
if [ "$ping_output" = "PONG" ]; then
log "信息: Redis 服务状态正常。"
return 0
else
log "错误: Redis 服务状态异常: $ping_output"
return 1
fi
else
log "错误: Redis 服务状态检查失败。"
return 1
fi
else
# 需要认证,尝试使用密码
auth_output=$(docker exec uredis redis-cli ping 2>&1)
if [[ "$auth_output" == *"NOAUTH"* ]] || [[ "$auth_output" == *"Authentication required"* ]]; then
# 使用硬编码的密码进行认证测试
if docker exec uredis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping >/dev/null 2>&1; then
ping_output=$(docker exec uredis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping 2>&1)
if [ "$ping_output" = "PONG" ]; then
log "信息: Redis 服务状态正常(已通过密码认证)。"
return 0
else
log "错误: Redis 服务状态异常: $ping_output"
return 1
fi
else
log "错误: Redis 需要认证,但无法使用预设密码连接到 Redis。"
return 1
fi
else
log "错误: Redis 服务状态检查失败。"
return 1
fi
fi
else
log "错误: Redis 容器在启动后不久就停止了。"
return 1
fi
log "Redis 异常,正在拉起容器 '$CONTAINER_NAME'..."
# 容器是否存在(含已停止的)
if ! docker ps -a --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "错误: 容器 '$CONTAINER_NAME' 不存在,请手动检查容器状态"
return 1
fi
# 容器存在但未运行,直接启动(已运行的容器会自动跳过 stop,避免误停)
if docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "信息: 容器已在运行但服务异常,尝试重启容器..."
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
sleep 2
fi
if docker start "$CONTAINER_NAME"; then
log "容器 '$CONTAINER_NAME' 已启动,动态轮询等待 Redis 恢复..."
if wait_for_redis; then
log "Redis 容器拉起成功,服务状态正常"
return 0
else
log "错误: 无法启动 Redis 容器。"
# 尝试清理数据目录并重启
log "信息: 尝试清理数据目录并重新启动 Redis 容器..."
# 检查并删除可能存在的数据目录
if [ -d "/var/www/java/redis/data" ]; then
log "信息: 清理 /var/www/java/redis/data 目录..."
rm -rf /var/www/java/redis/data/*
fi
if [ -d "/var/www/redis/data" ]; then
log "信息: 清理 /var/www/redis/data 目录..."
rm -rf /var/www/redis/data/*
fi
# 再次尝试启动容器
log "信息: 再次尝试启动 Redis 容器..."
if docker start uredis; then
log "成功: Redis 容器在清理数据后已成功启动。"
# 等待几秒让服务启动
log "信息: 等待 20 秒让 Redis 服务完全启动..."
for i in {1..20}; do
echo -n "." >> "$LOG_FILE"
sleep 1
done
echo "" >> "$LOG_FILE"
# 检查启动后容器是否仍在运行
if docker ps --format '{{.Names}}' | grep -Fxq "uredis"; then
log "信息: Redis 容器现在正在运行。"
# 等待一段时间后再次检查服务状态
log "信息: 等待 10 秒后检查服务状态..."
sleep 10
# 再次检查是否需要认证
if docker exec uredis redis-cli ping >/dev/null 2>&1; then
# 不需要认证
if docker exec uredis redis-cli ping >/dev/null 2>&1; then
ping_output=$(docker exec uredis redis-cli ping 2>&1)
if [ "$ping_output" = "PONG" ]; then
log "信息: Redis 服务状态正常。"
return 0
else
log "错误: Redis 服务状态异常: $ping_output"
return 1
fi
else
log "错误: Redis 服务状态检查失败。"
return 1
fi
else
# 需要认证,尝试使用密码
auth_output=$(docker exec uredis redis-cli ping 2>&1)
if [[ "$auth_output" == *"NOAUTH"* ]] || [[ "$auth_output" == *"Authentication required"* ]]; then
# 使用硬编码的密码进行认证测试
if docker exec uredis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping >/dev/null 2>&1; then
ping_output=$(docker exec uredis redis-cli -a "$REDIS_PASSWORD" --no-auth-warning ping 2>&1)
if [ "$ping_output" = "PONG" ]; then
log "信息: Redis 服务状态正常(已通过密码认证)。"
return 0
else
log "错误: Redis 服务状态异常: $ping_output"
return 1
fi
else
log "错误: Redis 需要认证,但无法使用预设密码连接到 Redis。"
return 1
fi
else
log "错误: Redis 服务状态检查失败。"
return 1
fi
fi
else
log "错误: Redis 容器在启动后不久就停止了。"
return 1
fi
else
log "错误: 即使清理了数据目录,仍然无法启动 Redis 容器。"
return 1
fi
log "错误: 容器已启动但 Redis 服务未恢复,请手动检查: docker logs $CONTAINER_NAME"
return 1
fi
else
log "错误: 容器 'uredis' 不存在!"
log "信息: 您可能需要手动重新创建 Redis 容器。"
log "错误: 无法启动容器 '$CONTAINER_NAME',请手动检查: docker logs $CONTAINER_NAME"
return 1
fi
}
# 主逻辑
log "开始检查 Redis 服务状态..."
if check_redis; then
log "Redis 正在运行且状态正常。"
else
log "Redis 无响应或容器未运行。"
restart_redis_container
# 检查重启后是否正常工作
# ==================== 主逻辑 ====================
main() {
# 获取锁 + 日志轮转
acquire_lock
rotate_logs
log "==========================================="
log "Redis 服务健康检查开始"
log "==========================================="
if check_redis; then
log "Redis 已成功重启,现在状态正常。"
log "Redis 容器运行正常,无需处理"
else
log "错误: 重启尝试后 Redis 仍无响应。"
log "信息: 请使用以下命令检查容器日志: docker logs uredis"
log "Redis 异常,自动拉起容器..."
if restart_redis_container; then
log "Redis 已成功恢复"
else
log "错误: 自动拉起失败,请手动排查: docker ps -a && docker logs $CONTAINER_NAME"
fi
fi
fi
\ No newline at end of file
log "==========================================="
log "Redis 服务健康检查完成"
log "==========================================="
}
# 执行主逻辑
main
\ No newline at end of file
#!/bin/bash
# ==================== 配置区域 ====================
# 可在此处配置要监控的容器名称,默认为ujava2
#===============================================================================
# 脚本名称:monitor_ujava_service.sh
# 功能描述:监测 ujava2 容器内多个 Java 微服务,异常时自动拉起
# 版本:V3.0
# 修改日期:2026-08-13
# 变更历史:
# V1.0: 初始版本
# V3.0: 重构
# - service_map 三处重复定义改为配置区单一定义
# - 关联数组遍历顺序随机改为索引数组(保证服务按固定顺序启动)
# - 循环内重复查询进程列表改为按需查询
# - 固定等待 10 分钟改为动态轮询(服务提前恢复则提前返回)
# - 增加 flock 并发锁,防止 cron 重叠执行
# - 增加超时保护,防止 docker exec 挂起
# - 容器未运行或完全无进程时重启整个容器;部分缺失时按优先级逐个拉起
# - 配置项统一收敛到文件顶部
#
# 监测逻辑:
# 1. 容器未运行 → 重启整个容器
# 2. 容器运行但无任何 Java 进程 → 重启整个容器
# 3. 部分服务缺失 → 按配置顺序逐个启动缺失服务(核心服务优先)
# 4. 启动后动态轮询验证,未恢复则重启整个容器兜底
#
# 定时任务示例:
# */5 * * * * /data/services/scripts/monitor_ujava_service.sh
#===============================================================================
# ==================== 配置区(修改配置请在此处调整) ====================
# 容器名称:支持 UJAVA_CONTAINER_NAME 环境变量或第一个命令行参数覆盖
CONTAINER_NAME="${UJAVA_CONTAINER_NAME:-${1:-ujava2}}"
LOG_FILE="/var/log/monitor_ujava_service.log"
LOG_FILE="/var/log/scripts/monitor_ujava_service.log"
LOCK_FILE="/var/log/scripts/monitor_ujava_service.lock" # flock 并发锁文件
CONTAINER_START_WAIT=30 # 容器启动后初始等待时间(秒)
CHECK_TIMEOUT=10 # docker exec 命令超时(秒)
POLL_INTERVAL=10 # 动态轮询检查间隔(秒)
POLL_TIMEOUT_MINUTES=10 # 动态轮询最长等待时间(分钟)
# ========================================================================
# ==================== 服务定义(顺序即启动优先级,核心服务在前) ====================
# 修改服务列表时只需调整这里,检查/启动/验证逻辑自动生效
SERVICE_DIRS=(
"/var/www/java/auth-sso-auth"
"/var/www/java/auth-sso-gatway"
"/var/www/java/auth-sso-system"
"/var/www/java/api-java-meeting2.0"
"/var/www/java/external-meeting-api"
"/var/www/java/api-dubbo-meeting-control"
"/var/www/java/api-dubbo-smc-three"
)
SERVICE_JARS=(
"ubains-auth.jar"
"ubains-gateway.jar"
"ubains-modules-system.jar"
"ubains-meeting-inner-api-1.0-SNAPSHOT"
"ubains-meeting-api-1.0-SNAPSHOT.jar"
"ubains-dubbo-meeting-control"
"ubains-dubbo-smc-three-0.0.1"
)
SERVICE_COUNT=${#SERVICE_DIRS[@]}
# ========================================================================
# ==================== 日志函数 ====================
log() {
local message
message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
echo "$message"
# 同时写入日志文件
echo "$message" >> "$LOG_FILE"
}
check_ujava() {
# 检查指定的 ujava 容器是否正在运行
if docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
# 获取运行中的Java进程列表
running_processes=$(docker exec "$CONTAINER_NAME" ps aux | grep -E "java.*\.jar|ubains" | grep -v grep)
if [ -n "$running_processes" ]; then
log "检测到容器中有Java进程在运行"
log "运行中的Java进程: $(echo "$running_processes" | wc -l) 个"
# 定义服务目录和JAR文件的对应关系
declare -A service_map
service_map["/var/www/java/auth-sso-auth"]="ubains-auth.jar"
service_map["/var/www/java/auth-sso-gatway"]="ubains-gateway.jar"
service_map["/var/www/java/auth-sso-system"]="ubains-modules-system.jar"
service_map["/var/www/java/api-java-meeting2.0"]="ubains-meeting-inner-api-1.0-SNAPSHOT"
service_map["/var/www/java/external-meeting-api"]="ubains-meeting-api-1.0-SNAPSHOT.jar"
service_map["/var/www/java/api-dubbo-meeting-control"]="ubains-dubbo-meeting-control"
service_map["/var/www/java/api-dubbo-smc-three"]="ubains-dubbo-smc-three-0.0.1"
missing_services=()
active_services=()
# 检查每个服务是否在运行
for dir in "${!service_map[@]}"; do
jar_file="${service_map[$dir]}"
if echo "$running_processes" | grep -q "$jar_file"; then
active_services+=("$jar_file")
else
missing_services+=("$dir:$jar_file")
fi
done
if [ ${#missing_services[@]} -eq 0 ]; then
log "所有预期的Java进程都在运行中 (共 ${#active_services[@]} 个)"
return 0
else
log "警告: 以下服务未运行: ${missing_services[*]}"
log "信息: 以下服务正在运行: ${active_services[*]}"
return 1
fi
else
log "警告: 容器运行中,但未检测到Java进程"
# 返回1表示服务不正常
return 1
fi
else
log "信息: 容器 $CONTAINER_NAME 未运行"
# ==================== flock 并发锁 ====================
acquire_lock() {
exec 200>"$LOCK_FILE"
flock -n 200 || {
log "另一个实例正在运行,本次跳过"
exit 1
}
}
# ==================== 进程检测 ====================
# 获取容器内当前运行的所有 Java 进程列表(一次性查询,避免循环内重复执行)
get_running_processes() {
timeout "$CHECK_TIMEOUT" docker exec "$CONTAINER_NAME" \
sh -c "ps aux | grep -E 'java.*\.jar|ubains' | grep -v grep" 2>/dev/null
}
# 判断指定 JAR 关键字对应的服务是否在运行
is_service_running() {
local jar_key="$1"
local running_processes="$2"
echo "$running_processes" | grep -Fq "$jar_key"
}
# ==================== 服务启动 ====================
# 启动单个服务:统一通过 run.sh 脚本启动(保证环境变量、依赖、参数完整加载)
# 若 run.sh 不存在,说明服务未部署或目录异常,记录错误由人工处理
start_single_service() {
local dir="$1"
local jar_key="$2"
local start_mode="${3:-start}" # 默认执行 ./run.sh start,支持自定义参数
log "启动服务: $jar_key (目录: $dir)"
# 检查 run.sh 是否存在
if ! timeout "$CHECK_TIMEOUT" docker exec -w "$dir" "$CONTAINER_NAME" test -f "run.sh"; then
log "错误: $dir 目录下未找到 run.sh,服务未部署或目录异常,请人工检查"
return 1
fi
}
restart_missing_services() {
log "检测到服务缺失,正在尝试启动缺失的服务..."
# 定义服务目录和JAR文件的对应关系
declare -A service_map
service_map["/var/www/java/auth-sso-auth"]="ubains-auth.jar"
service_map["/var/www/java/auth-sso-gatway"]="ubains-gateway.jar"
service_map["/var/www/java/auth-sso-system"]="ubains-modules-system.jar"
service_map["/var/www/java/api-java-meeting2.0"]="ubains-meeting-inner-api-1.0-SNAPSHOT"
service_map["/var/www/java/external-meeting-api"]="ubains-meeting-api-1.0-SNAPSHOT.jar"
service_map["/var/www/java/api-dubbo-meeting-control"]="ubains-dubbo-meeting-control"
service_map["/var/www/java/api-dubbo-smc-three"]="ubains-dubbo-smc-three-0.0.1"
# 统计启动了多少服务
started_count=0
# 遍历每个服务,检查是否运行,如果没有则启动
for dir in "${!service_map[@]}"; do
jar_file="${service_map[$dir]}"
# 检查服务是否已经运行
running_processes=$(docker exec "$CONTAINER_NAME" ps aux | grep -E "java.*\.jar|ubains" | grep -v grep)
if ! echo "$running_processes" | grep -q "$jar_file"; then
log "启动服务: $jar_file 在目录 $dir"
# 检查JAR文件是否存在
if docker exec -w "$dir" "$CONTAINER_NAME" test -f "$jar_file\.jar"; then
# 直接执行Java命令启动服务,而不是运行run.sh
docker exec -d -w "$dir" "$CONTAINER_NAME" sh -c "nohup java -Xms1024m -Xmx1024m -jar -DAPP_CONFIG=./config $jar_file\.jar > log.out 2>&1 &"
log "已尝试启动 $jar_file"
((started_count++))
elif docker exec -w "$dir" "$CONTAINER_NAME" test -f "$jar_file-0.0.1-SNAPSHOT\.jar"; then
# 对于带版本号的JAR文件
docker exec -d -w "$dir" "$CONTAINER_NAME" sh -c "nohup java -Xms1024m -Xmx1024m -jar -DAPP_CONFIG=./config $jar_file-0.0.1-SNAPSHOT\.jar > log.out 2>&1 &"
log "已尝试启动 $jar_file-0.0.1-SNAPSHOT.jar"
((started_count++))
elif docker exec -w "$dir" "$CONTAINER_NAME" test -f "run.sh"; then
# 如果JAR文件不存在但run.sh存在,安全地执行run.sh(传递参数以避免默认执行start)
docker exec -d -w "$dir" "$CONTAINER_NAME" sh -c "./run.sh start"
log "已尝试使用run.sh启动 $jar_file"
((started_count++))
else
log "错误: JAR文件和run.sh脚本在 $dir 目录中都不存在"
fi
else
log "服务 $jar_file 已经在运行"
fi
done
if [ $started_count -gt 0 ]; then
log "成功启动了 $started_count 个服务,等待服务启动..."
# 通过 run.sh 启动服务(后台执行,不阻塞脚本)
if docker exec -d -w "$dir" "$CONTAINER_NAME" sh -c "./run.sh $start_mode > log.out 2>&1"; then
log "已通过 run.sh 启动 $jar_key"
return 0
else
log "没有需要启动的服务"
return 0
log "错误: $dir 下执行 run.sh 失败"
return 1
fi
}
wait_for_services_to_start() {
log "开始监控服务启动状态,最多等待10分钟..."
# 循环检查服务是否启动成功,最多等待10次,每次等待1分钟
for i in {1..10}; do
log "等待第 $i 分钟,检查服务是否已启动..."
# 检查是否还有缺失的服务
running_processes=$(docker exec "$CONTAINER_NAME" ps aux | grep -E "java.*\.jar|ubains" | grep -v grep)
missing_services=()
# 定义服务目录和JAR文件的对应关系
declare -A service_map
service_map["/var/www/java/auth-sso-auth"]="ubains-auth.jar"
service_map["/var/www/java/auth-sso-gatway"]="ubains-gateway.jar"
service_map["/var/www/java/auth-sso-system"]="ubains-modules-system.jar"
service_map["/var/www/java/api-java-meeting2.0"]="ubains-meeting-inner-api-1.0-SNAPSHOT"
service_map["/var/www/java/external-meeting-api"]="ubains-meeting-api-1.0-SNAPSHOT.jar"
service_map["/var/www/java/api-dubbo-meeting-control"]="ubains-dubbo-meeting-control"
service_map["/var/www/java/api-dubbo-smc-three"]="ubains-dubbo-smc-three-0.0.1"
# 检查每个服务是否在运行
for dir in "${!service_map[@]}"; do
jar_file="${service_map[$dir]}"
if ! echo "$running_processes" | grep -q "$jar_file"; then
missing_services+=("$dir:$jar_file")
# ==================== 动态轮询等待所有服务恢复 ====================
wait_for_all_services() {
local timeout_minutes="${1:-$POLL_TIMEOUT_MINUTES}"
local max_attempts=$((timeout_minutes * 60 / POLL_INTERVAL))
local attempt=0
while [ $attempt -lt $max_attempts ]; do
attempt=$((attempt + 1))
local running_processes
running_processes=$(get_running_processes)
# 统计缺失服务
local missing_list=()
local i
for i in $(seq 0 $((SERVICE_COUNT - 1))); do
if ! echo "$running_processes" | grep -Fq "${SERVICE_JARS[$i]}"; then
missing_list+=("${SERVICE_JARS[$i]}")
fi
done
if [ ${#missing_services[@]} -eq 0 ]; then
log "恭喜!所有服务都已成功启动。"
if [ ${#missing_list[@]} -eq 0 ]; then
log "所有服务已在约 $((attempt * POLL_INTERVAL / 60))$((attempt * POLL_INTERVAL % 60)) 秒内恢复"
return 0
else
log "仍有 ${#missing_services[@]} 个服务未启动: ${missing_services[*]}"
fi
# 等待1分钟
sleep 60
sleep "$POLL_INTERVAL"
done
log "已等待10分钟,仍有服务未能启动,将重启整个容器。"
log "等待 ${timeout_minutes} 分钟后仍有服务未恢复"
return 1
}
# ==================== 重启整个容器 ====================
restart_ujava_container() {
log "容器 $CONTAINER_NAME 服务不正常。正在尝试重启容器 '$CONTAINER_NAME'..."
log "正在重启容器 '$CONTAINER_NAME'..."
# 容器是否存在(含已停止的)
if ! docker ps -a --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "错误: 容器 '$CONTAINER_NAME' 不存在,请手动检查容器状态"
return 1
fi
# 检查容器是否存在(不仅仅是运行状态)
if docker ps -a --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
# 停止可能挂起的容器
# 容器在运行但服务完全异常,先停止;未运行则直接启动
if docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "信息: 容器已在运行但服务异常,先停止容器..."
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
# 容器存在但未运行,尝试启动
if docker start "$CONTAINER_NAME"; then
log "成功: 容器 $CONTAINER_NAME 已成功启动。"
# 等待几分钟让Java服务启动
log "信息: 等待 90 秒让 Java 服务完全启动..."
for i in {1..90}; do
echo -n "." >> "$LOG_FILE"
sleep 1
done
echo "" >> "$LOG_FILE"
# 检查启动后容器是否仍在运行
if docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "信息: 容器 $CONTAINER_NAME 现在正在运行。"
# 启动缺失的服务
restart_missing_services
# 检查Java进程是否已启动
log "信息: 检查服务状态..."
if check_ujava; then
log "信息: 容器 $CONTAINER_NAME 服务状态正常。"
return 0
else
log "错误: 容器 $CONTAINER_NAME 进程运行中,但服务状态异常。"
return 1
fi
else
log "错误: 容器 $CONTAINER_NAME 在启动后不久就停止了。"
return 1
fi
else
log "错误: 无法启动容器 $CONTAINER_NAME。"
return 1
fi
fi
if docker start "$CONTAINER_NAME"; then
log "容器 '$CONTAINER_NAME' 已启动,等待 ${CONTAINER_START_WAIT} 秒让服务初始化..."
sleep "$CONTAINER_START_WAIT"
return 0
else
log "错误: 容器 '$CONTAINER_NAME' 不存在!"
log "信息: 您可能需要手动重新创建 $CONTAINER_NAME 容器。"
log "错误: 无法启动容器 '$CONTAINER_NAME',请手动检查: docker logs $CONTAINER_NAME"
return 1
fi
}
# 主逻辑
log "开始检查容器 $CONTAINER_NAME 服务状态..."
if check_ujava; then
log "容器 $CONTAINER_NAME 正在运行且状态正常。"
else
log "容器 $CONTAINER_NAME 服务不完整或容器未运行。"
# 如果容器在运行但服务不完整,尝试启动缺失的服务
if docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
# 先获取当前运行的服务状态
running_processes=$(docker exec "$CONTAINER_NAME" ps aux | grep -E "java.*\.jar|ubains" | grep -v grep)
# 如果没有任何Java进程在运行,可能是所有服务都崩溃了,直接重启容器
if [ -z "$running_processes" ]; then
log "警告: 检测到没有任何Java进程在运行,尝试重启整个容器。"
restart_ujava_container
# ==================== 主逻辑 ====================
main() {
# 并发锁
acquire_lock
log "==========================================="
log "开始检查容器 $CONTAINER_NAME Java 服务状态"
log "==========================================="
# 1. 容器是否存在
if ! docker ps -a --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "错误: 容器 '$CONTAINER_NAME' 不存在,请手动检查"
exit 1
fi
# 2. 获取运行中的 Java 进程列表(一次性查询)
local running_processes
running_processes=$(get_running_processes)
# 3. 容器是否在运行
if ! docker ps --format '{{.Names}}' | grep -Fxq "$CONTAINER_NAME"; then
log "容器未运行,尝试重启容器..."
restart_ujava_container
running_processes=$(get_running_processes)
fi
# 4. 判断是否所有服务都在运行
local missing_list=()
local running_count=0
local i
for i in $(seq 0 $((SERVICE_COUNT - 1))); do
if echo "$running_processes" | grep -Fq "${SERVICE_JARS[$i]}"; then
((running_count++))
else
# 否则,尝试启动缺失的服务
restart_missing_services
# 检查重启后是否正常工作
if check_ujava; then
log "容器 $CONTAINER_NAME 服务已恢复,现在状态正常。"
else
log "容器 $CONTAINER_NAME 服务仍然不正常,尝试重启整个容器。"
restart_ujava_container
fi
missing_list+=("${SERVICE_JARS[$i]}")
fi
done
if [ ${#missing_list[@]} -eq 0 ]; then
log "所有 ${SERVICE_COUNT} 个预期的 Java 服务都在运行 (${running_count} 个)"
log "==========================================="
log "检查完成,服务正常"
log "==========================================="
exit 0
fi
# 5. 有缺失服务
log "警告: 以下服务未运行: ${missing_list[*]}"
# 5.1 如果完全没有 Java 进程,直接重启整个容器
if [ -z "$running_processes" ]; then
log "警告: 容器内没有任何 Java 进程,重启整个容器..."
if ! restart_ujava_container; then
log "错误: 容器重启失败,请手动排查: docker ps -a && docker logs $CONTAINER_NAME"
exit 1
fi
else
# 如果容器不在运行,直接重启容器
restart_ujava_container
# 5.2 部分服务缺失,按优先级逐个启动(核心服务在前)
log "部分服务缺失,按优先级逐个启动..."
for i in $(seq 0 $((SERVICE_COUNT - 1))); do
if ! echo "$missing_list" | grep -Fq "${SERVICE_JARS[$i]}"; then
continue
fi
if echo "$running_processes" | grep -Fq "${SERVICE_JARS[$i]}"; then
continue
fi
start_single_service "${SERVICE_DIRS[$i]}" "${SERVICE_JARS[$i]}"
done
fi
# 最终检查
if check_ujava; then
log "容器 $CONTAINER_NAME 已成功恢复,现在状态正常。"
# 6. 动态轮询等待所有服务恢复
if wait_for_all_services; then
log "容器 $CONTAINER_NAME 已成功恢复,所有服务运行正常"
else
log "错误: 尝试恢复后 $CONTAINER_NAME 仍无响应。"
log "信息: 请使用以下命令检查容器日志: docker logs $CONTAINER_NAME"
log "错误: 服务恢复失败,尝试重启整个容器兜底..."
if restart_ujava_container && wait_for_all_services; then
log "容器重启后所有服务恢复正常"
else
log "错误: 容器 $CONTAINER_NAME 恢复失败,请手动处理: docker ps -a && docker logs $CONTAINER_NAME"
fi
fi
fi
\ No newline at end of file
log "==========================================="
log "检查完成"
log "==========================================="
}
# 执行主逻辑
main "$@"
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论