提交 59635acc authored 作者: PGY's avatar PGY

feat(monitor): 监控脚本新增重启机制处理、日志轮转与并发控制机制。

  - 为监控脚本添加连续检测失败重启机制
  - 为监控脚本添加日志自动轮转机制(5MB轮转、保留30天)
  - 为监控脚本添加PID文件锁机制防止并发执行
  - 统一所有脚本头部说明格式,规范版本管理
上级 72625e39
......@@ -126,7 +126,12 @@
"Bash(grep:*)",
"Bash(mkdir:*)",
"Bash(cd \"E:/GithubData/ubains-module-test/AuxiliaryTool/DocumentAutoOptimizationCalibration\" && pip install translators -q)",
"Bash(ls -la \"E:\\\\GithubData\\\\ubains-module-test\\\\Docs\\\\PRD\" 2>&1 || dir \"E:\\\\GithubData\\\\ubains-module-test\\\\Docs\\\\PRD\" 2>&1)"
"Bash(ls -la \"E:\\\\GithubData\\\\ubains-module-test\\\\Docs\\\\PRD\" 2>&1 || dir \"E:\\\\GithubData\\\\ubains-module-test\\\\Docs\\\\PRD\" 2>&1)",
"Bash(sed -i 's/if \\\\[ \"\"$size_bytes\"\" -gt 0 \\\\] || \\\\[\\\\[ \"\"$target_file\"\" == \\\\*\"\"\\\\$TARGET_KEY\"\"\\\\* \\\\]\\\\]; then/if [ \"\"$size_bytes\"\" -gt 0 ]; then/' check_deleted_file_ubains.sh)",
"Bash(sed -i '/^[[:space:]]*TARGET_KEY=/d' check_deleted_file_ubains.sh)",
"Bash(check_deleted_file_ubains_header.tmp:*)",
"Bash(mv check_deleted_file_ubains_header.tmp check_deleted_file_ubains.sh)",
"Bash(chmod +x check_deleted_file_ubains.sh)"
]
}
}
# _PRD_预定系统EMQX服务监控需求文档.md
> 版本:V2.1
> 更新日期:2026-03-30
> 适用范围:预定系统EMQX消息队列服务监测与自愈
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_emqx_service.sh`
---
## 版本变更记录
| 版本 | 日期 | 变更内容 | 变更人 |
|------|------|----------|--------|
| V2.2 | 2026-03-30 | 新增日志自动轮转机制(5MB轮转、保留30天);新增PID文件锁机制防止并发执行 | Claude |
| V2.1 | 2026-03-30 | 统一文档结构,与其他监控脚本保持一致;完善失败计数机制说明 | Claude |
| V2.0 | 2026-03-30 | 重大升级:增加连续失败计数机制、失败重试间隔、重启冷却时间、多维度健康检查、状态持久化 | AI Assistant |
| V1.0 | 2026-01-27 | 初始版本,基础检测 + 重启功能 | - |
---
## 1. 背景与目标
### 1.1 背景
EMQX作为预定系统的MQTT消息中间件,承担着设备通信、状态同步等核心功能。为确保服务高可用性,需要建立智能化的监控与自愈机制。
### 1.2 现状问题
- 误重启问题:原脚本单次检测失败即触发重启,导致网络波动或临时阻塞时不必要的服务中断
- 缺乏缓冲机制:未考虑服务启动延迟、瞬时故障等场景
- 重启过于频繁:短时间内可能多次重启,影响业务稳定性
### 1.3 改进目标
实现EMQX服务的智能化定时监控功能,具备:
- 智能防抖动:通过连续失败计数机制,避免因单次检测失败就重启服务
- 多维度验证:结合进程、端口、CLI命令的综合健康检查
- 冷却期保护:设置重启冷却时间,防止频繁重启
- 连续失败阈值判断机制,避免单次临时堵塞触发重启影响业务
- 失败次数记录与重置机制
- 详细的日志追踪
---
## 2. 总体范围
### 2.1 纳入监控对象
| 对象 | 说明 | 检测方法 |
|------|------|----------|
| **容器名称** | `uemqx` | EMQX消息队列容器 |
| **EMQX进程** | 容器内的EMQX主进程 | `pgrep -f "emqx"` |
| **核心端口** | MQTT/MQTTS/WebSocket等监听端口 | `netstat -tuln`检查端口监听 |
| **服务状态** | EMQX服务整体健康度 | `emqx_ctl status`命令响应 |
### 2.2 监测端口清单
- **1883**: MQTT默认端口(必须)
- **8883**: MQTTS SSL端口
- **8083**: WebSocket端口
- **8084**: WebSocket SSL端口
- **18083**: Dashboard管理端口
**判定标准**:至少2个核心端口正常监听,且1883端口必须可用。
### 2.3 不在本期范围
- EMQX集群模式监控
- 消息吞吐量监控
- 客户端连接数监控
### 2.4 核心机制说明
**连续失败重启机制**:为避免因网络抖动、瞬时卡顿等临时故障导致的不必要容器重启,采用失败计数器机制:
- 单次检查失败时,仅记录失败次数并等待5秒后重试,不立即重启
- 连续失败达到阈值(默认3次)时,才触发容器重启
- 检查成功后,自动重置失败计数器
- 通过状态文件持久化保存失败次数和重启时间,避免计数器因进程重启丢失
- 设置重启冷却时间(默认30分钟),防止频繁重启影响业务稳定性
---
## 3. 术语说明
- **连续失败计数**:记录连续检测失败的次数,达到阈值才触发重启
- **重启冷却时间**:两次重启之间必须等待的最小时间间隔
- **状态持久化**:将失败计数和重启时间保存到文件,进程重启不丢失
- **多维度健康检查**:综合容器、进程、端口、CLI命令的多重验证
---
## 4. 功能需求
### 4.1 监控流程
#### 4.1.1 状态检查机制
#### 4.1.2 连续失败计数机制
- **阈值设置**: 连续失败 3 次 (`MAX_FAILURES=3`) 才触发重启
- **重置机制**: 任意一次检测成功立即清零计数器
- **失败间隔**: 每次检测间隔 5 秒 (`FAILURE_INTERVAL=5`),排除瞬时故障
#### 4.1.3 多维度健康检查
每次检测执行以下检查项:
```
┌─────────────────────────────────────┐
│ 检查 1: 容器是否运行 │
│ docker ps | grep uemqx │
└──────────────┬──────────────────────┘
┌─────────────────────────────────────┐
│ 检查 2: EMQX 进程是否存在 │
│ docker exec uemqx pgrep -f emqx │
└──────────────┬──────────────────────┘
┌─────────────────────────────────────┐
│ 检查 3: 核心端口是否监听 │
│ netstat -tuln | grep :1883 │
└──────────────┬──────────────────────┘
┌─────────────────────────────────────┐
│ 检查 4: emqx_ctl 命令响应 │
│ emqx_ctl status → "is started" │
└─────────────────────────────────────┘
```
**通过标准**: 所有检查项全部通过视为健康。
#### 4.1.4 失败处理机制(核心)
采用**连续失败阈值判断**机制,避免单次临时故障触发重启:
1. **单次失败处理**
- 任一检查环节失败时,失败计数器+1
- 将最新失败次数写入状态文件持久化保存
- 等待5秒后再次检测
- 记录警告日志,说明当前失败次数和阈值
- **未达到阈值时不触发重启**
2. **连续失败达到阈值**
- 当失败次数 ≥ MAX_FAILURES(默认3次)时
- 检查重启冷却时间是否已过
- 冷却期内:记录警告,跳过重启
- 冷却期外:清零计数器,触发容器重启
- 记录错误日志说明已连续失败N次
3. **检查成功处理**
- 所有检查项通过时
- 将失败计数器清零并更新状态文件
- 记录成功日志
- 正常退出本次检查
### 4.2 异常处理流程
当检测到异常且达到失败阈值时执行:
1. **容器重启**
- 停止现有容器
- 等待2秒
- 启动容器
- 等待30秒让EMQX服务完全启动
2. **状态验证循环**
- 验证容器运行状态
- 验证EMQX进程存在
- 验证端口监听正常
- 等待10秒后验证服务状态
3. **最终确认**
- 所有检查项都通过才算恢复成功
- 恢复成功后更新重启时间戳,清零失败计数器
- 任一环节失败都记录为恢复失败
### 4.3 重启冷却时间
- **冷却时长**: 30 分钟 (`COOLDOWN_PERIOD=1800`秒)
- **状态持久化**: 记录到 `/var/log/scripts/.emqx_monitor_state`
- **冷却期行为**:
- ✅ 继续执行检测
- ✅ 记录失败次数
- ❌ 不执行重启动作 (除非超过冷却期)
### 4.2 自动恢复策略
#### 4.2.1 渐进式恢复流程
```
检测失败 (第 1 次)
↓ 等待 5 秒
检测失败 (第 2 次)
↓ 等待 5 秒
检测失败 (第 3 次) → 达到阈值
执行重启流程:
1. 停止现有容器
2. 等待 2 秒
3. 启动容器
4. 等待 30 秒 (START_WAIT_TIME)
5. 验证容器运行
6. 验证进程存在
7. 验证端口监听
8. 等待 10 秒 (SERVICE_CHECK_WAIT)
9. 验证服务状态
重启成功 → 更新状态,清零计数器
重启失败 → 记录日志,等待人工介入
```
#### 4.2.2 重启验证步骤
重启后依次验证:
1. **容器运行状态** (`docker ps`)
2. **EMQX 进程** (`pgrep -f emqx`)
3. **端口监听** (`netstat -tuln`)
4. **服务状态** (`emqx_ctl status`)
任何一步失败则判定重启失败。
### 4.4 日志与审计
#### 4.4.1 日志文件
- **主日志**: `/var/log/scripts/monitor_emqx_service.log`
- **状态文件**: `/var/log/scripts/.emqx_monitor_state`
- **PID锁文件**: `/var/log/scripts/.emqx_monitor.pid`
#### 4.4.2 日志自动轮转机制
为避免日志文件无限增长占用磁盘空间,实现自动轮转机制:
1. **轮转触发条件**
- 当前日志文件大小达到 5MB (`MAX_LOG_SIZE=$((5*1024*1024))`)
- 每次脚本执行时自动检查
2. **轮转执行动作**
- 将当前日志文件重命名为带时间戳的旧日志:`monitor_emqx_service.log.YYYYMMDDHHMMSS`
- 创建新的空日志文件继续写入
- 记录轮转操作日志:`日志文件超过 5MB,已自动轮转。`
3. **旧日志清理**
- 自动删除超过保留天数的旧日志文件(默认30天)
- 使用 `find` 命令清理:`find ... -name "*.log.*" -mtime +30 -exec rm -f {} \;`
4. **轮转日志示例**
```
[2026-03-30 10:00:00] - 日志文件超过 5MB,已自动轮转。
```
#### 4.4.3 并发控制机制(PID锁)
为防止定时任务重叠执行导致的问题,实现PID文件锁机制:
1. **锁文件检查**
- 脚本开始执行时检查PID文件是否存在
- 如果PID文件存在,读取其中的PID值
2. **进程存活判断**
- 使用 `kill -0 $PID` 检查该进程是否仍在运行
- 如果进程存在,记录警告日志并退出本次执行
- 如果进程不存在,清理旧PID文件并继续执行
3. **锁文件创建**
- 将当前进程PID写入PID文件:`echo $$ > $PID_FILE`
- 确保PID文件目录存在,不存在则自动创建
4. **锁文件释放**
- 使用 `trap` 命令设置退出时自动清理
- 捕获信号:EXIT INT TERM HUP
- 脚本正常或异常退出时自动删除PID文件
5. **并发执行日志示例**
```
[2026-03-30 10:05:01] - 警告: 上一次脚本执行仍在运行 (PID: 12345),本次跳过执行
```
#### 4.4.2 日志格式
每行必须包含:
- 时间戳:`[YYYY-MM-DD HH:MM:SS]`
- 级别标识:信息/警告/错误/成功
- 具体操作和结果状态
- **失败计数信息**(失败时显示当前失败次数)
示例:
```
[2026-03-30 10:00:00] ==========================================
[2026-03-30 10:00:00] EMQX 服务健康检查开始
[2026-03-30 10:00:00] ==========================================
[2026-03-30 10:00:00] 配置参数:
[2026-03-30 10:00:00] - 最大失败次数:3
[2026-03-30 10:00:00] - 失败检测间隔:5 秒
[2026-03-30 10:00:00] - 重启冷却时间:1800 秒 (30 分钟)
[2026-03-30 10:00:00] ==========================================
[2026-03-30 10:00:00] 读取状态文件: 当前失败次数 = 0, 距上次重启 = 3600 秒
[2026-03-30 10:00:01] ✓ 容器运行状态正常
[2026-03-30 10:00:03] ✓ EMQX 进程已启动
[2026-03-30 10:00:03] ✓ EMQX 端口监听正常
[2026-03-30 10:00:05] ✓ EMQX 服务状态验证通过
[2026-03-30 10:00:05] 检查成功,重置失败计数器为 0
---
[2026-03-30 10:15:23] ==========================================
[2026-03-30 10:15:23] EMQX 服务健康检查开始
[2026-03-30 10:15:23] ==========================================
[2026-03-30 10:15:23] 读取状态文件: 当前失败次数 = 0, 距上次重启 = 3600 秒
[2026-03-30 10:15:24] 警告: EMQX 服务状态异常:emqx is not running
[2026-03-30 10:15:24] 失败计数器更新: 0 -> 1 (阈值: 3)
[2026-03-30 10:15:29] 警告: EMQX 服务状态异常:emqx is not running
[2026-03-30 10:15:29] 失败计数器更新: 1 -> 2 (阈值: 3)
[2026-03-30 10:15:34] 警告: EMQX 服务状态异常:emqx is not running
[2026-03-30 10:15:34] 失败计数器更新: 2 -> 3 (阈值: 3)
[2026-03-30 10:15:34] 错误: 连续失败达到阈值(3次),触发容器重启
[2026-03-30 10:15:34] 重置失败计数器为 0
[2026-03-30 10:15:34] ==========================================
[2026-03-30 10:15:34] 开始执行 EMQX 容器重启流程
[2026-03-30 10:15:34] ==========================================
[2026-03-30 10:15:34] 距上次重启时间:3600 秒 (冷却期已过)
[2026-03-30 10:15:36] 容器启动命令执行成功
[2026-03-30 10:15:36] 等待 30 秒让 EMQX 服务完全启动...
[2026-03-30 10:16:06] 验证服务启动结果...
[2026-03-30 10:16:06] ✓ 容器运行状态正常
[2026-03-30 10:16:11] ✓ EMQX 进程已启动
[2026-03-30 10:16:11] ✓ EMQX 端口监听正常
[2026-03-30 10:16:21] ✓ EMQX 服务状态验证通过
[2026-03-30 10:16:21] ==========================================
[2026-03-30 10:16:21] EMQX 容器重启成功,更新重启时间戳
[2026-03-30 10:16:21] ==========================================
```
---
## 5. 配置参数
### 5.1 核心配置参数
| 参数名 | 变量名 | 默认值 | 说明 | 调整建议 |
|--------|--------|--------|------|----------|
| **容器名称** | `CONTAINER_NAME` | uemqx | EMQX容器名称 | 根据实际部署调整 |
| **日志文件** | `LOG_FILE` | /var/log/scripts/monitor_emqx_service.log | 日志文件路径 | 根据实际环境调整 |
| **状态文件** | `STATE_FILE` | /var/log/scripts/.emqx_monitor_state | 失败计数器状态文件 | 固定路径 |
| **PID文件** | `PID_FILE` | /var/log/scripts/.emqx_monitor.pid | 进程锁文件,防止并发执行 | 固定路径 |
| **日志大小限制** | `MAX_LOG_SIZE` | 5MB | 日志文件大小限制,超过后自动轮转 | 可根据需要调整 |
| **日志保留天数** | `LOG_RETENTION_DAYS` | 30天 | 轮转后的日志文件保留天数 | 可根据需要调整 |
| **最大失败次数** | `MAX_FAILURES` | 3 | 连续失败达此阈值触发重启 | 网络不稳定环境可增至 4-5 次 |
| **失败检测间隔** | `FAILURE_INTERVAL` | 5 秒 | 每次检测之间的等待时间 | 可调整为 3-10 秒 |
| **重启冷却时间** | `COOLDOWN_PERIOD` | 1800 秒 (30 分钟) | 两次重启之间的最小间隔 | 关键业务可延长至 60 分钟 |
| **启动等待时间** | `START_WAIT_TIME` | 30 秒 | 容器启动后的等待时长 | 根据实际启动速度调整 |
| **服务检查等待** | `SERVICE_CHECK_WAIT` | 10 秒 | 验证服务状态前的等待 | 可调整为 5-15 秒 |
### 5.2 状态文件字段
状态文件:`/var/log/scripts/.emqx_monitor_state`
```bash
FAILURE_COUNT=0 # 当前连续失败次数
LAST_RESTART_TIME=0 # 上次重启时间戳 (Unix timestamp)
LAST_SUCCESS_TIME=0 # 上次成功检测时间戳
```
---
## 6. 执行要求
### 6.1 执行时机
```cron
*/5 * * * * /opt/scripts/monitor_emqx_service.sh
```
**执行频率**:每5分钟执行一次
### 6.2 执行环境
- **操作系统**:Linux (CentOS/Ubuntu/Debian等主流发行版)
- **必需工具**:Docker、bash、netstat(或ss)
- **权限要求**:root或sudo权限(用于访问Docker和写入日志)
- **磁盘空间**:至少100MB(日志文件增长需监控)
- **内存**:最低512MB可用内存
### 6.3 资源要求
为避免同时执行造成资源竞争,建议错开执行时间:
| 脚本 | 执行时间 | 偏移量 |
|------|----------|--------|
| `monitor_emqx_service.sh` | */5 * * * * | +0 分钟 |
| `monitor_mysql_service.sh` | */5 * * * * | +1 分钟 |
| `monitor_redis_service.sh` | */5 * * * * | +2 分钟 |
| `monitor_external_api_services_v2.sh` | */5 * * * * | +3 分钟 |
| `monitor_inner_api_services.sh` | */5 * * * * | +4 分钟 |
**crontab 配置示例**:
```cron
*/5 * * * * /opt/scripts/monitor_emqx_service.sh
*/5 * * * * sleep 60 && /opt/scripts/monitor_mysql_service.sh
*/5 * * * * sleep 120 && /opt/scripts/monitor_redis_service.sh
*/5 * * * * sleep 180 && /opt/scripts/monitor_external_api_services_v2.sh
*/5 * * * * sleep 240 && /opt/scripts/monitor_inner_api_services.sh
```
---
## 7. 错误处理
### 7.1 错误处理流程
| 级别 | 标识 | 使用场景 |
|------|------|----------|
| **信息** | 无标记 | 正常检测通过、配置说明 |
| **警告** | `警告:` | 冷却期内检测、非致命异常 |
| **错误** | `错误:` | 重启失败、容器不存在等严重问题 |
### 7.2 错误处理流程
```
检测失败
记录失败次数 → 判断是否达阈值
是 → 检查冷却期
冷却期内 → 记录日志,跳过重启
冷却期外 → 执行重启
重启成功 → 更新状态,返回成功
重启失败 → 记录详细错误,提示人工介入
```
### 7.3 异常情况处理
| 异常场景 | 处理策略 | 日志输出 |
|----------|----------|----------|
| **容器不存在** | 放弃重启,提示手动创建 | `错误:容器 'uemqx' 不存在!` |
| **启动后立即停止** | 记录失败,查看容器日志 | `错误:容器启动后立即停止` |
| **冷却期内故障** | 只记录不重启 | `警告:容器在冷却期内` |
| **端口检查工具缺失** | 降级检查 (跳过端口验证) | 记录警告信息 |
| **状态文件损坏** | 重建状态文件,从零开始 | 自动初始化 |
---
## 8. 验收标准
### 8.1 监测准确性
- ✅ 能正确识别容器运行状态
- ✅ 能准确检测EMQX进程状态
- ✅ 能正确验证端口监听状态
- ✅ 能正确执行CLI命令检查
### 8.2 失败计数机制
- ✅ 单次失败时正确累加计数器
- ✅ 失败计数能持久化保存,进程重启不丢失
- ✅ 检查成功时正确重置计数器为0
- ✅ 未达阈值时不会触发重启
- ✅ 达到阈值时正确触发重启并清零计数器
- ✅ 冷却期内不会重复触发重启
### 8.3 恢复有效性
- ✅ 连续失败达到阈值后能正确触发容器重启
- ✅ 重启操作能成功执行
- ✅ 重启后EMQX服务确实正常运行
- ✅ 冷却时间机制有效防止频繁重启
### 8.4 日志完整性
- ✅ 每次检查都有完整的开始和结束记录
- ✅ 日志中包含当前失败次数和阈值信息
- ✅ 异常情况有详细的诊断信息
- ✅ 恢复过程有清晰的步骤记录
- ✅ 不会静默失败
### 8.5 功能验收
#### 8.5.1 正常场景
- ✅ 服务正常时,检测通过且不触发重启
- ✅ 单次检测失败后,等待 5 秒再次检测
- ✅ 连续 3 次失败后才执行重启
- ✅ 重启后服务恢复,计数器清零
#### 8.5.2 异常场景
- ✅ 网络波动导致单次失败,不触发重启
- ✅ 冷却期内检测到故障,不执行重启
- ✅ 容器不存在时,输出明确错误提示
- ✅ 重启失败后,提供详细的诊断建议
### 8.6 性能验收
- ✅ 单次检测耗时 < 3 秒
- ✅ 重启流程总耗时 < 2 分钟
- ✅ 日志文件大小控制在合理范围 (支持轮转)
### 8.7 稳定性验收
- ✅ 连续运行 30 天无异常
- ✅ 重启次数符合预期 (非频繁重启)
- ✅ 状态文件数据准确,无丢失损坏
---
## 9. 故障排查
### 9.1 常见问题
```
问题:EMQX 服务不可用
步骤 1: 查看最近日志
tail -100 /var/log/scripts/monitor_emqx_service.log
步骤 2: 检查容器状态
docker ps -a | grep uemqx
步骤 3: 查看容器日志
docker logs uemqx --tail 50
步骤 4: 检查状态文件
cat /var/log/scripts/.emqx_monitor_state
步骤 5: 手动执行检测脚本
bash -x /opt/scripts/monitor_emqx_service.sh
```
### 9.2 常见问题处理
#### 问题 1: 服务检查失败(未达阈值)
```
[警告] EMQX 服务状态异常:emqx is not running
[警告] 失败计数器更新: 0 -> 1 (阈值: 3)
```
说明:这是正常现象,系统在等待下次重试确认是否为持续故障
#### 问题 2: 服务检查失败(达到阈值)
```
[错误] 连续失败达到阈值(3次),触发容器重启
```
说明:已连续3次检查失败,系统正在执行自动恢复
#### 问题 3: 冷却期内故障
```
[警告] 容器在冷却期内,距上次重启仅 300 秒
```
说明:这是设计行为,防止频繁重启影响业务
#### 问题 4: 频繁重启
**现象**: 日志中频繁出现重启记录
**原因**:
- 服务本身存在问题,无法正常启动
- 配置参数过于敏感
**解决**:
```bash
# 1. 查看重启频率
grep "重启" /var/log/scripts/monitor_emqx_service.log | tail -20
# 2. 调整冷却时间 (延长至 60 分钟)
编辑脚本,修改 COOLDOWN_PERIOD=3600
# 3. 查看容器日志定位根本原因
docker logs uemqx --tail 100
```
#### 问题 5: 状态文件异常
**现象**: 计数器不准确或重启时间错误
**解决**:
```bash
# 1. 备份并删除状态文件
cp /var/log/scripts/.emqx_monitor_state /var/log/scripts/.emqx_monitor_state.bak
rm /var/log/scripts/.emqx_monitor_state
# 2. 重启脚本会自动重建状态文件
/opt/scripts/monitor_emqx_service.sh
```
### 9.3 诊断命令速查表
| 目的 | 命令 | 说明 |
|------|------|------|
| **查看实时日志** | `tail -f /var/log/scripts/monitor_emqx_service.log` | 实时监控检测过程 |
| **统计异常发生频率** | `grep "警告\|错误" /var/log/scripts/monitor_emqx_service.log \| wc -l` | 统计异常次数 |
| **查看失败计数变化趋势** | `grep "失败计数器更新" /var/log/scripts/monitor_emqx_service.log` | 分析计数变化 |
| **统计触发重启的次数** | `grep "连续失败达到阈值" /var/log/scripts/monitor_emqx_service.log \| wc -l` | 统计重启次数 |
| **查看最近重启记录** | `grep "重启" /var/log/scripts/monitor_emqx_service.log \| tail -10` | 分析重启频率 |
| **检查容器状态** | `docker ps -a \| grep uemqx` | 查看容器运行状态 |
| **查看容器日志** | `docker logs uemqx --tail 50` | 排查容器内部问题 |
| **查看状态文件** | `cat /var/log/scripts/.emqx_monitor_state` | 查看计数器和时间戳 |
| **手动重置失败计数器** | `echo "FAILURE_COUNT=0\nLAST_RESTART_TIME=0\nLAST_SUCCESS_TIME=0" > /var/log/scripts/.emqx_monitor_state` | 恢复后无需手动操作 |
| **手动执行检测** | `bash -x /opt/scripts/monitor_emqx_service.sh` | 调试模式执行脚本 |
| **检查端口监听** | `docker exec uemqx netstat -tuln` | 验证端口是否正常 |
| **检查进程状态** | `docker exec uemqx pgrep -f emqx` | 验证进程是否存在 |
| **检查服务状态** | `docker exec uemqx emqx_ctl status` | 验证服务是否健康 |
---
## 10. 维护建议
### 10.1 定期检查
- **每日**: 查看日志是否有异常重启记录
- **每周**: 检查状态文件大小和内容
- **每月**: 评估重启频率是否合理
### 10.2 日志管理
- **日志轮转**: 建议配置 logrotate 定期压缩旧日志
- **清理策略**: 保留最近 30 天的日志
- **大小监控**: 当日志文件>100MB 时进行轮转
### 10.3 参数调优
根据实际运行情况调整参数:
- **网络不稳定环境**: 增加 `MAX_FAILURES` 至 4-5 次
- **启动缓慢容器**: 增加 `START_WAIT_TIME` 至 60 秒
- **关键业务场景**: 延长 `COOLDOWN_PERIOD` 至 60 分钟
### 10.4 备份策略
- **脚本备份**: `/opt/scripts/monitor_emqx_service.sh` 纳入版本控制
- **配置备份**: 定期备份 `/var/log/scripts/.emqx_monitor_state`
- **日志备份**: 重要时期的日志单独归档
---
## 11. 附录
### 11.1 相关文档
- `_PRD_预定系统定时脚本需求文档概览.md` - 总体设计规范
- `monitor_emqx_service.sh` - 实际执行脚本
- `/var/log/scripts/monitor_emqx_service.log` - 运行日志
- 《自动化脚本、代码生成与文档标准化综合规范》 - 脚本开发规范
- 《定时任务配置同步规范》 - 调度配置规范
### 11.2 缩略语说明
| 缩写 | 全称 | 说明 |
|------|------|------|
| EMQX | ElasticMQ Telemetry X | 开源 MQTT 消息代理 |
| MQTT | Message Queuing Telemetry Transport | 消息队列遥测传输协议 |
| CLI | Command Line Interface | 命令行界面 |
| API | Application Programming Interface | 应用程序编程接口 |
| SSL | Secure Sockets Layer | 安全套接字层 |
| WebSocket | WebSocket Protocol | 全双工通信协议 |
### 11.3 参考资料
- EMQX 官方文档:https://www.emqx.io/docs/
- Docker 官方文档:https://docs.docker.com/
- Crontab 配置指南:https://man7.org/linux/man-pages/man5/crontab.5.html
---
## 需求规范
- 代码规范:Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md
- 问题总结:Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md
- 方法总结:Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md
- 文档规范:Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md
- 测试规范:Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md
\ No newline at end of file
# _PRD_预定系统EMQX服务监控需求文档.md
> 版本:V1.0
> 更新日期:2026-01-29
> 适用范围:预定系统EMQX消息队列服务监测与自愈
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_emqx_service.sh`
---
## 1. 背景与目标
### 1.1 背景
EMQX作为预定系统的核心消息队列服务,负责处理设备通信、消息路由等关键功能。服务中断会直接影响系统整体可用性,需要建立自动监控和恢复机制。
### 1.2 目标
实现EMQX服务的自动化定时监控功能,具备:
- 容器运行状态监测
- EMQX进程存在性验证
- 服务可用性状态检查
- 异常时的自动重启恢复
- 多层次的状态验证机制
- 完整的操作日志记录
---
## 2. 总体范围
### 2.1 纳入监控对象
- **容器名称**`uemqx`
- **监控维度**
- 容器运行状态(docker ps)
- EMQX进程存在性(pgrep -f "emqx")
- 服务可用性(emqx_ctl status命令)
- 服务状态详细信息
### 2.2 不在本期范围
- EMQX集群状态监控
- 消息队列性能指标监控
- 客户端连接数统计
- 消息流量监控
---
## 3. 术语说明
- **服务状态验证**:通过EMQX自带的emqx_ctl命令检查服务运行状态
- **渐进式恢复**:先检查再重启,重启后再次验证的恢复策略
- **多层次检测**:容器层→进程层→服务层的逐级验证机制
---
## 4. 功能需求
### 4.1 监控流程
#### 4.1.1 状态检查机制
按以下顺序进行状态验证:
1. **容器运行检查**
- 使用`docker ps`检查uemqx容器是否运行
- 容器未运行时直接进入重启流程
2. **进程存在检查**
- 在容器内执行`pgrep -f "emqx"`查找EMQX进程
- 进程不存在时标记为异常状态
3. **服务可用性检查**
- 执行`emqx_ctl status`命令检查服务状态
- 验证返回结果中包含"is started"标识
- 确认服务真正可用而非仅进程存在
#### 4.1.2 异常处理流程
当检测到异常时执行:
1. **容器重启**
- 停止可能挂起的容器实例
- 启动uemqx容器
- 等待30秒让服务完全启动
2. **状态验证循环**
- 检查容器是否成功运行
- 验证EMQX进程是否启动
- 等待10秒后再次检查服务状态
- 确认emqx_ctl status返回正常
3. **最终状态确认**
- 所有检查项都通过才算恢复成功
- 任一环节失败都记录为恢复失败
### 4.2 日志与审计
#### 4.2.1 日志文件
- 主日志:`/var/log/scripts/monitor_emqx_service.log`
#### 4.2.2 日志级别和格式
每行必须包含:
- 时间戳:`YYYY-MM-DD HH:MM:SS`
- 级别标识:信息/警告/错误/成功
- 具体操作和结果状态
示例:
```
2026-01-29 10:00:00 - 开始检查 EMQX 服务状态...
2026-01-29 10:00:01 - EMQX 服务状态正常
2026-01-29 10:05:00 - 警告: 容器正在运行,EMQX 进程存在,但 emqx_ctl 命令执行失败
2026-01-29 10:05:05 - EMQX 未运行。正在尝试重启容器 'uemqx'...
2026-01-29 10:05:35 - 信息: 等待 30 秒让 EMQX 服务完全启动..............................
2026-01-29 10:06:05 - 成功: EMQX 容器已成功启动。
```
### 4.3 错误处理
#### 4.3.1 容器状态异常
- 容器不存在:记录严重错误并建议手动创建
- 容器启动失败:记录错误详情和可能原因
- 容器启动后立即停止:记录错误并建议检查日志
#### 4.3.2 服务状态异常
- emqx_ctl命令不存在:记录警告并降级检查
- 命令执行失败:记录错误并尝试重启
- 状态返回异常:记录详细状态信息
#### 4.3.3 恢复过程异常
- 重启后容器仍不运行:记录失败并建议手动干预
- 进程启动但服务不可用:记录详细诊断信息
- 超时等待后仍未恢复:记录超时并建议深入排查
---
## 5. 配置参数
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| CONTAINER_NAME | uemqx | EMQX容器名称 |
| LOG_FILE | /var/log/scripts/monitor_emqx_service.log | 日志文件路径 |
| STARTUP_WAIT | 30秒 | 容器启动等待时间 |
| STATUS_CHECK_WAIT | 10秒 | 状态验证等待时间 |
---
## 6. 执行要求
### 6.1 执行时机
- 建议:每5分钟执行一次
- cron配置:`*/5 * * * * /opt/scripts/monitor_emqx_service.sh`
### 6.2 执行环境
- 需要docker命令权限
- uemqx容器必须存在
- 容器内需要有emqx_ctl命令
### 6.3 资源要求
- 内存:监控过程占用少量内存
- CPU:状态检查操作轻量级
- 网络:仅本地容器通信
---
## 7. 验收标准
1. **监控准确性**
- 能正确识别容器运行状态
- 准确检测EMQX进程存在性
- 正确验证服务可用性状态
2. **恢复有效性**
- 异常检测后能正确触发重启
- 重启操作能成功执行
- 恢复后服务状态确实正常
3. **日志完整性**
- 每次检查都有完整的开始和结束记录
- 异常情况有详细的诊断信息
- 恢复过程有清晰的步骤记录
4. **系统稳定性**
- 监控过程不影响EMQX正常运行
- 重启操作安全可靠
- 不会产生连锁故障
---
## 8. 故障排查
### 8.1 常见问题
1. **容器不存在**
```
错误: 容器 'uemqx' 不存在!
```
解决:重新创建uemqx容器
2. **命令执行失败**:
```
警告: 容器正在运行,EMQX 进程存在,但 emqx_ctl 命令执行失败
```
解决:检查容器内EMQX安装状态
3. **重启失败**:
```
错误: 无法启动 EMQX 容器。
```
解决:查看容器日志`docker logs uemqx`
### 8.2 诊断命令
```bash
# 检查容器状态
docker ps | grep uemqx
# 检查容器内进程
docker exec uemqx pgrep -f "emqx"
# 手动检查服务状态
docker exec uemqx emqx_ctl status
# 查看容器日志
docker logs uemqx --tail 50
# 手动重启测试
docker restart uemqx
```
### 8.3 日志分析
```bash
# 实时监控脚本执行
tail -f /var/log/scripts/monitor_emqx_service.log
# 统计异常发生频率
grep "警告\|错误" /var/log/scripts/monitor_emqx_service.log | wc -l
# 查看最近的恢复记录
grep "成功: EMQX 容器已成功启动" /var/log/scripts/monitor_emqx_service.log
```
---
## 需求规范
代码规范:Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md
问题总结:Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md
方法总结:Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md
文档规范:Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md
测试规范:Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md
\ No newline at end of file
# _PRD_预定系统EMQX服务监控需求文档_计划执行.md
> 版本:V2.1
> 更新日期:2026-03-30
> 关联文档:_PRD_预定系统EMQX服务监控需求文档.md (V2.1)
> 关联脚本:monitor_emqx_service.sh (V2.0 → V2.1)
---
## 一、变更概述
### 1.1 变更背景
V2.0版本已引入连续失败计数机制和重启冷却时间,但为进一步优化和统一,需要对现有实现进行验证和可能的调整。
### 1.2 变更目标
确保EMQX服务监控脚本与统一的连续失败阈值机制完全一致:
- 验证现有实现是否符合需求文档
- 调整日志格式以统一规范
- 确保状态持久化正常工作
- 验证重启冷却时间机制
---
## 二、当前脚本分析
### 2.1 现有功能(V2.0)
```
开始 → 读取状态文件
多维度健康检查
┌───┴───┐
↓ ↓
检查成功 检查失败
↓ ↓
清零计数器 等待5秒重试
↓ ↓
结束 累加计数器
达到阈值?
┌─────┴─────┐
↓ ↓
是 否
↓ ↓
检查冷却期 跳过重启
冷却期外?
┌───┴───┐
↓ ↓
是 否
↓ ↓
重启容器 记录警告
```
### 2.2 已实现功能
- ✅ 连续失败计数机制(MAX_FAILURES=3)
- ✅ 失败重试间隔(FAILURE_INTERVAL=5秒)
- ✅ 重启冷却时间(COOLDOWN_PERIOD=1800秒)
- ✅ 多维度健康检查(容器+进程+端口+CLI)
- ✅ 状态持久化(.emqx_monitor_state文件)
### 2.3 需要验证/调整的内容
1. **日志格式**:确保与统一规范一致
2. **状态文件字段**:验证字段完整性
3. **错误处理**:确保所有异常情况都有处理
---
## 三、实现计划
### 3.1 日志格式统一
#### 3.1.1 当前格式
```
[2026-03-30 10:15:24] 检测失败 (连续失败 1/3 次)
```
#### 3.1.2 统一格式
```
2026-03-30 10:15:24 - 警告: EMQX 服务状态异常:emqx is not running
2026-03-30 10:15:24 - 失败计数器更新: 0 -> 1 (阈值: 3)
```
### 3.2 状态文件验证
#### 3.2.1 状态文件位置
```bash
/var/log/scripts/.emqx_monitor_state
```
#### 3.2.2 状态文件字段
```bash
FAILURE_COUNT=0 # 当前连续失败次数
LAST_RESTART_TIME=0 # 上次重启时间戳 (Unix timestamp)
LAST_SUCCESS_TIME=0 # 上次成功检测时间戳
```
### 3.3 配置参数验证
| 参数名 | 变量名 | 默认值 | 说明 |
|--------|--------|--------|------|
| 容器名称 | `CONTAINER_NAME` | uemqx | EMQX容器名称 |
| 日志文件 | `LOG_FILE` | /var/log/scripts/monitor_emqx_service.log | 日志文件路径 |
| 状态文件 | `STATE_FILE` | /var/log/scripts/.emqx_monitor_state | 失败计数器状态文件 |
| 最大失败次数 | `MAX_FAILURES` | 3 | 触发重启的最大连续失败次数 |
| 失败检测间隔 | `FAILURE_INTERVAL` | 5 | 每次检测之间的等待时间(秒) |
| 重启冷却时间 | `COOLDOWN_PERIOD` | 1800 | 两次重启之间的最小间隔(秒) |
| 启动等待时间 | `START_WAIT_TIME` | 30 | 容器启动后的等待时长(秒) |
| 服务检查等待 | `SERVICE_CHECK_WAIT` | 10 | 验证服务状态前的等待(秒) |
### 3.4 主流程验证
#### 3.4.1 单次检查流程
```bash
# 1. 读取状态文件
read_state_file
# 2. 执行健康检查
check_container_status
check_emqx_process
check_port_listening
check_emqx_status
# 3. 根据结果处理
if all_checks_pass; then
reset_failure_count
log_success
else
increment_failure_count
write_state_file
if failure_count >= MAX_FAILURES; then
check_cooldown_period
if cooldown_expired; then
restart_container
fi
fi
fi
```
#### 3.4.2 重启流程
```bash
restart_container() {
log "开始执行 EMQX 容器重启流程"
# 1. 停止容器
docker stop uemqx
# 2. 等待2秒
sleep 2
# 3. 启动容器
docker start uemqx
# 4. 等待启动完成
sleep START_WAIT_TIME
# 5. 验证重启结果
verify_restart
# 6. 更新状态
update_restart_timestamp
}
```
### 3.5 日志输出示例
#### 3.5.1 正常场景
```
2026-03-30 10:00:00 - ==========================================
2026-03-30 10:00:00 - EMQX 服务健康检查开始
2026-03-30 10:00:00 - ==========================================
2026-03-30 10:00:00 - 配置参数:
2026-03-30 10:00:00 - - 最大失败次数:3
2026-03-30 10:00:00 - - 失败检测间隔:5 秒
2026-03-30 10:00:00 - - 重启冷却时间:1800 秒 (30 分钟)
2026-03-30 10:00:00 - ==========================================
2026-03-30 10:00:00 - 读取状态文件: 当前失败次数 = 0, 距上次重启 = 3600 秒
2026-03-30 10:00:01 - ✓ 容器运行状态正常
2026-03-30 10:00:03 - ✓ EMQX 进程已启动
2026-03-30 10:00:03 - ✓ EMQX 端口监听正常
2026-03-30 10:00:05 - ✓ EMQX 服务状态验证通过
2026-03-30 10:00:05 - 检查成功,重置失败计数器为 0
2026-03-30 10:00:05 - ==========================================
2026-03-30 10:00:05 - EMQX 服务健康检查完成,状态正常
2026-03-30 10:00:05 - ==========================================
```
#### 3.5.2 失败场景
```
2026-03-30 10:15:23 - ==========================================
2026-03-30 10:15:23 - EMQX 服务健康检查开始
2026-03-30 10:15:23 - ==========================================
2026-03-30 10:15:23 - 读取状态文件: 当前失败次数 = 0, 距上次重启 = 3600 秒
2026-03-30 10:15:24 - 警告: EMQX 服务状态异常:emqx is not running
2026-03-30 10:15:24 - 失败计数器更新: 0 -> 1 (阈值: 3)
2026-03-30 10:15:29 - 警告: EMQX 服务状态异常:emqx is not running
2026-03-30 10:15:29 - 失败计数器更新: 1 -> 2 (阈值: 3)
2026-03-30 10:15:34 - 警告: EMQX 服务状态异常:emqx is not running
2026-03-30 10:15:34 - 失败计数器更新: 2 -> 3 (阈值: 3)
2026-03-30 10:15:34 - 错误: 连续失败达到阈值(3次),触发容器重启
2026-03-30 10:15:34 - 重置失败计数器为 0
2026-03-30 10:15:34 - ==========================================
2026-03-30 10:15:34 - 开始执行 EMQX 容器重启流程
2026-03-30 10:15:34 - ==========================================
2026-03-30 10:15:34 - 距上次重启时间:3600 秒 (冷却期已过)
2026-03-30 10:15:36 - 容器启动命令执行成功
2026-03-30 10:15:36 - 等待 30 秒让 EMQX 服务完全启动...
2026-03-30 10:16:06 - 验证服务启动结果...
2026-03-30 10:16:06 - ✓ 容器运行状态正常
2026-03-30 10:16:11 - ✓ EMQX 进程已启动
2026-03-30 10:16:11 - ✓ EMQX 端口监听正常
2026-03-30 10:16:21 - ✓ EMQX 服务状态验证通过
2026-03-30 10:16:21 - ==========================================
2026-03-30 10:16:21 - EMQX 容器重启成功,更新重启时间戳
2026-03-30 10:16:21 - ==========================================
```
#### 3.5.3 冷却期内场景
```
2026-03-30 10:20:00 - ==========================================
2026-03-30 10:20:00 - EMQX 服务健康检查开始
2026-03-30 10:20:00 - ==========================================
2026-03-30 10:20:00 - 读取状态文件: 当前失败次数 = 0, 距上次重启 = 200 秒
2026-03-30 10:20:01 - 警告: EMQX 服务状态异常:emqx is not running
2026-03-30 10:20:01 - 失败计数器更新: 0 -> 1 (阈值: 3)
2026-03-30 10:20:06 - 警告: EMQX 服务状态异常:emqx is not running
2026-03-30 10:20:06 - 失败计数器更新: 1 -> 2 (阈值: 3)
2026-03-30 10:20:11 - 警告: EMQX 服务状态异常:emqx is not running
2026-03-30 10:20:11 - 失败计数器更新: 2 -> 3 (阈值: 3)
2026-03-30 10:20:11 - 错误: 连续失败达到阈值(3次),准备执行重启
2026-03-30 10:20:11 - 警告: 距上次重启仅 200 秒,冷却期未过(1800秒),跳过本次重启
2026-03-30 10:20:11 - 失败计数器保持为 3,等待冷却期过后重新检查
2026-03-30 10:20:11 - ==========================================
```
---
## 四、代码验证清单
### 4.1 配置变量区域
```bash
# 验证以下变量存在且值正确
CONTAINER_NAME="uemqx"
LOG_FILE="/var/log/scripts/monitor_emqx_service.log"
STATE_FILE="/var/log/scripts/.emqx_monitor_state"
MAX_FAILURES=3
FAILURE_INTERVAL=5
COOLDOWN_PERIOD=1800
START_WAIT_TIME=30
SERVICE_CHECK_WAIT=10
```
### 4.2 状态文件函数
```bash
# 验证以下函数存在
read_state_file() # 读取状态文件
write_state_file() # 写入状态文件
reset_failure_count() # 重置失败计数器
increment_failure_count() # 累加失败计数器
```
### 4.3 健康检查函数
```bash
# 验证以下函数存在
check_container_status() # 检查容器运行状态
check_emqx_process() # 检查EMQX进程
check_port_listening() # 检查端口监听
check_emqx_status() # 检查服务状态(CLI)
```
### 4.4 重启流程函数
```bash
# 验证以下函数存在
restart_emqx_container() # 重启EMQX容器
verify_restart() # 验证重启结果
is_in_cooldown_period() # 判断是否在冷却期
```
### 4.5 日志格式验证
```bash
# 验证日志输出包含以下信息
# - 时间戳格式:YYYY-MM-DD HH:MM:SS
# - 失败计数信息:失败计数器更新: X -> Y (阈值: Z)
# - 阈值达到提示:错误: 连续失败达到阈值(N次),触发容器重启
# - 冷却期提示:警告: 距上次重启仅 X 秒,冷却期未过
```
---
## 五、测试计划
### 5.1 单元测试
| 测试项 | 测试方法 | 预期结果 |
|--------|----------|----------|
| 状态文件读取 | 删除状态文件后执行 | 自动初始化为0 |
| 状态文件写入 | 写入后检查文件内容 | 正确写入所有字段 |
| 冷却期判断 | 距上次重启<1800秒 | 跳过重启并记录警告 |
| 计数器累加 | 连续失败3次 | 第3次触发重启 |
### 5.2 集成测试
| 测试场景 | 操作步骤 | 预期结果 |
|----------|----------|----------|
| 正常场景 | EMQX正常运行 | 检查通过,计数器清零 |
| 单次失败 | 停止EMQX,执行脚本1次 | 计数器=1,不重启 |
| 连续失败 | 停止EMQX,执行脚本3次 | 第3次触发重启 |
| 冷却期保护 | 重启后立即再触发失败 | 跳过重启,记录冷却期警告 |
| 恢复验证 | 重启后检查服务 | 服务恢复正常,计数器清零 |
### 5.3 压力测试
| 测试项 | 测试方法 | 预期结果 |
|--------|----------|----------|
| 长时间运行 | 连续运行7天 | 无内存泄漏,日志正常轮转 |
| 频繁失败 | 模拟频繁启停 | 冷却期机制正常工作 |
| 状态文件损坏 | 删除/损坏状态文件 | 自动重建,不影响功能 |
---
## 六、部署步骤
### 6.1 备份
```bash
cp /opt/scripts/monitor_emqx_service.sh /opt/scripts/monitor_emqx_service.sh.bak_v2.0
```
### 6.2 部署
```bash
# 上传新脚本
scp monitor_emqx_service.sh user@server:/opt/scripts/
# 设置执行权限
chmod +x /opt/scripts/monitor_emqx_service.sh
# 确保日志目录存在
mkdir -p /var/log/scripts/
touch /var/log/scripts/.emqx_monitor_state
```
### 6.3 验证
```bash
# 手动执行测试
/opt/scripts/monitor_emqx_service.sh
# 检查日志
tail -f /var/log/scripts/monitor_emqx_service.log
# 检查状态文件
cat /var/log/scripts/.emqx_monitor_state
# 模拟失败测试
docker stop uemqx
/opt/scripts/monitor_emqx_service.sh
docker start uemqx
```
---
## 七、回滚方案
### 7.1 回滚步骤
```bash
# 停止定时任务
crontab -e # 注释掉相关行
# 恢复原脚本
cp /opt/scripts/monitor_emqx_service.sh.bak_v2.0 /opt/scripts/monitor_emqx_service.sh
# 恢复状态文件(如需要)
cp /var/log/scripts/.emqx_monitor_state.bak /var/log/scripts/.emqx_monitor_state
# 重启定时任务
crontab -e # 取消注释
```
---
## 八、验收标准
### 8.1 功能验收
- [ ] 单次失败时计数器正确累加
- [ ] 达到阈值时正确触发重启
- [ ] 检查成功时计数器正确清零
- [ ] 冷却期内不会重复触发重启
- [ ] 状态持久化正常工作
### 8.2 日志验收
- [ ] 日志格式符合统一规范
- [ ] 包含失败计数信息
- [ ] 包含阈值判断信息
- [ ] 冷却期警告信息明确
### 8.3 稳定性验收
- [ ] 长期运行无异常
- [ ] 状态文件异常时能正确处理
- [ ] 冷却期机制有效防止频繁重启
---
## 需求规范
- 代码规范:Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md
- 问题总结:Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md
- 方法总结:Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md
- 文档规范:Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md
- 测试规范:Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md
# _PRD_预定系统MySQL服务监控需求文档.md
> 版本:V1.0
> 更新日期:2026-01-29
> 适用范围:预定系统MySQL数据库服务监测与自愈
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_mysql_service.sh`
> 版本:V1.1
> 更新日期:2026-03-30
> 适用范围:预定系统MySQL数据库服务监测与自愈
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_mysql_service.sh`
---
## 版本变更记录
| 版本 | 日期 | 变更内容 | 变更人 |
|------|------|----------|--------|
| V1.2 | 2026-03-30 | 新增日志自动轮转机制(5MB轮转、保留30天);新增PID文件锁机制防止并发执行 | Claude |
| V1.1 | 2026-03-30 | 新增连续失败阈值判断机制,避免单次临时堵塞触发重启影响业务 | Claude |
| V1.0 | 2026-01-29 | 初始版本,定义MySQL服务监控基础需求 | - |
### V1.1 详细变更说明
#### 新增功能
1. **连续失败阈值机制**
- 新增失败计数器,单次失败时仅累加计数,不立即重启
- 默认阈值为3次,连续失败达到阈值才触发容器重启
- 检查成功后自动重置失败计数器
2. **状态持久化**
- 新增状态文件 `STATE_FILE`,持久化保存失败次数
- 进程重启后计数器状态不丢失
3. **容器停止特殊处理**
- 容器停止属于严重故障,直接触发重启,不计入失败次数
#### 配置参数变更
| 新增参数 | 默认值 | 说明 |
|----------|--------|------|
| STATE_FILE | /var/log/scripts/monitor_mysql_service.state | 失败计数器状态文件 |
| MAX_FAILURES | 3 | 触发重启的最大连续失败次数 |
#### 日志格式变更
- 日志中新增失败计数信息展示:`失败计数器更新: X -> Y (阈值: Z)`
- 新增阈值达到提示:`错误: 连续失败达到阈值(N次),触发容器重启`
#### 诊断命令变更
- 新增状态文件查看命令
- 新增失败计数变化趋势分析命令
---
---
......@@ -18,6 +59,8 @@ MySQL作为预定系统的核心数据库服务,存储着所有业务数据。
- 数据库连接可用性验证
- SQL执行功能测试
- 异常时的自动重启恢复
- **连续失败阈值判断机制,避免单次临时堵塞触发重启**
- 失败次数记录与重置机制
- 密码自动获取机制
- 多层次的健康检查
- 完整的操作日志记录
......@@ -40,6 +83,13 @@ MySQL作为预定系统的核心数据库服务,存储着所有业务数据。
- 数据库备份状态监控
- 主从复制状态监控
### 2.3 核心机制说明
**连续失败重启机制**:为避免因网络抖动、临时堵塞等瞬时故障导致的不必要重启,采用失败计数器机制:
- 单次检查失败时,仅记录失败次数,不立即重启
- 连续失败达到阈值(默认3次)时,才触发容器重启
- 检查成功后,自动重置失败计数器
- 通过状态文件持久化保存失败次数,避免计数器因进程重启丢失
---
## 3. 术语说明
......@@ -57,24 +107,49 @@ MySQL作为预定系统的核心数据库服务,存储着所有业务数据。
#### 4.1.1 状态检查机制
按以下顺序进行健康验证:
1. **容器运行检查**
1. **读取失败计数器**
- 从状态文件读取当前连续失败次数
- 状态文件不存在时初始化为0
2. **容器运行检查**
- 使用`docker ps`检查umysql容器是否运行
- 容器未运行时直接进入重启流程
- 容器未运行时**直接进入重启流程**(容器停止属于严重故障)
2. **密码获取验证**
3. **密码获取验证**
- 从容器环境变量获取MYSQL_ROOT_PASSWORD
- 密码获取失败时标记为异常状态
3. **连接可用性测试**
4. **连接可用性测试**
- 使用`mysqladmin ping`测试基本连接
- 验证TCP连接和认证通过
4. **功能完整性验证**
5. **功能完整性验证**
- 执行`SELECT 1`简单查询
- 确认数据库具备完整SQL执行能力
#### 4.1.2 异常处理流程
当检测到异常时执行:
#### 4.1.2 失败处理机制(核心)
采用**连续失败阈值判断**机制,避免单次临时故障触发重启:
1. **单次失败处理**
- 任一检查环节失败时,失败计数器+1
- 将最新失败次数写入状态文件持久化保存
- 记录警告日志,说明当前失败次数和阈值
- **不触发重启,直接退出本次检查**
2. **连续失败达到阈值**
- 当失败次数 ≥ MAX_FAILURES(默认3次)时
- 清零失败计数器
- 触发容器重启流程
- 记录错误日志说明已连续失败N次
3. **检查成功处理**
- 所有检查项通过时
- 将失败计数器清零并更新状态文件
- 记录成功日志
- 正常退出本次检查
#### 4.1.3 异常处理流程
当检测到异常且达到失败阈值时执行:
1. **容器重启**
- 停止可能挂起的容器实例
......@@ -89,6 +164,7 @@ MySQL作为预定系统的核心数据库服务,存储着所有业务数据。
3. **最终确认**
- 所有检查项都通过才算恢复成功
- 恢复成功后清零失败计数器
- 任一环节失败都记录为恢复失败
### 4.2 密码管理机制
......@@ -105,40 +181,98 @@ docker exec umysql printenv MYSQL_ROOT_PASSWORD
### 4.3 日志与审计
#### 4.3.1 日志文件
- 主日志:`/var/log/scripts/monitor_mysql_service.log`
- 状态文件:`/var/log/scripts/monitor_mysql_service.state`
- PID锁文件:`/var/log/scripts/.mysql_monitor.pid`
#### 4.3.2 日志自动轮转机制
为避免日志文件无限增长占用磁盘空间,实现自动轮转机制:
1. **轮转触发条件**
- 当前日志文件大小达到 5MB
- 每次脚本执行时自动检查
2. **轮转执行动作**
- 将当前日志文件重命名为带时间戳的旧日志
- 创建新的空日志文件继续写入
- 记录轮转操作日志
3. **旧日志清理**
- 自动删除超过保留天数的旧日志文件(默认30天)
#### 4.3.3 并发控制机制(PID锁)
为防止定时任务重叠执行导致的问题,实现PID文件锁机制:
1. **锁文件检查**
- 脚本开始执行时检查PID文件是否存在
- 如果PID文件存在,读取其中的PID值
2. **进程存活判断**
- 使用 `kill -0 $PID` 检查该进程是否仍在运行
- 如果进程存在,记录警告日志并退出本次执行
- 如果进程不存在,清理旧PID文件并继续执行
3. **锁文件创建与释放**
- 将当前进程PID写入PID文件
- 使用 `trap` 命令设置退出时自动清理
- 捕获信号:EXIT INT TERM HUP
#### 4.3.2 日志级别和格式
每行必须包含:
- 时间戳:`YYYY-MM-DD HH:MM:SS`
- 级别标识:信息/警告/错误/成功
- 具体操作和结果状态
- **失败计数信息**(失败时显示当前失败次数)
示例:
```
2026-01-29 10:00:00 - 开始检查 MySQL 服务状态...
2026-01-29 10:00:00 - 读取失败计数器: 当前失败次数 = 0
2026-01-29 10:00:02 - MySQL 服务连接正常,可以执行SQL查询
2026-01-29 10:05:00 - 警告: 无法连接到 MySQL 服务
2026-01-29 10:05:05 - MySQL 未运行。正在尝试重启容器 'umysql'...
2026-01-29 10:06:05 - 成功: MySQL 容器已成功启动。
2026-01-29 10:07:05 - 信息: 等待 60 秒让 MySQL 服务完全启动............................................................
2026-01-29 10:08:05 - 信息: 等待 20 秒后检查服务状态....................
2026-01-29 10:08:25 - 信息: MySQL 服务状态正常。
2026-01-29 10:00:02 - 检查成功,重置失败计数器为 0
2026-01-29 10:05:00 - 开始检查 MySQL 服务状态...
2026-01-29 10:05:00 - 读取失败计数器: 当前失败次数 = 0
2026-01-29 10:05:02 - 警告: 无法连接到 MySQL 服务
2026-01-29 10:05:02 - 失败计数器更新: 0 -> 1 (阈值: 3)
2026-01-29 10:10:00 - 开始检查 MySQL 服务状态...
2026-01-29 10:10:00 - 读取失败计数器: 当前失败次数 = 1
2026-01-29 10:10:02 - 警告: 无法连接到 MySQL 服务
2026-01-29 10:10:02 - 失败计数器更新: 1 -> 2 (阈值: 3)
2026-01-29 10:15:00 - 开始检查 MySQL 服务状态...
2026-01-29 10:15:00 - 读取失败计数器: 当前失败次数 = 2
2026-01-29 10:15:02 - 警告: 无法连接到 MySQL 服务
2026-01-29 10:15:02 - 失败计数器更新: 2 -> 3 (阈值: 3)
2026-01-29 10:15:03 - 错误: 连续失败达到阈值(3次),触发容器重启
2026-01-29 10:15:03 - 重置失败计数器为 0
2026-01-29 10:15:05 - MySQL 未运行。正在尝试重启容器 'umysql'...
2026-01-29 10:16:05 - 成功: MySQL 容器已成功启动。
2026-01-29 10:17:05 - 信息: 等待 60 秒让 MySQL 服务完全启动............................................................
2026-01-29 10:18:05 - 信息: 等待 20 秒后检查服务状态....................
2026-01-29 10:18:25 - 信息: MySQL 服务状态正常,恢复成功
```
### 4.4 错误处理
#### 4.4.1 容器状态异常
#### 4.4.1 失败计数器异常
- 状态文件读取失败:记录警告,初始化计数器为0
- 状态文件写入失败:记录警告,但不影响重启决策
- 状态文件权限错误:记录错误并建议检查文件权限
- 计数器值异常(非数字):记录警告,重置为0
#### 4.4.2 容器状态异常
- 容器不存在:记录严重错误并建议手动创建
- 容器启动失败:记录错误详情和可能原因
- 容器启动后立即停止:记录错误并建议检查日志
- **容器停止时**:直接触发重启,不计入失败次数
#### 4.4.2 连接状态异常
- 密码获取失败:记录错误并退出检查
- 连接测试失败:记录具体的连接错误信息
- SQL执行失败:区分连接问题和权限问题
#### 4.4.3 连接状态异常
- 密码获取失败:记录错误并退出检查,失败计数器+1
- 连接测试失败:记录具体的连接错误信息,失败计数器+1
- SQL执行失败:区分连接问题和权限问题,失败计数器+1
#### 4.4.3 恢复过程异常
#### 4.4.4 恢复过程异常
- 重启后容器仍不运行:记录失败并建议手动干预
- 密码重新获取失败:记录错误并终止恢复
- 功能验证持续失败:记录详细诊断信息
- **恢复失败不增加计数器**:避免重复触发重启
---
......@@ -148,6 +282,11 @@ docker exec umysql printenv MYSQL_ROOT_PASSWORD
|--------|--------|------|
| CONTAINER_NAME | umysql | MySQL容器名称 |
| LOG_FILE | /var/log/scripts/monitor_mysql_service.log | 日志文件路径 |
| STATE_FILE | /var/log/scripts/monitor_mysql_service.state | 失败计数器状态文件 |
| PID_FILE | /var/log/scripts/.mysql_monitor.pid | 进程锁文件,防止并发执行 |
| MAX_LOG_SIZE | 5MB | 日志文件大小限制,超过后自动轮转 |
| LOG_RETENTION_DAYS | 30天 | 轮转后的日志文件保留天数 |
| MAX_FAILURES | 3 | 触发重启的最大连续失败次数 |
| STARTUP_WAIT | 60秒 | 容器启动等待时间 |
| STATUS_CHECK_WAIT | 20秒 | 状态验证等待时间 |
......@@ -179,17 +318,26 @@ docker exec umysql printenv MYSQL_ROOT_PASSWORD
- 准确获取和使用数据库密码
- 正确执行连接和功能测试
2. **恢复有效性**
- 异常检测后能正确触发重启
2. **失败计数机制**
- 单次失败时正确累加计数器
- 失败计数能持久化保存,进程重启不丢失
- 检查成功时正确重置计数器为0
- 未达阈值时不会触发重启
- 达到阈值时正确触发重启并清零计数器
- 容器停止时直接重启,不计入失败次数
3. **恢复有效性**
- 连续失败达到阈值后能正确触发重启
- 重启操作能成功执行
- 恢复后数据库功能确实正常
3. **日志完整性**
4. **日志完整性**
- 每次检查都有完整的开始和结束记录
- 日志中包含当前失败次数和阈值信息
- 异常情况有详细的诊断信息
- 恢复过程有清晰的步骤记录
4. **安全性**
5. **安全性**
- 密码处理安全,不在日志中明文显示
- 命令执行安全,防止注入攻击
- 权限控制适当,最小化操作权限
......@@ -211,23 +359,43 @@ docker exec umysql printenv MYSQL_ROOT_PASSWORD
```
解决:检查容器运行状态和环境变量配置
3. **连接测试失败**:
3. **连接测试失败(未达阈值)**:
```
警告: 无法连接到 MySQL 服务
失败计数器更新: 0 -> 1 (阈值: 3)
```
解决:查看容器日志`docker logs umysql`
说明:这是正常现象,系统在等待下次检查确认是否为持续故障
4. **SQL执行失败**:
4. **连接测试失败(达到阈值)**:
```
错误: 连续失败达到阈值(3次),触发容器重启
```
说明:已连续3次检查失败,系统正在执行自动恢复
5. **SQL执行失败**:
```
警告: MySQL 服务可以ping通,但无法执行SQL查询
失败计数器更新: 0 -> 1 (阈值: 3)
```
解决:检查数据库权限和状态
6. **状态文件异常**:
```
警告: 无法读取状态文件,初始化失败计数器为 0
```
说明:首次运行或状态文件被删除,属于正常情况
### 8.2 诊断命令
```bash
# 检查容器状态
docker ps | grep umysql
# 查看当前失败计数器
cat /var/log/scripts/monitor_mysql_service.state
# 手动重置失败计数器(恢复后无需手动操作,脚本会自动处理)
echo "0" > /var/log/scripts/monitor_mysql_service.state
# 手动获取密码
docker exec umysql printenv MYSQL_ROOT_PASSWORD
......@@ -252,11 +420,20 @@ tail -f /var/log/scripts/monitor_mysql_service.log
# 统计异常发生频率
grep "警告\|错误" /var/log/scripts/monitor_mysql_service.log | wc -l
# 查看失败计数变化趋势
grep "失败计数器更新" /var/log/scripts/monitor_mysql_service.log
# 统计触发重启的次数
grep "连续失败达到阈值" /var/log/scripts/monitor_mysql_service.log | wc -l
# 查看最近的恢复记录
grep "成功: MySQL 容器已成功启动" /var/log/scripts/monitor_mysql_service.log
# 分析连接问题
grep "无法连接到 MySQL" /var/log/scripts/monitor_mysql_service.log
# 查看失败次数达到阈值前的情况
grep -B 2 "连续失败达到阈值" /var/log/scripts/monitor_mysql_service.log
```
---
......
# _PRD_预定系统MySQL服务监控需求文档_计划执行.md
> 版本:V1.1
> 更新日期:2026-03-30
> 关联文档:_PRD_预定系统MySQL服务监控需求文档.md (V1.1)
> 关联脚本:monitor_mysql_service.sh (V1.0 → V1.1)
---
## 一、变更概述
### 1.1 变更背景
原脚本(V1.0)在检测到MySQL服务异常时直接触发容器重启,导致单次网络波动或临时堵塞时就会重启服务,影响业务连续性。
### 1.2 变更目标
引入**连续失败阈值判断机制**,避免因单次临时故障触发不必要的服务重启:
- 单次检查失败时仅记录失败次数,不立即重启
- 连续失败达到阈值(默认3次)时才触发容器重启
- 检查成功后自动重置失败计数器
- 状态持久化保存,进程重启不丢失
---
## 二、当前脚本分析
### 2.1 现有逻辑流程(V1.0)
```
开始 → 检查MySQL服务
检查成功 → 结束
检查失败 → 立即重启容器
验证重启结果 → 结束
```
### 2.2 存在的问题
1. **单次失败即重启**:临时网络波动也会触发重启
2. **无失败计数**:无法区分瞬时故障和持续故障
3. **无状态持久化**:脚本重启后丢失失败历史
4. **日志不完整**:缺少失败次数和阈值的日志信息
---
## 三、实现计划
### 3.1 新增配置参数
```bash
# 状态文件路径
STATE_FILE="/var/log/scripts/monitor_mysql_service.state"
# 最大失败次数阈值
MAX_FAILURES=3
# 状态文件格式
# FAILURE_COUNT=当前连续失败次数
# LAST_CHECK_TIME=上次检查时间戳
```
### 3.2 新增函数
#### 3.2.1 读取状态文件函数
```bash
# 函数名:read_state_file
# 功能:从状态文件读取失败计数器
# 返回:当前失败次数
read_state_file() {
if [ -f "$STATE_FILE" ]; then
source "$STATE_FILE"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
```
#### 3.2.2 写入状态文件函数
```bash
# 函数名:write_state_file
# 功能:将失败计数器写入状态文件
# 参数:$1 - 失败次数
write_state_file() {
local count=$1
cat > "$STATE_FILE" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
```
#### 3.2.3 日志函数增强
```bash
# 原log函数保持不变,增加失败计数日志
log_failure() {
local current=$1
local threshold=$2
log "失败计数器更新: $((current - 1)) -> $current (阈值: $threshold)"
}
```
### 3.3 主逻辑修改
#### 3.3.1 新的主流程
```
开始 → 读取失败计数器
检查MySQL服务
┌───┴───┐
↓ ↓
检查成功 检查失败
↓ ↓
清零计数器 计数器+1
↓ ↓
结束 写入状态文件
判断是否达阈值?
┌──────┴──────┐
↓ ↓
未达阈值 达到阈值
↓ ↓
结束 清零计数器
重启容器
验证结果
结束
```
#### 3.3.2 主逻辑伪代码
```bash
# 主逻辑
log "开始检查 MySQL 服务状态..."
# 读取当前失败次数
failure_count=$(read_state_file)
log "读取失败计数器: 当前失败次数 = $failure_count"
# 检查MySQL服务
if check_mysql; then
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_state_file 0
log "检查成功,重置失败计数器为 0"
fi
log "MySQL 服务状态正常"
else
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_state_file "$failure_count"
log_failure "$failure_count" "$MAX_FAILURES"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "错误: 连续失败达到阈值($MAX_FAILURES次),触发容器重启"
write_state_file 0
# 执行重启流程
restart_mysql_container
# 验证重启结果
if check_mysql; then
log "MySQL 服务已成功恢复"
else
log "错误: 重启后MySQL服务仍不可用"
fi
else
log "警告: 未达到重启阈值,等待下次检查确认"
fi
fi
```
### 3.4 日志格式变更
#### 3.4.1 新增日志内容
```
2026-03-30 10:00:00 - 开始检查 MySQL 服务状态...
2026-03-30 10:00:00 - 读取失败计数器: 当前失败次数 = 0
2026-03-30 10:00:02 - MySQL 服务连接正常,可以执行SQL查询
2026-03-30 10:00:02 - 检查成功,重置失败计数器为 0
2026-03-30 10:05:00 - 开始检查 MySQL 服务状态...
2026-03-30 10:05:00 - 读取失败计数器: 当前失败次数 = 0
2026-03-30 10:05:01 - 警告: 无法连接到 MySQL 服务
2026-03-30 10:05:01 - 失败计数器更新: 0 -> 1 (阈值: 3)
2026-03-30 10:05:01 - 警告: 未达到重启阈值,等待下次检查确认
2026-03-30 10:15:00 - 开始检查 MySQL 服务状态...
2026-03-30 10:15:00 - 读取失败计数器: 当前失败次数 = 2
2026-03-30 10:15:01 - 警告: 无法连接到 MySQL 服务
2026-03-30 10:15:01 - 失败计数器更新: 2 -> 3 (阈值: 3)
2026-03-30 10:15:02 - 错误: 连续失败达到阈值(3次),触发容器重启
2026-03-30 10:15:02 - 重置失败计数器为 0
2026-03-30 10:15:05 - MySQL 未运行。正在尝试重启容器 'umysql'...
```
---
## 四、代码修改清单
### 4.1 配置变量区域(约第33行后添加)
```bash
# 状态文件配置
STATE_FILE="/var/log/scripts/monitor_mysql_service.state"
MAX_FAILURES=3
```
### 4.2 函数区域(约第42行后添加)
```bash
# 读取状态文件
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
cat > "$STATE_FILE" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
```
### 4.3 主逻辑区域(约第138-153行,完全重写)
```bash
# 主逻辑(新版本)
log "开始检查 MySQL 服务状态..."
# 读取当前失败次数
failure_count=$(read_state_file)
log "读取失败计数器: 当前失败次数 = $failure_count"
# 检查MySQL服务
if check_mysql; then
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_state_file 0
log "检查成功,重置失败计数器为 0"
fi
log "MySQL 服务状态正常"
else
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_state_file "$failure_count"
log "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "错误: 连续失败达到阈值($MAX_FAILURES次),触发容器重启"
write_state_file 0
# 执行重启流程
restart_mysql_container
# 验证重启结果
if check_mysql; then
log "MySQL 服务已成功恢复"
else
log "错误: 重启后MySQL服务仍不可用"
fi
else
log "警告: 未达到重启阈值,等待下次检查确认"
fi
fi
```
### 4.4 版本信息更新(约第6行)
```bash
# 版本:V1.1
```
---
## 五、测试计划
### 5.1 单元测试
| 测试项 | 测试方法 | 预期结果 |
|--------|----------|----------|
| 状态文件读取 | 删除状态文件后执行脚本 | 自动初始化为0 |
| 状态文件写入 | 执行后检查文件内容 | 正确写入FAILURE_COUNT |
| 计数器累加 | 模拟服务失败 | 计数器正确+1 |
| 计数器清零 | 模拟服务恢复 | 计数器归0 |
### 5.2 集成测试
| 测试场景 | 操作步骤 | 预期结果 |
|----------|----------|----------|
| 单次失败 | 1. 停止MySQL<br>2. 执行脚本1次 | 计数器=1,不重启 |
| 连续失败 | 1. 停止MySQL<br>2. 执行脚本3次 | 第3次触发重启 |
| 恢复清零 | 1. 重启成功<br>2. 启动MySQL<br>3. 执行脚本 | 计数器归0 |
### 5.3 日志验证
检查日志中是否包含:
- `读取失败计数器: 当前失败次数 = X`
- `失败计数器更新: X -> Y (阈值: 3)`
- `错误: 连续失败达到阈值(3次),触发容器重启`
---
## 六、验收标准
### 6.1 功能验收
- [ ] 单次失败时计数器正确累加
- [ ] 达到阈值时正确触发重启
- [ ] 检查成功时计数器正确清零
- [ ] 状态文件持久化正常工作
### 6.2 日志验收
- [ ] 日志包含失败计数信息
- [ ] 日志包含阈值判断信息
- [ ] 日志格式符合需求文档
### 6.3 稳定性验收
- [ ] 状态文件异常时能正确处理
- [ ] 脚本重启后计数器状态保持
- [ ] 长期运行无内存泄漏
---
## 七、部署步骤
### 7.1 备份
```bash
cp /opt/scripts/monitor_mysql_service.sh /opt/scripts/monitor_mysql_service.sh.bak
```
### 7.2 部署
```bash
# 上传新脚本到服务器
scp monitor_mysql_service.sh user@server:/opt/scripts/
# 设置执行权限
chmod +x /opt/scripts/monitor_mysql_service.sh
# 创建状态文件目录
mkdir -p /var/log/scripts/
touch /var/log/scripts/monitor_mysql_service.state
```
### 7.3 验证
```bash
# 手动执行测试
/opt/scripts/monitor_mysql_service.sh
# 检查日志
tail -f /var/log/scripts/monitor_mysql_service.log
# 检查状态文件
cat /var/log/scripts/monitor_mysql_service.state
```
---
## 八、回滚方案
### 8.1 回滚步骤
```bash
# 停止定时任务
crontab -e # 注释掉相关行
# 恢复原脚本
cp /opt/scripts/monitor_mysql_service.sh.bak /opt/scripts/monitor_mysql_service.sh
# 删除状态文件
rm -f /var/log/scripts/monitor_mysql_service.state
# 重启定时任务
crontab -e # 取消注释
```
---
## 需求规范
- 代码规范:Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md
- 问题总结:Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md
- 方法总结:Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md
- 文档规范:Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md
- 测试规范:Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md
# _PRD_预定系统Redis服务监控需求文档.md
> 版本:V1.0
> 更新日期:2026-01-29
> 适用范围:预定系统Redis缓存服务监测与自愈
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_redis_service.sh`
> 版本:V1.1
> 更新日期:2026-03-30
> 适用范围:预定系统Redis缓存服务监测与自愈
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_redis_service.sh`
---
## 版本变更记录
| 版本 | 日期 | 变更内容 | 变更人 |
|------|------|----------|--------|
| V1.2 | 2026-03-30 | 新增日志自动轮转机制(5MB轮转、保留30天);新增PID文件锁机制防止并发执行 | Claude |
| V1.1 | 2026-03-30 | 新增连续失败阈值判断机制,避免单次临时堵塞触发重启影响业务 | Claude |
| V1.0 | 2026-01-29 | 初始版本,定义Redis服务监控基础需求 | - |
### V1.1 详细变更说明
#### 新增功能
1. **连续失败阈值机制**
- 新增失败计数器,单次失败时仅累加计数,不立即重启
- 默认阈值为3次,连续失败达到阈值才触发容器重启
- 检查成功后自动重置失败计数器
2. **状态持久化**
- 新增状态文件 `STATE_FILE`,持久化保存失败次数
- 进程重启后计数器状态不丢失
3. **容器停止特殊处理**
- 容器停止属于严重故障,直接触发重启,不计入失败次数
#### 配置参数变更
| 新增参数 | 默认值 | 说明 |
|----------|--------|------|
| STATE_FILE | /var/log/scripts/monitor_redis_service.state | 失败计数器状态文件 |
| MAX_FAILURES | 3 | 触发重启的最大连续失败次数 |
#### 日志格式变更
- 日志中新增失败计数信息展示:`失败计数器更新: X -> Y (阈值: Z)`
- 新增阈值达到提示:`错误: 连续失败达到阈值(N次),触发容器重启`
#### 诊断命令变更
- 新增状态文件查看命令
- 新增失败计数变化趋势分析命令
---
---
......@@ -18,6 +59,8 @@ Redis作为预定系统的重要缓存服务,提供高速数据访问和会话
- 智能认证模式识别(有密码/无密码)
- PING命令响应验证
- 异常时的自动重启恢复
- **连续失败阈值判断机制,避免单次临时堵塞触发重启**
- 失败次数记录与重置机制
- 数据目录清理重试机制
- 完整的操作日志记录
......@@ -39,6 +82,13 @@ Redis作为预定系统的重要缓存服务,提供高速数据访问和会话
- 持久化状态监控
- 集群模式监控
### 2.3 核心机制说明
**连续失败重启机制**:为避免因网络抖动、临时堵塞等瞬时故障导致的不必要重启,采用失败计数器机制:
- 单次检查失败时,仅记录失败次数,不立即重启
- 连续失败达到阈值(默认3次)时,才触发容器重启
- 检查成功后,自动重置失败计数器
- 通过状态文件持久化保存失败次数,避免计数器因进程重启丢失
---
## 3. 术语说明
......@@ -56,26 +106,51 @@ Redis作为预定系统的重要缓存服务,提供高速数据访问和会话
#### 4.1.1 状态检查机制
按以下逻辑进行健康验证:
1. **容器运行检查**
1. **读取失败计数器**
- 从状态文件读取当前连续失败次数
- 状态文件不存在时初始化为0
2. **容器运行检查**
- 使用`docker ps`检查uredis容器是否运行
- 容器未运行时直接进入重启流程
- 容器未运行时**直接进入重启流程**(容器停止属于严重故障)
2. **无密码连接测试**
3. **无密码连接测试**
- 首先尝试无密码PING命令:`redis-cli ping`
- 成功返回PONG表示服务正常
- 失败时分析错误信息
3. **认证状态识别**
4. **认证状态识别**
- 检查错误信息是否包含"NOAUTH"或"Authentication required"
- 确认为需要密码认证的情况
4. **密码认证测试**
5. **密码认证测试**
- 使用预设密码执行PING命令
- 验证认证成功后的PING响应
- 确认服务真正可用
#### 4.1.2 异常处理流程
当检测到异常时执行:
#### 4.1.2 失败处理机制(核心)
采用**连续失败阈值判断**机制,避免单次临时故障触发重启:
1. **单次失败处理**
- 任一检查环节失败时,失败计数器+1
- 将最新失败次数写入状态文件持久化保存
- 记录警告日志,说明当前失败次数和阈值
- **不触发重启,直接退出本次检查**
2. **连续失败达到阈值**
- 当失败次数 ≥ MAX_FAILURES(默认3次)时
- 清零失败计数器
- 触发容器重启流程
- 记录错误日志说明已连续失败N次
3. **检查成功处理**
- 所有检查项通过时
- 将失败计数器清零并更新状态文件
- 记录成功日志
- 正常退出本次检查
#### 4.1.3 异常处理流程
当检测到异常且达到失败阈值时执行:
1. **初次重启尝试**
- 停止可能挂起的容器实例
......@@ -97,6 +172,11 @@ Redis作为预定系统的重要缓存服务,提供高速数据访问和会话
- 再次尝试启动容器
- 重复状态验证流程
4. **恢复确认**
- 所有检查项通过才算恢复成功
- 恢复成功后清零失败计数器
- 恢复失败不增加计数器,避免重复触发重启
### 4.2 认证处理机制
#### 4.2.1 密码配置
```bash
......@@ -143,40 +223,98 @@ fi
### 4.4 日志与审计
#### 4.4.1 日志文件
- 主日志:`/var/log/scripts/monitor_redis_service.log`
- 状态文件:`/var/log/scripts/monitor_redis_service.state`
- PID锁文件:`/var/log/scripts/.redis_monitor.pid`
#### 4.4.2 日志自动轮转机制
为避免日志文件无限增长占用磁盘空间,实现自动轮转机制:
1. **轮转触发条件**
- 当前日志文件大小达到 5MB
- 每次脚本执行时自动检查
2. **轮转执行动作**
- 将当前日志文件重命名为带时间戳的旧日志
- 创建新的空日志文件继续写入
- 记录轮转操作日志
3. **旧日志清理**
- 自动删除超过保留天数的旧日志文件(默认30天)
#### 4.4.3 并发控制机制(PID锁)
为防止定时任务重叠执行导致的问题,实现PID文件锁机制:
1. **锁文件检查**
- 脚本开始执行时检查PID文件是否存在
- 如果PID文件存在,读取其中的PID值
2. **进程存活判断**
- 使用 `kill -0 $PID` 检查该进程是否仍在运行
- 如果进程存在,记录警告日志并退出本次执行
- 如果进程不存在,清理旧PID文件并继续执行
3. **锁文件创建与释放**
- 将当前进程PID写入PID文件
- 使用 `trap` 命令设置退出时自动清理
- 捕获信号:EXIT INT TERM HUP
#### 4.4.2 日志级别和格式
每行必须包含:
- 时间戳:`YYYY-MM-DD HH:MM:SS`
- 级别标识:信息/警告/错误/成功
- 具体操作和结果状态
- **失败计数信息**(失败时显示当前失败次数)
示例:
```
2026-01-29 10:00:00 - 开始检查 Redis 服务状态...
2026-01-29 10:00:00 - 读取失败计数器: 当前失败次数 = 0
2026-01-29 10:00:01 - 检测到需要认证的 Redis 服务,正在尝试使用密码...
2026-01-29 10:00:02 - Redis 服务状态正常(已通过密码认证)
2026-01-29 10:05:00 - 信息: uredis 容器未运行
2026-01-29 10:05:05 - Redis 未运行。正在尝试重启容器 'uredis'...
2026-01-29 10:05:25 - 成功: Redis 容器已成功启动。
2026-01-29 10:05:45 - 信息: 等待 10 秒后检查服务状态..........
2026-01-29 10:05:55 - 信息: Redis 服务状态正常(已通过密码认证)。
2026-01-29 10:00:02 - 检查成功,重置失败计数器为 0
2026-01-29 10:05:00 - 开始检查 Redis 服务状态...
2026-01-29 10:05:00 - 读取失败计数器: 当前失败次数 = 0
2026-01-29 10:05:01 - 警告: 无法连接到 Redis 服务
2026-01-29 10:05:01 - 失败计数器更新: 0 -> 1 (阈值: 3)
2026-01-29 10:10:00 - 开始检查 Redis 服务状态...
2026-01-29 10:10:00 - 读取失败计数器: 当前失败次数 = 1
2026-01-29 10:10:01 - 警告: 无法连接到 Redis 服务
2026-01-29 10:10:01 - 失败计数器更新: 1 -> 2 (阈值: 3)
2026-01-29 10:15:00 - 开始检查 Redis 服务状态...
2026-01-29 10:15:00 - 读取失败计数器: 当前失败次数 = 2
2026-01-29 10:15:01 - 警告: 无法连接到 Redis 服务
2026-01-29 10:15:01 - 失败计数器更新: 2 -> 3 (阈值: 3)
2026-01-29 10:15:02 - 错误: 连续失败达到阈值(3次),触发容器重启
2026-01-29 10:15:02 - 重置失败计数器为 0
2026-01-29 10:15:05 - Redis 未运行。正在尝试重启容器 'uredis'...
2026-01-29 10:15:25 - 成功: Redis 容器已成功启动。
2026-01-29 10:15:45 - 信息: 等待 10 秒后检查服务状态..........
2026-01-29 10:15:55 - 信息: Redis 服务状态正常(已通过密码认证),恢复成功
```
### 4.5 错误处理
#### 4.5.1 容器状态异常
#### 4.5.1 失败计数器异常
- 状态文件读取失败:记录警告,初始化计数器为0
- 状态文件写入失败:记录警告,但不影响重启决策
- 状态文件权限错误:记录错误并建议检查文件权限
- 计数器值异常(非数字):记录警告,重置为0
#### 4.5.2 容器状态异常
- 容器不存在:记录严重错误并建议手动创建
- 容器启动失败:记录错误详情和可能原因
- 容器启动后立即停止:记录错误并建议检查日志
- **容器停止时**:直接触发重启,不计入失败次数
#### 4.5.2 认证状态异常
- 无密码连接失败但不是认证问题:记录具体错误
- 密码认证失败:记录认证错误信息
- PING响应异常:记录实际响应内容
#### 4.5.3 认证状态异常
- 无密码连接失败但不是认证问题:记录具体错误,失败计数器+1
- 密码认证失败:记录认证错误信息,失败计数器+1
- PING响应异常:记录实际响应内容,失败计数器+1
#### 4.5.3 恢复过程异常
#### 4.5.4 恢复过程异常
- 重启后容器仍不运行:记录失败并建议手动干预
- 数据清理后仍无法启动:记录严重错误
- 多次重试均失败:建议深入排查根本原因
- **恢复失败不增加计数器**:避免重复触发重启
---
......@@ -187,6 +325,11 @@ fi
| CONTAINER_NAME | uredis | Redis容器名称 |
| REDIS_PASSWORD | dNrprU&2S | Redis访问密码 |
| LOG_FILE | /var/log/scripts/monitor_redis_service.log | 日志文件路径 |
| STATE_FILE | /var/log/scripts/monitor_redis_service.state | 失败计数器状态文件 |
| PID_FILE | /var/log/scripts/.redis_monitor.pid | 进程锁文件,防止并发执行 |
| MAX_LOG_SIZE | 5MB | 日志文件大小限制,超过后自动轮转 |
| LOG_RETENTION_DAYS | 30天 | 轮转后的日志文件保留天数 |
| MAX_FAILURES | 3 | 触发重启的最大连续失败次数 |
| STARTUP_WAIT | 20秒 | 容器启动等待时间 |
| STATUS_CHECK_WAIT | 10秒 | 状态验证等待时间 |
......@@ -218,19 +361,28 @@ fi
- 准确执行对应的连接测试
- 智能选择合适的恢复策略
2. **恢复有效性**
- 异常检测后能正确触发重启
2. **失败计数机制**
- 单次失败时正确累加计数器
- 失败计数能持久化保存,进程重启不丢失
- 检查成功时正确重置计数器为0
- 未达阈值时不会触发重启
- 达到阈值时正确触发重启并清零计数器
- 容器停止时直接重启,不计入失败次数
3. **恢复有效性**
- 连续失败达到阈值后能正确触发重启
- 重启操作能成功执行
- 数据清理机制能解决启动问题
- 恢复后服务状态确实正常
3. **日志完整性**
4. **日志完整性**
- 每次检查都有完整的开始和结束记录
- 日志中包含当前失败次数和阈值信息
- 认证模式识别过程详细记录
- 异常情况有详细的诊断信息
- 数据清理操作明确记录
4. **安全性**
5. **安全性**
- 密码在脚本中合理配置
- 数据清理操作安全可控
- 不会对正常运行的服务产生副作用
......@@ -249,26 +401,52 @@ fi
2. **认证模式识别错误**:
```
警告: 需要认证,但无法使用预设密码连接到 Redis
失败计数器更新: 0 -> 1 (阈值: 3)
```
解决:检查Redis密码配置是否正确
3. **数据清理触发**:
3. **连接测试失败(未达阈值)**:
```
警告: 无法连接到 Redis 服务
失败计数器更新: 0 -> 1 (阈值: 3)
```
说明:这是正常现象,系统在等待下次检查确认是否为持续故障
4. **连接测试失败(达到阈值)**:
```
错误: 连续失败达到阈值(3次),触发容器重启
```
说明:已连续3次检查失败,系统正在执行自动恢复
5. **数据清理触发**:
```
信息: 尝试清理数据目录并重新启动 Redis 容器...
```
解决:这表示常规重启失败,脚本自动尝试清理数据
4. **持续启动失败**:
6. **持续启动失败**:
```
错误: 即使清理了数据目录,仍然无法启动 Redis 容器。
```
解决:需要手动检查容器配置和系统资源
7. **状态文件异常**:
```
警告: 无法读取状态文件,初始化失败计数器为 0
```
说明:首次运行或状态文件被删除,属于正常情况
### 8.2 诊断命令
```bash
# 检查容器状态
docker ps | grep uredis
# 查看当前失败计数器
cat /var/log/scripts/monitor_redis_service.state
# 手动重置失败计数器(恢复后无需手动操作,脚本会自动处理)
echo "0" > /var/log/scripts/monitor_redis_service.state
# 手动无密码测试
docker exec uredis redis-cli ping
......@@ -291,6 +469,15 @@ docker restart uredis
# 实时监控脚本执行
tail -f /var/log/scripts/monitor_redis_service.log
# 统计异常发生频率
grep "警告\|错误" /var/log/scripts/monitor_redis_service.log | wc -l
# 查看失败计数变化趋势
grep "失败计数器更新" /var/log/scripts/monitor_redis_service.log
# 统计触发重启的次数
grep "连续失败达到阈值" /var/log/scripts/monitor_redis_service.log | wc -l
# 统计认证模式使用情况
grep "检测到需要认证" /var/log/scripts/monitor_redis_service.log | wc -l
grep "无密码情况下能 ping 通" /var/log/scripts/monitor_redis_service.log | wc -l
......@@ -301,6 +488,12 @@ grep "清理.*data 目录" /var/log/scripts/monitor_redis_service.log
# 分析重启成功率
grep "Redis 容器已成功启动" /var/log/scripts/monitor_redis_service.log | wc -l
grep "仍然无法启动 Redis 容器" /var/log/scripts/monitor_redis_service.log | wc -l
# 分析连接问题
grep "无法连接到 Redis" /var/log/scripts/monitor_redis_service.log
# 查看失败次数达到阈值前的情况
grep -B 2 "连续失败达到阈值" /var/log/scripts/monitor_redis_service.log
```
---
......
# _PRD_预定系统Redis服务监控需求文档_计划执行.md
> 版本:V1.1
> 更新日期:2026-03-30
> 关联文档:_PRD_预定系统Redis服务监控需求文档.md (V1.1)
> 关联脚本:monitor_redis_service.sh (V1.0 → V1.1)
---
## 一、变更概述
### 1.1 变更背景
原脚本(V1.0)在检测到Redis服务异常时直接触发容器重启,导致单次网络波动或临时堵塞时就会重启服务,影响业务连续性。
### 1.2 变更目标
引入**连续失败阈值判断机制**,避免因单次临时故障触发不必要的服务重启:
- 单次检查失败时仅记录失败次数,不立即重启
- 连续失败达到阈值(默认3次)时才触发容器重启
- 检查成功后自动重置失败计数器
- 状态持久化保存,进程重启不丢失
---
## 二、当前脚本分析
### 2.1 现有逻辑流程(V1.0)
```
开始 → 检查Redis服务
检查成功 → 结束
检查失败 → 立即重启容器
重启失败 → 清理数据目录重试
验证重启结果 → 结束
```
### 2.2 存在的问题
1. **单次失败即重启**:临时网络波动也会触发重启
2. **无失败计数**:无法区分瞬时故障和持续故障
3. **无状态持久化**:脚本重启后丢失失败历史
4. **日志不完整**:缺少失败次数和阈值的日志信息
---
## 三、实现计划
### 3.1 新增配置参数
```bash
# 状态文件路径
STATE_FILE="/var/log/scripts/monitor_redis_service.state"
# 最大失败次数阈值
MAX_FAILURES=3
```
### 3.2 新增函数
#### 3.2.1 读取状态文件函数
```bash
read_state_file() {
if [ -f "$STATE_FILE" ]; then
source "$STATE_FILE"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
```
#### 3.2.2 写入状态文件函数
```bash
write_state_file() {
local count=$1
cat > "$STATE_FILE" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
```
### 3.3 主逻辑修改
#### 3.3.1 新的主流程
```
开始 → 读取失败计数器
检查Redis服务
┌───┴───┐
↓ ↓
检查成功 检查失败
↓ ↓
清零计数器 计数器+1
↓ ↓
结束 写入状态文件
判断是否达阈值?
┌──────┴──────┐
↓ ↓
未达阈值 达到阈值
↓ ↓
结束 清零计数器
重启容器
重启失败? → 清理数据重试
验证结果
结束
```
#### 3.3.2 主逻辑伪代码
```bash
# 主逻辑
log "开始检查 Redis 服务状态..."
# 读取当前失败次数
failure_count=$(read_state_file)
log "读取失败计数器: 当前失败次数 = $failure_count"
# 检查Redis服务
if check_redis; then
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_state_file 0
log "检查成功,重置失败计数器为 0"
fi
log "Redis 服务状态正常"
else
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_state_file "$failure_count"
log "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "错误: 连续失败达到阈值($MAX_FAILURES次),触发容器重启"
write_state_file 0
# 执行重启流程
restart_redis_container
# 验证重启结果
if check_redis; then
log "Redis 服务已成功恢复"
else
log "错误: 重启后Redis服务仍不可用"
fi
else
log "警告: 未达到重启阈值,等待下次检查确认"
fi
fi
```
### 3.4 日志格式变更
```
2026-03-30 10:00:00 - 开始检查 Redis 服务状态...
2026-03-30 10:00:00 - 读取失败计数器: 当前失败次数 = 0
2026-03-30 10:00:01 - Redis 服务状态正常(已通过密码认证)
2026-03-30 10:00:01 - 检查成功,重置失败计数器为 0
2026-03-30 10:05:00 - 开始检查 Redis 服务状态...
2026-03-30 10:05:00 - 读取失败计数器: 当前失败次数 = 0
2026-03-30 10:05:01 - 警告: 无法连接到 Redis 服务
2026-03-30 10:05:01 - 失败计数器更新: 0 -> 1 (阈值: 3)
2026-03-30 10:05:01 - 警告: 未达到重启阈值,等待下次检查确认
2026-03-30 10:15:01 - 警告: 无法连接到 Redis 服务
2026-03-30 10:15:01 - 失败计数器更新: 2 -> 3 (阈值: 3)
2026-03-30 10:15:02 - 错误: 连续失败达到阈值(3次),触发容器重启
2026-03-30 10:15:02 - 重置失败计数器为 0
```
---
## 四、代码修改清单
### 4.1 配置变量区域(约第33行后添加)
```bash
# 状态文件配置
STATE_FILE="/var/log/scripts/monitor_redis_service.state"
MAX_FAILURES=3
```
### 4.2 函数区域(约第42行后添加)
```bash
# 读取状态文件
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
cat > "$STATE_FILE" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
```
### 4.3 主逻辑区域(约第250-265行,完全重写)
```bash
# 主逻辑(新版本)
log "开始检查 Redis 服务状态..."
# 读取当前失败次数
failure_count=$(read_state_file)
log "读取失败计数器: 当前失败次数 = $failure_count"
# 检查Redis服务
if check_redis; then
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_state_file 0
log "检查成功,重置失败计数器为 0"
fi
log "Redis 服务状态正常"
else
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_state_file "$failure_count"
log "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "错误: 连续失败达到阈值($MAX_FAILURES次),触发容器重启"
write_state_file 0
# 执行重启流程
restart_redis_container
# 验证重启结果
if check_redis; then
log "Redis 服务已成功恢复"
else
log "错误: 重启后Redis服务仍不可用"
fi
else
log "警告: 未达到重启阈值,等待下次检查确认"
fi
fi
```
### 4.4 版本信息更新(约第6行)
```bash
# 版本:V1.1
```
---
## 五、测试计划
### 5.1 单元测试
| 测试项 | 测试方法 | 预期结果 |
|--------|----------|----------|
| 状态文件读取 | 删除状态文件后执行脚本 | 自动初始化为0 |
| 状态文件写入 | 执行后检查文件内容 | 正确写入FAILURE_COUNT |
| 计数器累加 | 模拟服务失败 | 计数器正确+1 |
| 计数器清零 | 模拟服务恢复 | 计数器归0 |
### 5.2 集成测试
| 测试场景 | 操作步骤 | 预期结果 |
|----------|----------|----------|
| 单次失败 | 1. 停止Redis<br>2. 执行脚本1次 | 计数器=1,不重启 |
| 连续失败 | 1. 停止Redis<br>2. 执行脚本3次 | 第3次触发重启 |
| 恢复清零 | 1. 重启成功<br>2. 启动Redis<br>3. 执行脚本 | 计数器归0 |
---
## 六、部署步骤
### 6.1 备份
```bash
cp /opt/scripts/monitor_redis_service.sh /opt/scripts/monitor_redis_service.sh.bak
```
### 6.2 部署
```bash
# 上传新脚本
scp monitor_redis_service.sh user@server:/opt/scripts/
# 设置执行权限
chmod +x /opt/scripts/monitor_redis_service.sh
# 创建状态文件
touch /var/log/scripts/monitor_redis_service.state
```
### 6.3 验证
```bash
# 手动执行测试
/opt/scripts/monitor_redis_service.sh
# 检查日志
tail -f /var/log/scripts/monitor_redis_service.log
# 检查状态文件
cat /var/log/scripts/monitor_redis_service.state
```
---
## 七、回滚方案
### 7.1 回滚步骤
```bash
# 停止定时任务
crontab -e # 注释掉相关行
# 恢复原脚本
cp /opt/scripts/monitor_redis_service.sh.bak /opt/scripts/monitor_redis_service.sh
# 删除状态文件
rm -f /var/log/scripts/monitor_redis_service.state
# 重启定时任务
crontab -e # 取消注释
```
---
## 需求规范
- 代码规范:Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md
- 问题总结:Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md
- 方法总结:Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md
- 文档规范:Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md
- 测试规范:Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md
# _PRD_预定系统后端服务监测需求文档_计划执行.md
> 版本:V1.1
> 更新日期:2026-03-30
> 关联文档:_PRD_预定系统后端服务监测需求文档.md (V1.1)
> 关联脚本:monitor_inner_api_services.sh (V1.0 → V1.1)
---
## 一、变更概述
### 1.1 变更背景
原脚本(V1.0)在检测到服务异常时直接触发恢复操作,导致单次网络波动或临时堵塞时就会重启/启动服务,影响业务连续性。
### 1.2 变更目标
引入**连续失败阈值判断机制**,避免因单次临时故障触发不必要的恢复操作:
- 单次检查失败时仅记录失败次数,不立即恢复
- 连续失败达到阈值(默认3次)时才触发恢复操作
- 检查成功后自动重置失败计数器
- 每个监测对象(平台API、meeting2.0、malan)独立维护失败计数器
---
## 二、当前脚本分析
### 2.1 现有逻辑流程(V1.0)
```
开始 → 检查平台API
API失败 → 强制重启内部服务
检查容器内服务
┌───┴───┐
↓ ↓
检查成功 检查失败
↓ ↓
跳过 立即启动服务
检查6060端口
┌───┴───┐
↓ ↓
监听正常 未监听
↓ ↓
结束 启动malan
结束
```
### 2.2 监测对象
| 对象 | 检测方法 | 恢复方式 |
|------|----------|----------|
| 平台API | curl https://IP/api/system/getVerifyCode | 强制重启内部服务 |
| meeting2.0 | docker exec ps aux \| grep jar | 启动服务进程 |
| malan | netstat -tuln \| grep :6060 | 启动malan服务 |
### 2.3 存在的问题
1. **单次失败即恢复**:临时网络波动也会触发恢复
2. **无失败计数**:无法区分瞬时故障和持续故障
3. **无状态持久化**:脚本重启后丢失失败历史
4. **日志不完整**:缺少失败次数和阈值的日志信息
---
## 三、实现计划
### 3.1 新增配置参数
```bash
# 状态文件目录(每个监测对象独立文件)
STATE_DIR="/var/log/scripts/inner_api_state"
# 最大失败次数阈值
MAX_FAILURES=3
```
### 3.2 新增函数
#### 3.2.1 获取对象状态文件路径
```bash
get_object_state_file() {
local object_key="$1"
# object_key: platform_api, meeting2.0, malan
local safe_name=$(echo "$object_key" | tr -cd '[:alnum:]_-')
echo "${STATE_DIR}/${safe_name}.state"
}
```
#### 3.2.2 读取对象状态文件
```bash
read_object_state() {
local object_key="$1"
local state_file
state_file=$(get_object_state_file "$object_key")
if [ -f "$state_file" ]; then
source "$state_file"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
```
#### 3.2.3 写入对象状态文件
```bash
write_object_state() {
local object_key="$1"
local count="$2"
local state_file
state_file=$(get_object_state_file "$object_key")
# 确保状态目录存在
mkdir -p "$STATE_DIR"
cat > "$state_file" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
```
### 3.3 监测逻辑修改
#### 3.3.1 平台API检查
```bash
# 原逻辑:API失败 → 直接重启内部服务
# 新逻辑:API失败 → 累加计数 → 达阈值才重启
check_platform_api() {
local http_code=$(curl -k -s -o /dev/null -w "%{http_code}" \
--max-time 10 "$API_URL")
if [ "$http_code" -eq 200 ]; then
response=$(curl -k -s --max-time 10 "$API_URL")
if [[ "$response" == *"操作成功"* ]] && [[ "$response" == *"\"code\":200"* ]]; then
echo "success"
return 0
fi
fi
echo "failed"
return 1
}
# 主流程中的API检查
failure_count=$(read_object_state "platform_api")
log "[INFO] 读取失败计数器[platform_api]: 当前失败次数 = $failure_count"
if check_platform_api >/dev/null 2>&1; then
if [ "$failure_count" -gt 0 ]; then
write_object_state "platform_api" 0
log "[SUCCESS] 平台API检查通过,重置失败计数器为 0"
fi
else
failure_count=$((failure_count + 1))
write_object_state "platform_api" "$failure_count"
log "[WARNING] 平台API检查失败"
log "[WARNING] 失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "[ERROR] 连续失败达到阈值($MAX_FAILURES次),触发内部服务重启"
write_object_state "platform_api" 0
restart_critical_services
fi
fi
```
#### 3.3.2 容器内服务检查
```bash
# 为每个服务独立维护失败计数器
for service_key in "${!SERVICES[@]}"; do
failure_count=$(read_object_state "$service_key")
log "[INFO] 读取失败计数器[$service_key]: 当前失败次数 = $failure_count"
if check_service_in_container "$service_key"; then
if [ "$failure_count" -gt 0 ]; then
write_object_state "$service_key" 0
log "[SUCCESS] 服务 '$service_key' 正常,重置失败计数器为 0"
fi
else
failure_count=$((failure_count + 1))
write_object_state "$service_key" "$failure_count"
log "[WARNING] 服务 '$service_key' 未运行"
log "[WARNING] 失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "[ERROR] 连续失败达到阈值($MAX_FAILURES次),触发服务启动"
write_object_state "$service_key" 0
start_service_in_container "$service_key"
fi
fi
done
```
#### 3.3.3 Malan服务检查
```bash
failure_count=$(read_object_state "malan")
log "[INFO] 读取失败计数器[malan]: 当前失败次数 = $failure_count"
if check_malan_port; then
if [ "$failure_count" -gt 0 ]; then
write_object_state "malan" 0
log "[SUCCESS] 6060端口已监听,重置失败计数器为 0"
fi
else
failure_count=$((failure_count + 1))
write_object_state "malan" "$failure_count"
log "[WARNING] 6060端口未监听"
log "[WARNING] 失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "[ERROR] 连续失败达到阈值($MAX_FAILURES次),触发malan服务启动"
write_object_state "malan" 0
start_malan_service
fi
fi
```
### 3.4 日志格式变更
```
2026-03-30 10:00:00 [START] ===== 后端服务监测开始 =====
2026-03-30 10:00:00 [INFO] 读取失败计数器[platform_api]: 当前失败次数 = 0
2026-03-30 10:00:02 [SUCCESS] 平台API检查通过,重置失败计数器为 0
2026-03-30 10:00:03 [INFO] 读取失败计数器[meeting2.0]: 当前失败次数 = 0
2026-03-30 10:00:04 [RUNNING] meeting2.0 (PID: 1234),重置失败计数器为 0
2026-03-30 10:00:05 [INFO] 读取失败计数器[malan]: 当前失败次数 = 0
2026-03-30 10:00:06 [SUCCESS] 6060端口已监听,重置失败计数器为 0
2026-03-30 10:05:00 [START] ===== 后端服务监测开始 =====
2026-03-30 10:05:00 [INFO] 读取失败计数器[meeting2.0]: 当前失败次数 = 0
2026-03-30 10:05:01 [WARNING] 服务 'meeting2.0' 未运行
2026-03-30 10:05:01 [WARNING] 失败计数器更新: 0 -> 1 (阈值: 3)
2026-03-30 10:15:00 [START] ===== 后端服务监测开始 =====
2026-03-30 10:15:00 [INFO] 读取失败计数器[meeting2.0]: 当前失败次数 = 2
2026-03-30 10:15:01 [WARNING] 服务 'meeting2.0' 未运行
2026-03-30 10:15:01 [WARNING] 失败计数器更新: 2 -> 3 (阈值: 3)
2026-03-30 10:15:02 [ERROR] 连续失败达到阈值(3次),触发服务启动
2026-03-30 10:15:02 [INFO] 重置失败计数器为 0
2026-03-30 10:15:03 [START] 正在启动服务 meeting2.0...
```
---
## 四、代码修改清单
### 4.1 配置变量区域(约第41行后添加)
```bash
# 状态文件配置
STATE_DIR="/var/log/scripts/inner_api_state"
MAX_FAILURES=3
```
### 4.2 函数区域(约第150行后添加)
```bash
# 获取对象状态文件路径
get_object_state_file() {
local object_key="$1"
local safe_name=$(echo "$object_key" | tr -cd '[:alnum:]_-')
echo "${STATE_DIR}/${safe_name}.state"
}
# 读取对象状态文件
read_object_state() {
local object_key="$1"
local state_file
state_file=$(get_object_state_file "$object_key")
if [ -f "$state_file" ]; then
source "$state_file"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
# 写入对象状态文件
write_object_state() {
local object_key="$1"
local count="$2"
local state_file
state_file=$(get_object_state_file "$object_key")
mkdir -p "$STATE_DIR"
cat > "$state_file" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
```
### 4.3 主函数区域修改
需要修改main()函数中的三个检查流程:
1. 平台API检查流程
2. 容器内服务检查流程
3. Malan服务检查流程
每个流程都需要:
- 在检查前读取失败计数器
- 根据检查结果更新计数器
- 达到阈值时才执行恢复操作
### 4.4 版本信息更新(约第6行)
```bash
# 版本:V1.1
```
---
## 五、测试计划
### 5.1 单元测试
| 测试项 | 测试方法 | 预期结果 |
|--------|----------|----------|
| 状态文件路径生成 | 传入不同对象key | 生成正确的状态文件路径 |
| 状态文件读取 | 删除状态文件后读取 | 自动初始化为0 |
| 状态文件写入 | 写入后检查文件 | 正确写入FAILURE_COUNT |
| 计数器独立性 | 多个对象各自失败 | 互不影响 |
### 5.2 集成测试
| 测试场景 | 操作步骤 | 预期结果 |
|----------|----------|----------|
| 平台API失败 | 1. 停止后端服务<br>2. 执行脚本3次 | 第3次触发重启 |
| 容器内服务失败 | 1. 停止meeting2.0<br>2. 执行脚本3次 | 第3次触发启动 |
| Malan服务失败 | 1. 停止malan<br>2. 执行脚本3次 | 第3次触发启动 |
---
## 六、部署步骤
### 6.1 备份
```bash
cp /opt/scripts/monitor_inner_api_services.sh /opt/scripts/monitor_inner_api_services.sh.bak
```
### 6.2 部署
```bash
# 上传新脚本
scp monitor_inner_api_services.sh user@server:/opt/scripts/
# 设置执行权限
chmod +x /opt/scripts/monitor_inner_api_services.sh
# 创建状态文件目录
mkdir -p /var/log/scripts/inner_api_state
```
### 6.3 验证
```bash
# 手动执行测试
/opt/scripts/monitor_inner_api_services.sh
# 检查日志
tail -f /var/log/scripts/monitor-inner-api-services.log
# 检查状态文件目录
ls -la /var/log/scripts/inner_api_state/
```
---
## 七、回滚方案
### 7.1 回滚步骤
```bash
# 停止定时任务
crontab -e # 注释掉相关行
# 恢复原脚本
cp /opt/scripts/monitor_inner_api_services.sh.bak /opt/scripts/monitor_inner_api_services.sh
# 删除状态文件目录
rm -rf /var/log/scripts/inner_api_state
# 重启定时任务
crontab -e # 取消注释
```
---
## 需求规范
- 代码规范:Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md
- 问题总结:Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md
- 方法总结:Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md
- 文档规范:Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md
- 测试规范:Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md
# _PRD_预定系统外部API服务监控需求文档.md
> 版本:V1.0
> 更新日期:2026-01-29
> 适用范围:预定系统外部API服务进程监测与自愈
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_external_api_services.sh`
> 版本:V1.1
> 更新日期:2026-03-30
> 适用范围:预定系统外部API服务进程监测与自愈
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_external_api_services.sh`
---
## 版本变更记录
| 版本 | 日期 | 变更内容 | 变更人 |
|------|------|----------|--------|
| V1.2 | 2026-03-30 | 新增日志自动轮转机制(5MB轮转、保留30天);新增PID文件锁机制防止并发执行 | Claude |
| V1.1 | 2026-03-30 | 新增连续失败阈值判断机制,避免单次临时堵塞触发重启影响业务 | Claude |
| V1.0 | 2026-01-29 | 初始版本,定义外部API服务监控基础需求 | - |
### V1.1 详细变更说明
#### 新增功能
1. **连续失败阈值机制**
- 新增失败计数器,单次服务停止时仅累加计数,不立即启动
- 默认阈值为3次,连续失败达到阈值才触发服务启动
- 服务正常运行后自动重置失败计数器
2. **状态持久化**
- 新增状态文件 `STATE_DIR`,为每个服务独立保存失败次数
- 进程重启后计数器状态不丢失
3. **服务停止特殊处理**
- 服务进程停止属于严重故障,直接触发启动,不计入失败次数
#### 配置参数变更
| 新增参数 | 默认值 | 说明 |
|----------|--------|------|
| STATE_DIR | /var/log/scripts/external_api_state | 失败计数器状态文件目录 |
| MAX_FAILURES | 3 | 触发启动的最大连续失败次数 |
#### 日志格式变更
- 日志中新增失败计数信息展示:`失败计数器更新: X -> Y (阈值: Z)`
- 新增阈值达到提示:`错误: 连续失败达到阈值(N次),触发服务启动`
#### 诊断命令变更
- 新增状态文件目录查看命令
- 新增失败计数变化趋势分析命令
---
---
......@@ -18,6 +59,8 @@
- 进程存在性检测
- 服务目录和启动脚本验证
- 异常时的自动启动恢复
- **连续失败阈值判断机制,避免单次临时堵塞触发启动影响业务**
- 失败次数记录与重置机制
- 完整的操作日志记录
---
......@@ -42,6 +85,13 @@
- 服务间依赖关系监控
- 负载均衡状态监控
### 2.3 核心机制说明
**连续失败启动机制**:为避免因进程检测误差、瞬时卡顿等临时故障导致的不必要服务启动,采用失败计数器机制:
- 单次检查服务停止时,仅记录失败次数,不立即启动
- 连续失败达到阈值(默认3次)时,才触发服务启动
- 服务正常运行后,自动重置失败计数器
- 为每个服务独立维护失败计数器,通过状态文件持久化保存,避免计数器因进程重启丢失
---
## 3. 术语说明
......@@ -68,21 +118,46 @@ SERVICES=(
#### 4.1.2 批量服务检查
对每个配置的服务执行:
1. **目录存在性验证**
1. **读取失败计数器**
- 从状态文件读取当前连续失败次数(按服务名称独立维护)
- 状态文件不存在时初始化为0
2. **目录存在性验证**
- 检查服务目录是否存在
- 目录不存在时跳过该服务监控
2. **进程运行状态检查**
3. **进程运行状态检查**
- 使用`pgrep -f`搜索进程标识
- 对于.jar文件搜索`java.*jar_name`模式
- 对于普通进程直接搜索进程名
3. **服务状态判定**
- 进程存在:标记为RUNNING状态
- 进程不存在:标记为STOPPED状态并尝试启动
4. **服务状态判定**
- 进程存在:标记为RUNNING状态,清零失败计数器
- 进程不存在:进入失败处理流程
#### 4.1.3 失败处理机制(核心)
采用**连续失败阈值判断**机制,避免单次临时故障触发服务启动:
1. **单次失败处理**
- 服务进程未运行时,失败计数器+1
- 将最新失败次数写入状态文件持久化保存
- 记录警告日志,说明当前失败次数和阈值
- **不触发启动,直接处理下一个服务**
2. **连续失败达到阈值**
- 当失败次数 ≥ MAX_FAILURES(默认3次)时
- 清零失败计数器
- 触发服务启动流程
- 记录错误日志说明已连续失败N次
#### 4.1.3 服务启动恢复
当服务处于STOPPED状态时执行:
3. **服务正常运行处理**
- 进程检测存在时
- 将失败计数器清零并更新状态文件
- 记录成功日志
- 继续处理下一个服务
#### 4.1.4 服务启动恢复
当服务连续失败达到阈值时执行:
1. **环境准备**
- 切换到服务目录(使用子shell)
......@@ -94,44 +169,103 @@ SERVICES=(
3. **启动验证**
- 再次检查进程是否运行
- 启动成功:记录SUCCESS状态
- 启动失败:记录ERROR状态
- 启动成功:记录SUCCESS状态,失败计数器保持为0
- 启动失败:记录ERROR状态,不增加计数器避免重复触发启动
### 4.2 日志与审计
#### 4.2.1 日志文件
- 主日志:`/var/log/scripts/monitor_external_api_services.log`
- 状态文件目录:`/var/log/scripts/external_api_state`
- PID锁文件:`/var/log/scripts/.external_api_monitor.pid`
#### 4.2.2 日志自动轮转机制
为避免日志文件无限增长占用磁盘空间,实现自动轮转机制:
1. **轮转触发条件**
- 当前日志文件大小达到 5MB
- 每次脚本执行时自动检查
2. **轮转执行动作**
- 将当前日志文件重命名为带时间戳的旧日志
- 创建新的空日志文件继续写入
- 记录轮转操作日志
3. **旧日志清理**
- 自动删除超过保留天数的旧日志文件(默认30天)
#### 4.2.3 并发控制机制(PID锁)
为防止定时任务重叠执行导致的问题,实现PID文件锁机制:
1. **锁文件检查**
- 脚本开始执行时检查PID文件是否存在
- 如果PID文件存在,读取其中的PID值
2. **进程存活判断**
- 使用 `kill -0 $PID` 检查该进程是否仍在运行
- 如果进程存在,记录警告日志并退出本次执行
- 如果进程不存在,清理旧PID文件并继续执行
3. **锁文件创建与释放**
- 将当前进程PID写入PID文件
- 使用 `trap` 命令设置退出时自动清理
- 捕获信号:EXIT INT TERM HUP
#### 4.2.2 日志内容格式
每行必须包含:
- 时间戳:`YYYY-MM-DD HH:MM:SS`
- 操作类型:开始/检查/启动/成功/错误
- 服务名称和操作结果
- **失败计数信息**(失败时显示当前失败次数)
示例:
```
2026-01-29 10:00:00 - === 服务监控脚本开始执行 ===
2026-01-29 10:00:01 - 检查服务: ubains-meeting-api-1.0-SNAPSHOT.jar (目录: /var/www/malan, 启动脚本: /var/www/malan/run.sh)
2026-01-29 10:00:01 - 读取失败计数器: 当前失败次数 = 0
2026-01-29 10:00:02 - 服务 'ubains-meeting-api-1.0-SNAPSHOT.jar' 正在运行。
2026-01-29 10:00:02 - 检查成功,重置失败计数器为 0
2026-01-29 10:00:03 - 检查服务: malan (目录: /var/www/malan, 启动脚本: /var/www/malan/run.sh)
2026-01-29 10:00:04 - 服务 'malan' 未运行。
2026-01-29 10:00:05 - 尝试启动服务 'malan'...
2026-01-29 10:00:08 - 服务 'malan' 启动成功。
2026-01-29 10:00:10 - === 服务监控脚本执行完毕 ===
2026-01-29 10:00:03 - 读取失败计数器: 当前失败次数 = 0
2026-01-29 10:00:04 - 警告: 服务 'malan' 未运行
2026-01-29 10:00:04 - 失败计数器更新: 0 -> 1 (阈值: 3)
2026-01-29 10:05:00 - === 服务监控脚本开始执行 ===
2026-01-29 10:05:01 - 检查服务: malan (目录: /var/www/malan, 启动脚本: /var/www/malan/run.sh)
2026-01-29 10:05:01 - 读取失败计数器: 当前失败次数 = 1
2026-01-29 10:05:02 - 警告: 服务 'malan' 未运行
2026-01-29 10:05:02 - 失败计数器更新: 1 -> 2 (阈值: 3)
2026-01-29 10:10:00 - === 服务监控脚本开始执行 ===
2026-01-29 10:10:01 - 检查服务: malan (目录: /var/www/malan, 启动脚本: /var/www/malan/run.sh)
2026-01-29 10:10:01 - 读取失败计数器: 当前失败次数 = 2
2026-01-29 10:10:02 - 警告: 服务 'malan' 未运行
2026-01-29 10:10:02 - 失败计数器更新: 2 -> 3 (阈值: 3)
2026-01-29 10:10:03 - 错误: 连续失败达到阈值(3次),触发服务启动
2026-01-29 10:10:03 - 重置失败计数器为 0
2026-01-29 10:10:05 - 尝试启动服务 'malan'...
2026-01-29 10:10:08 - 服务 'malan' 启动成功。
2026-01-29 10:10:10 - === 服务监控脚本执行完毕 ===
```
### 4.3 错误处理
#### 4.3.1 目录异常
#### 4.3.1 失败计数器异常
- 状态目录不存在:记录警告,创建状态目录
- 状态文件读取失败:记录警告,初始化计数器为0
- 状态文件写入失败:记录警告,但不影响启动决策
- 状态文件权限错误:记录错误并建议检查文件权限
- 计数器值异常(非数字):记录警告,重置为0
#### 4.3.2 目录异常
- 服务目录不存在:记录警告并跳过该服务
- 无法切换目录:记录错误并跳过启动
#### 4.3.2 脚本异常
#### 4.3.3 脚本异常
- 启动脚本不存在:记录错误并跳过启动
- 脚本无执行权限:记录错误并跳过启动
#### 4.3.3 启动异常
#### 4.3.4 启动异常
- 进程启动失败:记录错误详情
- 启动后进程消失:记录启动超时错误
- 启动脚本执行异常:记录脚本错误输出
- **启动失败不增加计数器**:避免重复触发启动
---
......@@ -140,6 +274,11 @@ SERVICES=(
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| LOG_FILE | /var/log/scripts/monitor_external_api_services.log | 日志文件路径 |
| STATE_DIR | /var/log/scripts/external_api_state | 失败计数器状态文件目录 |
| PID_FILE | /var/log/scripts/.external_api_monitor.pid | 进程锁文件,防止并发执行 |
| MAX_LOG_SIZE | 5MB | 日志文件大小限制,超过后自动轮转 |
| LOG_RETENTION_DAYS | 30天 | 轮转后的日志文件保留天数 |
| MAX_FAILURES | 3 | 触发启动的最大连续失败次数 |
| SERVICES | 数组配置 | 监控服务列表配置 |
| STARTUP_WAIT | 3秒 | 进程启动等待时间 |
......@@ -170,17 +309,26 @@ SERVICES=(
- 能准确识别进程运行状态
- 目录和脚本验证机制有效
2. **恢复有效性**
- 停止的服务能被正确识别
2. **失败计数机制**
- 单次失败时正确累加计数器
- 失败计数能持久化保存,进程重启不丢失
- 检查成功时正确重置计数器为0
- 未达阈值时不会触发服务启动
- 达到阈值时正确触发服务启动并清零计数器
- 每个服务独立维护失败计数器,互不影响
3. **恢复有效性**
- 连续失败达到阈值后能正确触发服务启动
- 启动操作能成功执行
- 启动后服务确实正常运行
3. **日志完整性**
4. **日志完整性**
- 每次执行都有完整的开始和结束记录
- 每个服务的检查结果都详细记录
- 日志中包含当前失败次数和阈值信息
- 启动操作有清晰的过程记录
4. **执行安全性**
5. **执行安全性**
- 不影响正在运行的正常服务
- 启动失败时不产生副作用
- 权限检查机制完善
......@@ -202,18 +350,46 @@ SERVICES=(
```
解决:检查文件权限`chmod +x run.sh`
3. **进程启动失败**:
3. **服务停止(未达阈值)**:
```
警告: 服务 'malan' 未运行
失败计数器更新: 0 -> 1 (阈值: 3)
```
说明:这是正常现象,系统在等待下次检查确认是否为持续故障
4. **服务停止(达到阈值)**:
```
错误: 连续失败达到阈值(3次),触发服务启动
```
说明:已连续3次检查失败,系统正在执行自动启动
5. **进程启动失败**:
```
错误: 服务 'xxx' 启动失败。
```
解决:查看startup.log文件内容
6. **状态文件异常**:
```
警告: 无法读取状态文件,初始化失败计数器为 0
```
说明:首次运行或状态文件被删除,属于正常情况
### 8.2 诊断命令
```bash
# 检查进程状态
pgrep -f "ubains-meeting-api"
pgrep -x "malan"
# 查看失败计数器状态
ls -la /var/log/scripts/external_api_state/
cat /var/log/scripts/external_api_state/ubains-meeting-api.state
cat /var/log/scripts/external_api_state/malan.state
# 手动重置失败计数器(恢复后无需手动操作,脚本会自动处理)
echo "0" > /var/log/scripts/external_api_state/ubains-meeting-api.state
echo "0" > /var/log/scripts/external_api_state/malan.state
# 检查服务目录
ls -la /var/www/malan/
......@@ -233,11 +409,26 @@ cd /var/www/malan && ./run.sh
# 实时监控脚本执行
tail -f /var/log/scripts/monitor_external_api_services.log
# 统计异常发生频率
grep "警告\|错误" /var/log/scripts/monitor_external_api_services.log | wc -l
# 查看失败计数变化趋势
grep "失败计数器更新" /var/log/scripts/monitor_external_api_services.log
# 统计触发启动的次数
grep "连续失败达到阈值" /var/log/scripts/monitor_external_api_services.log | wc -l
# 统计服务重启次数
grep "启动成功" /var/log/scripts/monitor_external_api_services.log | wc -l
# 查看最近的错误记录
grep "错误\|跳过" /var/log/scripts/monitor_external_api_services.log
# 分析服务停止问题
grep "未运行" /var/log/scripts/monitor_external_api_services.log
# 查看失败次数达到阈值前的情况
grep -B 2 "连续失败达到阈值" /var/log/scripts/monitor_external_api_services.log
```
---
......
# _PRD_预定系统外部API服务监控需求文档_计划执行.md
> 版本:V1.1
> 更新日期:2026-03-30
> 关联文档:_PRD_预定系统外部API服务监控需求文档.md (V1.1)
> 关联脚本:monitor_external_api_services_v2.sh (V1.0 → V1.1)
---
## 一、变更概述
### 1.1 变更背景
原脚本(V1.0)在检测到服务异常时直接触发服务启动,导致单次网络波动或临时堵塞时就会启动服务,影响业务连续性。
### 1.2 变更目标
引入**连续失败阈值判断机制**,避免因单次临时故障触发不必要的服务启动:
- 单次检查失败时仅记录失败次数,不立即启动
- 连续失败达到阈值(默认3次)时才触发服务启动
- 检查成功后自动重置失败计数器
- 每个服务独立维护失败计数器,通过状态文件持久化保存
---
## 二、当前脚本分析
### 2.1 现有逻辑流程(V1.0)
```
开始 → 遍历服务列表
检查服务健康状态
┌───┴───┐
↓ ↓
检查成功 检查失败
↓ ↓
跳过 立即启动服务
验证结果 → 继续
```
### 2.2 存在的问题
1. **单次失败即启动**:临时网络波动也会触发服务启动
2. **无失败计数**:无法区分瞬时故障和持续故障
3. **无状态持久化**:脚本重启后丢失失败历史
4. **日志不完整**:缺少失败次数和阈值的日志信息
---
## 三、实现计划
### 3.1 新增配置参数
```bash
# 状态文件目录(每个服务独立文件)
STATE_DIR="/var/log/scripts/external_api_state"
# 最大失败次数阈值
MAX_FAILURES=3
```
### 3.2 新增函数
#### 3.2.1 获取服务状态文件路径
```bash
get_service_state_file() {
local service_name="$1"
# 服务名中的特殊字符替换为下划线
local safe_name=$(echo "$service_name" | tr -cd '[:alnum:]_-')
echo "${STATE_DIR}/${safe_name}.state"
}
```
#### 3.2.2 读取服务状态文件
```bash
read_service_state() {
local service_name="$1"
local state_file
state_file=$(get_service_state_file "$service_name")
if [ -f "$state_file" ]; then
source "$state_file"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
```
#### 3.2.3 写入服务状态文件
```bash
write_service_state() {
local service_name="$1"
local count="$2"
local state_file
state_file=$(get_service_state_file "$service_name")
# 确保状态目录存在
mkdir -p "$STATE_DIR"
cat > "$state_file" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
```
### 3.3 主逻辑修改
#### 3.3.1 新的服务检查流程
```
遍历服务 → 读取该服务失败计数器
检查服务健康状态
┌───┴───┐
↓ ↓
检查成功 检查失败
↓ ↓
清零计数器 计数器+1
↓ ↓
继续 写入状态文件
判断是否达阈值?
┌──────┴──────┐
↓ ↓
未达阈值 达到阈值
↓ ↓
继续 清零计数器
启动服务
验证结果
继续
```
#### 3.3.2 主逻辑伪代码
```bash
# 遍历所有服务
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"
# 读取当前失败次数
failure_count=$(read_service_state "$service_name")
log_message "读取失败计数器: 当前失败次数 = $failure_count"
# 检查目录是否存在
if ! is_directory_exists "$dir_path"; then
log_message "跳过服务 '$service_name': 目录 '$dir_path' 不存在。"
continue
fi
# 检查服务是否正常
if is_service_healthy "$service_name"; then
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_service_state "$service_name" 0
log_message "检查成功,重置失败计数器为 0"
fi
log_message "服务 '$service_name' 运行正常 (HTTP 200)。"
else
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_service_state "$service_name" "$failure_count"
log_message "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log_message "错误: 连续失败达到阈值($MAX_FAILURES次),触发服务启动"
write_service_state "$service_name" 0
# 尝试启动服务
start_service "$service_name" "$dir_path" "$script_path"
else
log_message "警告: 未达到启动阈值,等待下次检查确认"
fi
fi
done
```
### 3.4 日志格式变更
```
2026-03-30 10:00:00 - === 服务监控脚本开始执行 (API 检查模式) ===
2026-03-30 10:00:00 - 检查服务: ubains-meeting-api-1.0-SNAPSHOT.jar
2026-03-30 10:00:00 - 读取失败计数器: 当前失败次数 = 0
2026-03-30 10:00:01 - 服务 'ubains-meeting-api-1.0-SNAPSHOT.jar' 运行正常 (HTTP 200)。
2026-03-30 10:00:01 - 检查成功,重置失败计数器为 0
2026-03-30 10:05:00 - 检查服务: malan
2026-03-30 10:05:00 - 读取失败计数器: 当前失败次数 = 0
2026-03-30 10:05:01 - 服务 'malan' 响应异常或未运行。
2026-03-30 10:05:01 - 失败计数器更新: 0 -> 1 (阈值: 3)
2026-03-30 10:05:01 - 警告: 未达到启动阈值,等待下次检查确认
2026-03-30 10:15:00 - 检查服务: malan
2026-03-30 10:15:00 - 读取失败计数器: 当前失败次数 = 2
2026-03-30 10:15:01 - 服务 'malan' 响应异常或未运行。
2026-03-30 10:15:01 - 失败计数器更新: 2 -> 3 (阈值: 3)
2026-03-30 10:15:02 - 错误: 连续失败达到阈值(3次),触发服务启动
2026-03-30 10:15:02 - 重置失败计数器为 0
2026-03-30 10:15:03 - 尝试启动服务 'malan'...
```
---
## 四、代码修改清单
### 4.1 配置变量区域(约第5行后添加)
```bash
# 状态文件配置
STATE_DIR="/var/log/scripts/external_api_state"
MAX_FAILURES=3
```
### 4.2 函数区域(约第21行后添加)
```bash
# 获取服务状态文件路径
get_service_state_file() {
local service_name="$1"
local safe_name=$(echo "$service_name" | tr -cd '[:alnum:]_-')
echo "${STATE_DIR}/${safe_name}.state"
}
# 读取服务状态文件
read_service_state() {
local service_name="$1"
local state_file
state_file=$(get_service_state_file "$service_name")
if [ -f "$state_file" ]; then
source "$state_file"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
# 写入服务状态文件
write_service_state() {
local service_name="$1"
local count="$2"
local state_file
state_file=$(get_service_state_file "$service_name")
mkdir -p "$STATE_DIR"
cat > "$state_file" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
```
### 4.3 主逻辑区域(约第106-131行,完全重写)
```bash
# 遍历所有服务
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"
# 读取当前失败次数
failure_count=$(read_service_state "$service_name")
log_message "读取失败计数器: 当前失败次数 = $failure_count"
# 检查目录是否存在
if ! is_directory_exists "$dir_path"; then
log_message "跳过服务 '$service_name': 目录 '$dir_path' 不存在。"
continue
fi
# 检查服务是否正常
if is_service_healthy "$service_name"; then
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_service_state "$service_name" 0
log_message "检查成功,重置失败计数器为 0"
fi
log_message "服务 '$service_name' 运行正常 (HTTP 200)。"
else
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_service_state "$service_name" "$failure_count"
log_message "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log_message "错误: 连续失败达到阈值($MAX_FAILURES次),触发服务启动"
write_service_state "$service_name" 0
# 尝试启动服务
start_service "$service_name" "$dir_path" "$script_path"
else
log_message "警告: 未达到启动阈值,等待下次检查确认"
fi
fi
done
```
### 4.4 版本信息更新(约第3行,新增)
```bash
# 版本:V1.1
```
---
## 五、测试计划
### 5.1 单元测试
| 测试项 | 测试方法 | 预期结果 |
|--------|----------|----------|
| 状态文件路径生成 | 传入服务名 | 生成正确的状态文件路径 |
| 状态文件读取 | 删除状态文件后读取 | 自动初始化为0 |
| 状态文件写入 | 写入后检查文件 | 正确写入FAILURE_COUNT |
| 计数器独立性 | 多个服务各自失败 | 互不影响 |
### 5.2 集成测试
| 测试场景 | 操作步骤 | 预期结果 |
|----------|----------|----------|
| 单服务单次失败 | 1. 停止服务<br>2. 执行脚本1次 | 计数器=1,不启动 |
| 单服务连续失败 | 1. 停止服务<br>2. 执行脚本3次 | 第3次触发启动 |
| 多服务独立计数 | 1. 停止服务A<br>2. 执行脚本2次<br>3. 停止服务B<br>4. 执行脚本1次 | A=2,B=1 |
---
## 六、部署步骤
### 6.1 备份
```bash
cp /opt/scripts/monitor_external_api_services_v2.sh /opt/scripts/monitor_external_api_services_v2.sh.bak
```
### 6.2 部署
```bash
# 上传新脚本
scp monitor_external_api_services_v2.sh user@server:/opt/scripts/
# 设置执行权限
chmod +x /opt/scripts/monitor_external_api_services_v2.sh
# 创建状态文件目录
mkdir -p /var/log/scripts/external_api_state
```
### 6.3 验证
```bash
# 手动执行测试
/opt/scripts/monitor_external_api_services_v2.sh
# 检查日志
tail -f /var/log/scripts/monitor_external_api_services.log
# 检查状态文件目录
ls -la /var/log/scripts/external_api_state/
```
---
## 七、回滚方案
### 7.1 回滚步骤
```bash
# 停止定时任务
crontab -e # 注释掉相关行
# 恢复原脚本
cp /opt/scripts/monitor_external_api_services_v2.sh.bak /opt/scripts/monitor_external_api_services_v2.sh
# 删除状态文件目录
rm -rf /var/log/scripts/external_api_state
# 重启定时任务
crontab -e # 取消注释
```
---
## 需求规范
- 代码规范:Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md
- 问题总结:Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md
- 方法总结:Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md
- 文档规范:Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md
- 测试规范:Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md
# _PRD_后端服务监测需求文档.md
> 版本:V1.0
> 更新日期:2026-01-27
> 适用范围:预定系统后端服务监测与自愈(容器内服务 + 端口服务)
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_inner_api_services.sh`
> 版本:V1.1
> 更新日期:2026-03-30
> 适用范围:预定系统后端服务监测与自愈(容器内服务 + 端口服务)
> 实现脚本:`自动化部署脚本\x86架构\预定系统\定时脚本\monitor_inner_api_services.sh`
---
## 版本变更记录
| 版本 | 日期 | 变更内容 | 变更人 |
|------|------|----------|--------|
| V1.2 | 2026-03-30 | 新增日志自动轮转机制(5MB轮转、保留30天);新增PID文件锁机制防止并发执行 | Claude |
| V1.1 | 2026-03-30 | 新增连续失败阈值判断机制,避免单次临时堵塞触发重启影响业务 | Claude |
| V1.0 | 2026-01-27 | 初始版本,定义后端服务监测基础需求 | - |
### V1.1 详细变更说明
#### 新增功能
1. **连续失败阈值机制**
- 新增失败计数器,单次服务异常时仅累加计数,不立即重启
- 默认阈值为3次,连续失败达到阈值才触发服务重启/启动
- 服务正常运行后自动重置失败计数器
2. **状态持久化**
- 新增状态目录 `STATE_DIR`,为每个监测对象独立保存失败次数
- 进程重启后计数器状态不丢失
3. **监测对象独立计数**
- 平台API、容器内服务meeting2.0、malan服务各自维护失败计数器
- 互不干扰,独立判断是否需要恢复操作
#### 配置参数变更
| 新增参数 | 默认值 | 说明 |
|----------|--------|------|
| STATE_DIR | /var/log/scripts/inner_api_state | 失败计数器状态文件目录 |
| MAX_FAILURES | 3 | 触发重启/启动的最大连续失败次数 |
#### 日志格式变更
- 日志中新增失败计数信息展示:`失败计数器更新: X -> Y (阈值: Z)`
- 新增阈值达到提示:`[ERROR] 连续失败达到阈值(N次),触发服务启动`
#### 诊断命令变更
- 新增状态文件目录查看命令
- 新增失败计数变化趋势分析命令
---
---
......@@ -18,6 +60,8 @@
- 容器内关键服务进程监测与拉起
- 平台 API 可用性检查,异常时强制重启关键服务(inner)
- 宿主机 6060 端口监测,未监听时启动 malan
- **连续失败阈值判断机制,避免单次临时堵塞触发重启影响业务**
- 失败次数记录与重置机制
- 全流程日志输出与审计
---
......@@ -40,6 +84,13 @@
- 统一告警通道(钉钉/邮件/短信)
- 容器资源限制/扩容策略
### 2.3 核心机制说明
**连续失败恢复机制**:为避免因网络抖动、瞬时卡顿等临时故障导致的不必要服务重启,采用失败计数器机制:
- 单次检查异常时,仅记录失败次数,不立即执行恢复操作
- 连续失败达到阈值(默认3次)时,才触发服务重启/启动
- 服务正常运行后,自动重置失败计数器
- 为每个监测对象(平台API、容器内服务、malan服务)独立维护失败计数器,通过状态文件持久化保存,避免计数器因进程重启丢失
---
## 3. 术语说明
......@@ -54,7 +105,47 @@
### 4.1 日志与审计
#### 4.1.1 日志文件
- 主日志:`/var/log/monitor-inner-api-services.log`
- 主日志:`/var/log/scripts/monitor-inner-api-services.log`
- 状态文件目录:`/var/log/scripts/inner_api_state`
- PID锁文件:`/var/log/scripts/.inner_api_monitor.pid`
#### 4.1.2 日志自动轮转机制
为避免日志文件无限增长占用磁盘空间,实现自动轮转机制:
1. **轮转触发条件**
- 当前日志文件大小达到 5MB (`MAX_LOG_SIZE=$((5*1024*1024))`)
- 每次脚本执行时自动检查
2. **轮转执行动作**
- 将当前日志文件重命名为带时间戳的旧日志:`monitor-inner-api-services.log.YYYYMMDDHHMMSS`
- 创建新的空日志文件继续写入
- 记录轮转操作日志
3. **旧日志清理**
- 自动删除超过保留天数的旧日志文件(默认30天)
- 使用 `find` 命令清理
#### 4.1.3 并发控制机制(PID锁)
为防止定时任务重叠执行导致的问题,实现PID文件锁机制:
1. **锁文件检查**
- 脚本开始执行时检查PID文件是否存在
- 如果PID文件存在,读取其中的PID值
2. **进程存活判断**
- 使用 `kill -0 $PID` 检查该进程是否仍在运行
- 如果进程存在,记录警告日志并退出本次执行
- 如果进程不存在,清理旧PID文件并继续执行
3. **锁文件创建与释放**
- 将当前进程PID写入PID文件
- 使用 `trap` 命令设置退出时自动清理
- 捕获信号:EXIT INT TERM HUP
4. **并发执行日志示例**
```
[2026-03-30 10:05:01] [WARNING] 上一次脚本执行仍在运行 (PID: 12345),本次跳过执行
```
#### 4.1.2 日志格式
每行必须包含:
......@@ -78,9 +169,30 @@
- HTTP 状态码必须为 `200`
- 响应内容必须包含:`"操作成功"` 且 `code:200`(允许空格)
#### 4.2.2 处理策略
- 若 API 检查失败:触发 **强制重启内部服务**(见 4.3)
- API 正常:继续后续检查流程
#### 4.2.2 失败处理机制
采用**连续失败阈值判断**机制:
1. **读取失败计数器**
- 从状态文件读取平台API的当前连续失败次数
- 状态文件不存在时初始化为0
2. **单次失败处理**
- API检查失败时,失败计数器+1
- 将最新失败次数写入状态文件持久化保存
- 记录警告日志,说明当前失败次数和阈值
- **不触发重启,直接退出本次检查**
3. **连续失败达到阈值**
- 当失败次数 ≥ MAX_FAILURES(默认3次)时
- 清零失败计数器
- 触发 **强制重启内部服务**(见 4.3)
- 记录错误日志说明已连续失败N次
4. **API正常处理**
- API检查通过时
- 将失败计数器清零并更新状态文件
- 记录成功日志
- 继续后续检查流程
---
......@@ -92,45 +204,97 @@
> 注:meeting2.0/3.0 通过 ps 组合条件避免误匹配(脚本内为特判逻辑)。
#### 4.3.2 运行状态判定
说明:容器名称不一定叫ujava2,也可能是ujava3、ujava6,需要做模糊匹配。
判断1:
- 获取 PID:通过 `docker exec ujava2` 在容器内 `ps aux | grep` 定位
- 判定运行:PID 非空 且 `ps -p $pid` 成功
判断2:
- code接口调用:通过curl -v https:/服务器实际IP地址/api/system/getVerifyCode
#### 4.3.2 失败处理机制(核心)
采用**连续失败阈值判断**机制:
1. **读取失败计数器**
- 从状态文件读取当前服务的连续失败次数(按服务key独立维护)
- 状态文件不存在时初始化为0
2. **运行状态判定**
说明:容器名称不一定叫ujava2,也可能是ujava3、ujava6,需要做模糊匹配。
判断1:
- 获取 PID:通过 `docker exec ujava2` 在容器内 `ps aux | grep` 定位
- 判定运行:PID 非空 且 `ps -p $pid` 成功
判断2:
- code接口调用:通过curl -v https:/服务器实际IP地址/api/system/getVerifyCode
3. **单次失败处理**
- 服务进程未运行时,失败计数器+1
- 将最新失败次数写入状态文件持久化保存
- 记录警告日志,说明当前失败次数和阈值
- **不触发启动,继续处理下一个服务**
4. **连续失败达到阈值**
- 当失败次数 ≥ MAX_FAILURES(默认3次)时
- 清零失败计数器
- 触发服务启动流程
- 记录错误日志说明已连续失败N次
5. **服务正常运行处理**
- 进程检测存在时
- 将失败计数器清零并更新状态文件
- 记录成功日志
- 继续处理下一个服务
#### 4.3.3 自愈动作:run.sh
当服务连续失败达到阈值时执行:
- 启动:在容器内执行:
- `cd /var/www/java/api-java-meeting2.0/`
- `./run.sh`
- **注意**:必须使用 `./run.sh` 而非 `bash run.sh`,因为脚本可能依赖执行路径
- 等待启动:超时 30 秒
- 成功:输出 `[SUCCESS] service_key (PID: xxx)`
- 失败:输出 `[FAILED] service_key 启动超时`
- 成功:输出 `[SUCCESS] service_key (PID: xxx)`,失败计数器保持为0
- 失败:输出 `[FAILED] service_key 启动超时`,不增加计数器避免重复触发启动
#### 4.3.4 服务检查:check_services
- 输出本轮检查开始标识:`===== timestamp 服务状态检查 =====`
- 遍历全部服务输出 RUNNING/STOPPED
- 统计 STOPPED 数量 problems
- 调用 `restart_unacos_if_needed problems`
- 遍历全部服务,读取各自的失败计数器
- 输出 RUNNING/STOPPED 状态和失败计数信息
- 统计达到阈值的 STOPPED 服务数量 problems
- 仅对达到阈值的服务调用启动恢复
- 返回 problems 作为流程判断依据
---
### 4.4 宿主机 6060 端口与 malan 监测与启动
#### 4.4.1 6060端口检查
端口监听判定满足任一:
- `netstat -tuln | grep ':6060 '`
- `ss -tuln | grep ':6060 '`
- `lsof -i:6060`
#### 4.4.1 失败处理机制(核心)
采用**连续失败阈值判断**机制:
1. **读取失败计数器**
- 从状态文件读取malan服务的当前连续失败次数
- 状态文件不存在时初始化为0
2. **6060端口检查**
端口监听判定满足任一:
- `netstat -tuln | grep ':6060 '`
- `ss -tuln | grep ':6060 '`
- `lsof -i:6060`
3. **单次失败处理**
- 端口未监听时,失败计数器+1
- 将最新失败次数写入状态文件持久化保存
- 记录警告日志,说明当前失败次数和阈值
- **不触发启动,直接退出本次检查**
4. **连续失败达到阈值**
- 当失败次数 ≥ MAX_FAILURES(默认3次)时
- 清零失败计数器
- 触发malan服务启动流程
- 记录错误日志说明已连续失败N次
5. **端口正常监听处理**
- 端口检查通过时
- 将失败计数器清零并更新状态文件
- 记录成功日志
#### 4.4.2 启动 malan
当malan服务连续失败达到阈值时执行:
- 目录:`/var/www/malan`
- 启动命令:`bash -c "source /etc/profile && ./malan" &`
- 等待端口就绪 30 秒
- 成功:记录 `6060端口已监听`
- 失败:记录 `启动超时,6060端口未监听`
- 成功:记录 `6060端口已监听`,失败计数器保持为0
- 失败:记录 `启动超时,6060端口未监听`,不增加计数器避免重复触发启动
---
......@@ -159,6 +323,11 @@
| ujava容器名 | `ujava*` | 支持模糊匹配(ujava2/ujava3/ujava6等) |
| 平台 API URL | `https://自动获取的IP/api/system/getVerifyCode` | 用于判断平台核心可用性 |
| malan 目录 | `/var/www/malan` | 6060端口服务 |
| 状态文件目录 | `/var/log/scripts/inner_api_state` | 失败计数器状态文件目录 |
| PID文件 | `/var/log/scripts/.inner_api_monitor.pid` | 进程锁文件,防止并发执行 |
| 日志大小限制 | 5MB | 日志文件大小限制,超过后自动轮转 |
| 日志保留天数 | 30天 | 轮转后的日志文件保留天数 |
| 最大失败次数 | 3 | 触发重启/启动的最大连续失败次数 |
| 服务启动等待 | 30s | start_service/restart_extapi/malan 统一超时 |
| 调试模式 | `true` | 启用时打印详细运行参数,便于排查问题 |
......@@ -182,12 +351,40 @@
2026-01-27 19:10:00 [DEBUG] API_URL: https://192.168.5.47/api/system/getVerifyCode
2026-01-27 19:10:00 [DEBUG] MALAN_DIR: /var/www/malan
2026-01-27 19:10:00 [DEBUG] MALAN_PORT: 6060
2026-01-27 19:10:00 [DEBUG] STATE_DIR: /var/log/scripts/inner_api_state
2026-01-27 19:10:00 [DEBUG] MAX_FAILURES: 3
2026-01-27 19:10:00 [DEBUG] START_TIMEOUT: 30秒
2026-01-27 19:10:00 [DEBUG] RESTART_WAIT: 30秒
2026-01-27 19:10:00 [DEBUG] 服务[meeting2.0]: jar=ubains-meeting-inner-api, path=/var/www/java/api-java-meeting2.0/
2026-01-27 19:10:00 [DEBUG] ==============================
```
**日志输出示例(含失败计数):**
```
2026-01-27 10:00:00 [START] ===== 后端服务监测开始 =====
2026-01-27 10:00:00 [INFO] 读取失败计数器[platform_api]: 当前失败次数 = 0
2026-01-27 10:00:02 [SUCCESS] 平台API检查通过,重置失败计数器为 0
2026-01-27 10:00:03 [INFO] ===== 服务状态检查 =====
2026-01-27 10:00:03 [INFO] 读取失败计数器[meeting2.0]: 当前失败次数 = 0
2026-01-27 10:00:04 [RUNNING] meeting2.0 (PID: 1234),重置失败计数器为 0
2026-01-27 10:00:05 [INFO] 读取失败计数器[malan]: 当前失败次数 = 0
2026-01-27 10:00:06 [SUCCESS] 6060端口已监听,重置失败计数器为 0
---
2026-01-27 10:05:00 [START] ===== 后端服务监测开始 =====
2026-01-27 10:05:00 [INFO] 读取失败计数器[meeting2.0]: 当前失败次数 = 0
2026-01-27 10:05:01 [WARNING] 服务 'meeting2.0' 未运行
2026-01-27 10:05:01 [WARNING] 失败计数器更新: 0 -> 1 (阈值: 3)
---
2026-01-27 10:15:00 [START] ===== 后端服务监测开始 =====
2026-01-27 10:15:00 [INFO] 读取失败计数器[meeting2.0]: 当前失败次数 = 2
2026-01-27 10:15:01 [WARNING] 服务 'meeting2.0' 未运行
2026-01-27 10:15:01 [WARNING] 失败计数器更新: 2 -> 3 (阈值: 3)
2026-01-27 10:15:02 [ERROR] 连续失败达到阈值(3次),触发服务启动
2026-01-27 10:15:02 [INFO] 重置失败计数器为 0
2026-01-27 10:15:03 [START] 正在启动服务 meeting2.0...
2026-01-27 10:15:33 [SUCCESS] meeting2.0 (PID: 5678)
```
---
## 7. 异常处理与容错要求
......@@ -196,6 +393,13 @@
2. `run.sh` 不存在必须明确提示(各服务)。
3. 重启 unacos 成功后必须等待 30 秒再继续(避免依赖未就绪)。
4. 端口检测必须兼容不同系统命令(netstat/ss/lsof 任一存在即可)。
5. **失败计数器异常处理**:
- 状态目录不存在:记录警告,创建状态目录
- 状态文件读取失败:记录警告,初始化计数器为0
- 状态文件写入失败:记录警告,但不影响重启决策
- 状态文件权限错误:记录错误并建议检查文件权限
- 计数器值异常(非数字):记录警告,重置为0
6. **启动失败不增加计数器**:避免重复触发重启
---
......@@ -212,11 +416,133 @@
## 9. 验收标准
1. 当任一容器内服务停止时:
- 日志出现 `[STOPPED]` 与后续 `[START]/[SUCCESS]`,服务恢复运行。
2. 当 6060 未监听:
- 能启动 malan 并在 30 秒内监听成功,否则输出失败原因。
3. 所有执行过程有可追溯日志,且不会静默失败。
1. **监测准确性**:
- 能正确识别平台API、容器内服务、malan服务的运行状态
- 能准确检测端口监听状态
- 目录和脚本验证机制有效
2. **失败计数机制**:
- 单次失败时正确累加计数器
- 失败计数能持久化保存,进程重启不丢失
- 检查成功时正确重置计数器为0
- 未达阈值时不会触发重启/启动
- 达到阈值时正确触发重启/启动并清零计数器
- 每个监测对象(平台API、meeting2.0、malan)独立维护失败计数器,互不影响
3. **恢复有效性**:
- 连续失败达到阈值后能正确触发恢复操作
- 平台API异常时能正确重启内部服务
- 容器内服务停止时能正确启动
- malan服务停止时能正确启动
- 恢复后服务确实正常运行
4. **日志完整性**:
- 每次执行都有完整的开始和结束记录
- 每个监测对象的检查结果都详细记录
- 日志中包含当前失败次数和阈值信息
- 恢复操作有清晰的过程记录
- 不会静默失败
---
## 10. 故障排查
### 10.1 常见问题
1. **平台API检查失败(未达阈值)**:
```
[WARNING] 平台API检查失败
[WARNING] 失败计数器更新: 0 -> 1 (阈值: 3)
```
说明:这是正常现象,系统在等待下次检查确认是否为持续故障
2. **平台API检查失败(达到阈值)**:
```
[ERROR] 连续失败达到阈值(3次),触发服务启动
```
说明:已连续3次检查失败,系统正在执行自动恢复
3. **容器内服务停止**:
```
[WARNING] 服务 'meeting2.0' 未运行
[WARNING] 失败计数器更新: X -> Y (阈值: 3)
```
说明:服务进程未运行,系统正在计数
4. **malan服务启动失败**:
```
[FAILED] 启动超时,6060端口未监听
```
解决:查看malan服务目录和启动日志
5. **状态文件异常**:
```
[WARNING] 无法读取状态文件,初始化失败计数器为 0
```
说明:首次运行或状态文件被删除,属于正常情况
### 10.2 诊断命令
```bash
# 检查容器状态
docker ps | grep ujava
# 检查容器内服务进程
docker exec ujava2 ps aux | grep meeting
# 手动检查平台API
curl -k -m 10 https://服务器IP/api/system/getVerifyCode
# 检查6060端口
netstat -tuln | grep ':6060 '
ss -tuln | grep ':6060 '
lsof -i:6060
# 查看失败计数器状态
ls -la /var/log/scripts/inner_api_state/
cat /var/log/scripts/inner_api_state/platform_api.state
cat /var/log/scripts/inner_api_state/meeting2.0.state
cat /var/log/scripts/inner_api_state/malan.state
# 手动重置失败计数器(恢复后无需手动操作,脚本会自动处理)
echo "0" > /var/log/scripts/inner_api_state/platform_api.state
echo "0" > /var/log/scripts/inner_api_state/meeting2.0.state
echo "0" > /var/log/scripts/inner_api_state/malan.state
# 查看监测日志
tail -f /var/log/monitor-inner-api-services.log
# 手动启动容器内服务
docker exec ujava2 bash -c "cd /var/www/java/api-java-meeting2.0/ && ./run.sh"
# 手动启动malan服务
cd /var/www/malan && bash -c "source /etc/profile && ./malan" &
```
### 10.3 日志分析
```bash
# 实时监控脚本执行
tail -f /var/log/monitor-inner-api-services.log
# 统计异常发生频率
grep "\[WARNING\]\|\[ERROR\]" /var/log/monitor-inner-api-services.log | wc -l
# 查看失败计数变化趋势
grep "失败计数器更新" /var/log/monitor-inner-api-services.log
# 统计触发恢复的次数
grep "连续失败达到阈值" /var/log/monitor-inner-api-services.log | wc -l
# 统计各服务恢复成功次数
grep "\[SUCCESS\]" /var/log/monitor-inner-api-services.log | wc -l
# 查看平台API检查失败情况
grep "平台API检查失败" /var/log/monitor-inner-api-services.log
# 查看服务停止问题
grep "\[STOPPED\]" /var/log/monitor-inner-api-services.log
# 查看失败次数达到阈值前的情况
grep -B 2 "连续失败达到阈值" /var/log/monitor-inner-api-services.log
```
---
......
#!/bin/bash
#===============================================================================
# 脚本名称:auto_clean_deleted_ubains_v3.sh
# 功能描述:已删除大文件自动清理与容器重启脚本
# 版本:V3.0
# 创建日期:2026-01-27
# 更新日期:2026-03-30
#
# 监测对象:
# 1. 进程占用的已删除大文件(>1GB)
# 2. 匹配关键字: ubains-INFO-AND-ERROR
# 3. 自动清理并重启关联容器
# 4. 日志文件自动轮转(5MB轮转、保留7天)
#
# 清理策略:
# 1. 扫描所有进程的fd目录,查找deleted标记文件
# 2. 文件大小超过1GB时执行自动处理
# 3. 强制杀死占用进程
# 4. 重启docker容器ujava2
# 5. 若进程属于特定应用,启动该应用
#
# 使用方法:
# chmod +x auto_clean_deleted_ubains_v3.sh
# ./auto_clean_deleted_ubains_v3.sh
#
# 定时任务示例:
# 0 4 * * * /opt/scripts/auto_clean_deleted_ubains_v3.sh
#===============================================================================
# ================= 配置区域 =================
TARGET_KEY="ubains-INFO-AND-ERROR"
MIN_SIZE=$((1024*1024*1024)) # 1GB (单位:字节)
......
......@@ -29,6 +29,8 @@ CONTAINER_NAME="umysql"
DB_USER="root"
HOST_BACKUP_DIR="/opt/mysql" # 宿主机备份目录
LOG_FILE="/var/log/scripts/backup_mysql_databases.log"
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=30 # 日志保留天数
RETENTION_DAYS=30
TARGET_DBS=("devops" "devops_voice" "huazhao2" "nacos_mysql" "offline" "ubains" "wifi" "voice" "ubains_nacos_config" "ubains_sso")
......@@ -38,7 +40,21 @@ CONTAINER_TMP_DIR="/tmp/mysql_backup_$$.sql.gz" # 容器内临时目录(带PI
# ==================== 日志函数 ====================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# ==================== 日志轮转函数 ====================
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"
log "日志文件超过 5MB,已自动轮转。"
fi
fi
# 清理超过保留天数的旧日志文件
find "$(dirname "$LOG_FILE")" -name "backup_mysql_databases.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
get_mysql_password() {
......@@ -47,7 +63,12 @@ get_mysql_password() {
# ==================== 初始化 ====================
mkdir -p "$HOST_BACKUP_DIR/$DATE"
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
# 执行日志轮转
rotate_logs
log "===== 开始备份任务 (容器: $CONTAINER_NAME) ====="
# 在容器内创建临时目录
......
......@@ -3,8 +3,9 @@
#===============================================================================
# 脚本名称:backup_mysql_logs.sh
# 功能描述:MySQL日志文件定时备份与压缩脚本
# 版本:V1.0
# 版本:V1.1
# 创建日期:2026-01-27
# 更新日期:2026-03-30
# 基于文档:_PRD_预定系统_MySQL日志备份需求文档.md
#
# 备份对象:
......@@ -12,6 +13,7 @@
# 2. 自动压缩备份文件
# 3. 保留原始文件权限
# 4. 自动清理过期备份
# 5. 日志文件自动轮转(5MB轮转、保留30天)
#
# 日志文件类型:
# *.log, *.slow, error.log, slow.log, mysqld.log
......@@ -28,14 +30,30 @@
MYSQL_LOG_DIR="/opt/mysql/logs" # MySQL日志目录
BACKUP_DIR="/opt/mysql/logs/backup" # 备份目录
LOG_FILE="/var/log/scripts/backup_mysql_logs.log" # 日志文件路径
RETENTION_DAYS=30 # 保留天数
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=30 # 日志保留天数
RETENTION_DAYS=30 # 备份保留天数
DATE=$(date +"%Y%m%d") # 当前日期格式 YYYYMMDD
TODAY_DIR="$BACKUP_DIR/$DATE" # 今天的备份目录
# ==================== 日志函数 ====================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# ==================== 日志轮转函数 ====================
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"
log "日志文件超过 5MB,已自动轮转。"
fi
fi
# 清理超过保留天数的旧日志文件
find "$(dirname "$LOG_FILE")" -name "backup_mysql_logs.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# ==================== 权限保存和恢复函数 ====================
......@@ -54,26 +72,36 @@ save_permissions() {
restore_permissions() {
local file="$1"
local perm_file="$file.permissions.tmp"
if [ -f "$perm_file" ]; then
local perms owner group
read -r perms owner group < "$perm_file"
# 恢复权限和属主
chown "$owner:$group" "$file" 2>/dev/null
chmod "$perms" "$file" 2>/dev/null
# 恢复权限和属主(增加空值检查)
if [ -n "$owner" ] && [ -n "$group" ]; then
chown "$owner:$group" "$file" 2>/dev/null
fi
if [ -n "$perms" ]; then
chmod "$perms" "$file" 2>/dev/null
fi
# 删除临时权限文件
rm -f "$perm_file"
fi
}
# ==================== 主要功能 ====================
# 创建日志目录并确保存在
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
# 执行日志轮转
rotate_logs
log "===== 开始备份MySQL日志 (日期: $DATE) ====="
# 创建备份目录
mkdir -p "$TODAY_DIR"
touch "$LOG_FILE"
# 检查MySQL日志目录是否存在
if [ ! -d "$MYSQL_LOG_DIR" ]; then
......
......@@ -3,8 +3,9 @@
#===============================================================================
# 脚本名称:backup_nginx_logs.sh
# 功能描述:Nginx日志文件定时备份与压缩脚本
# 版本:V1.0
# 版本:V1.1
# 创建日期:2026-01-27
# 更新日期:2026-03-30
# 基于文档:_PRD_预定系统_Nginx日志备份需求文档.md
#
# 备份对象:
......@@ -13,6 +14,7 @@
# 3. 自动压缩并清空原日志文件
# 4. 向Nginx发送reopen信号重新打开日志
# 5. 自动清理过期备份
# 6. 日志文件自动轮转(5MB轮转、保留30天)
#
# 使用方法:
# chmod +x backup_nginx_logs.sh
......@@ -25,15 +27,31 @@
# ==================== 配置区 ====================
NGINX_LOG_DIR="/var/www/java/nginx-conf.d/nginx_log" # Nginx日志目录
BACKUP_DIR="$NGINX_LOG_DIR/backup" # 备份目录
LOG_FILE="/var/log/scripts/backup_nginx_logs.log" # 日志文件路径
RETENTION_DAYS=30 # 保留天数
LOG_FILE="/var/log/scripts/backup_nginx_logs.log" # 日志文件路径
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=30 # 日志保留天数
RETENTION_DAYS=30 # 备份保留天数
DATE=$(date +"%Y%m%d") # 当前日期格式 YYYYMMDD
TODAY_DIR="$BACKUP_DIR/$DATE" # 今天的备份目录
# ==================== 日志函数 ====================
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# ==================== 日志轮转函数 ====================
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"
log "日志文件超过 5MB,已自动轮转。"
fi
fi
# 清理超过保留天数的旧日志文件
find "$(dirname "$LOG_FILE")" -name "backup_nginx_logs.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# ==================== 权限保存和恢复函数 ====================
......@@ -52,26 +70,36 @@ save_permissions() {
restore_permissions() {
local file="$1"
local perm_file="$file.permissions.tmp"
if [ -f "$perm_file" ]; then
local perms owner group
read -r perms owner group < "$perm_file"
# 恢复权限和属主
chown "$owner:$group" "$file" 2>/dev/null
chmod "$perms" "$file" 2>/dev/null
# 恢复权限和属主(增加空值检查)
if [ -n "$owner" ] && [ -n "$group" ]; then
chown "$owner:$group" "$file" 2>/dev/null
fi
if [ -n "$perms" ]; then
chmod "$perms" "$file" 2>/dev/null
fi
# 删除临时权限文件
rm -f "$perm_file"
fi
}
# ==================== 主要功能 ====================
# 创建日志目录并确保存在
mkdir -p "$(dirname "$LOG_FILE")"
touch "$LOG_FILE"
# 执行日志轮转
rotate_logs
log "===== 开始备份Nginx日志 (日期: $DATE) ====="
# 创建备份目录
mkdir -p "$TODAY_DIR"
touch "$LOG_FILE"
# 检查Nginx日志目录是否存在
if [ ! -d "$NGINX_LOG_DIR" ]; then
......
#!/bin/bash
#===============================================================================
# 脚本名称:check_deleted_file_ubains.sh
# 功能描述:已删除但仍被占用文件排查工具(内核直读版)
# 版本:V1.1
# 创建日期:2026-02-28
# 更新日期:2026-03-30
#
# 监测对象:
# 1. 扫描所有进程的fd目录
# 2. 查找已删除但仍被占用的文件
# 3. 统计占用空间大小
# 4. 显示进程信息和文件路径
#
# 使用方法:
# chmod +x check_deleted_file_ubains.sh
# ./check_deleted_file_ubains.sh
#
# 注意事项:
# 本脚本为检查工具,仅显示信息不执行清理操作
# 清理操作请使用 auto_clean_deleted_ubains_v3.sh
#===============================================================================
echo "==============================================="
echo " Linux 已删除但仍被占用文件排查工具 (内核直读版)"
echo "==============================================="
......@@ -31,7 +54,7 @@ for fd_path in /proc/[0-9]*/fd/*; do
# 注意:对于已删除文件,直接 stat fd 路径是获取该文件大小最准确的方法
size_bytes=$(stat -L -c %s "$fd_path" 2>/dev/null || echo 0)
if [ "$size_bytes" -gt 0 ] || [[ "$target_file" == *"$TARGET_KEY"* ]]; then
if [ "$size_bytes" -gt 0 ]; then
found_count=$((found_count + 1))
total_size=$((total_size + size_bytes))
......
......@@ -2,35 +2,108 @@
#===============================================================================
# 脚本名称:monitor_emqx_service.sh
# 功能描述:EMQX服务监测与自愈脚本
# 版本:V1.0
# 功能描述:EMQX 服务监测与自愈脚本 (增强版)
# 版本:V2.1
# 创建日期:2026-01-27
# 基于文档:_PRD_预定系统_EMQX服务监控需求文档.md
# 修改日期:2026-03-30
# 基于文档:_PRD_预定系统_EMQX 服务监控需求文档.md V2.1
# 变更历史:
# V1.0 (2026-01-27): 初始版本,实现基础检测和重启功能
# V2.0 (2026-03-30): 重大升级
# - 增加连续失败计数机制 (MAX_FAILURES=3)
# - 增加失败重试间隔 (FAILURE_INTERVAL=5 秒)
# - 增加重启冷却时间 (COOLDOWN_PERIOD=1800 秒/30 分钟)
# - 增加多维度健康检查 (进程 + 端口+CLI 命令)
# - 增加状态持久化机制
# - 优化日志输出详细度
# - 增加冷却期保护逻辑
# - 完善错误处理和诊断信息
# V2.1 (2026-03-30): 统一日志格式,完善失败计数机制说明
#
# 监测对象
# 1. uemqx容器运行状态
# 2. 容器内EMQX进程状态
# 3. EMQX服务可用性(使用emqx_ctl status命令)
# 监测对象:
# 1. uemqx 容器运行状态
# 2. 容器内 EMQX 进程状态
# 3. EMQX 服务可用性 (使用 emqx_ctl status 命令)
# 4. 自动重启异常容器
#
# 恢复策略:
# 1. 检查容器是否运行
# 2. 检查EMQX进程是否存在
# 3. 验证服务状态是否正常
# 4. 异常时自动重启容器
# 5. 重启后验证服务恢复情况
# 智能恢复策略:
# 1. 连续失败检测:连续 3 次检测失败才触发重启
# 2. 多重验证机制:进程检查 + 端口检查 + 命令响应
# 3. 重启冷却时间:30 分钟内不重复重启同一容器
#
# 使用方法
# 使用方法:
# chmod +x monitor_emqx_service.sh
# ./monitor_emqx_service.sh
#
# 定时任务示例
# 定时任务示例:
# */5 * * * * /opt/scripts/monitor_emqx_service.sh
#===============================================================================
# 宿主机上的脚本:检查 EMQX,如果容器未运行则重启 uemqx 容器
# 配置参数
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 分钟)
START_WAIT_TIME=30 # 启动后等待时间 (秒)
SERVICE_CHECK_WAIT=10 # 服务状态检查等待时间 (秒)
#===============================================================================
# 函数定义
#===============================================================================
# 获取锁函数(防止并发执行)
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
}
# 释放锁函数(可选,trap 会自动处理)
release_lock() {
rm -f "$PID_FILE"
}
# 日志轮转函数
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 "monitor_emqx_service.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# 日志记录函数
log() {
local message
message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
......@@ -39,113 +112,240 @@ 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 端口是否监听
check_emqx_ports() {
local ports=(1883 8883 8083 8084 18083)
local listening_count=0
for port in "${ports[@]}"; do
if docker exec uemqx netstat -tuln 2>/dev/null | grep -q ":${port} "; then
((listening_count++))
fi
done
# 至少有 2 个核心端口监听认为正常 (1883 必须)
if [ $listening_count -ge 2 ] && docker exec uemqx netstat -tuln 2>/dev/null | grep -q ":1883 "; then
return 0
else
log "信息: uemqx 容器未运行"
return 1
fi
}
# 综合健康检查
comprehensive_health_check() {
# 检查 1: 容器是否运行
if ! docker ps --format '{{.Names}}' | grep -Fxq "uemqx"; then
return 1
fi
# 检查 2: EMQX 进程是否存在
if ! docker exec uemqx pgrep -f "emqx" >/dev/null 2>&1; then
return 1
fi
# 检查 3: 核心端口是否监听
if ! check_emqx_ports; then
return 1
fi
# 检查 4: emqx_ctl 命令响应
local status_output
status_output=$(docker exec uemqx emqx_ctl status 2>&1)
if ! echo "$status_output" | grep -q "is started"; then
return 1
fi
return 0
}
# 重启EMQX容器
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
local current_time
current_time=$(date +%s)
local last_restart_time
last_restart_time=$(get_last_restart_time)
local time_since_restart=$((current_time - last_restart_time))
log "开始执行 EMQX 容器重启流程"
log "距上次重启时间:${time_since_restart}秒"
# 检查容器是否存在
if ! docker ps -a --format '{{.Names}}' | grep -Fxq "uemqx"; then
log "错误: 容器 'uemqx' 不存在!"
return 1
fi
# 停止容器
docker stop uemqx >/dev/null 2>&1 || true
sleep 2
# 启动容器
if docker start uemqx; then
log "容器启动命令执行成功"
# 等待服务启动
log "等待 ${START_WAIT_TIME} 秒让 EMQX 服务完全启动..."
for i in $(seq 1 $START_WAIT_TIME); do
echo -n "." >> "$LOG_FILE"
sleep 1
done
echo "" >> "$LOG_FILE"
# 验证启动结果
# 检查容器运行状态
if ! docker ps --format '{{.Names}}' | grep -Fxq "uemqx"; then
log "错误: 容器启动后立即停止"
write_state_file 0 "$current_time"
return 1
fi
log "容器运行状态正常"
# 检查进程状态
sleep 5
if ! docker exec uemqx pgrep -f "emqx" >/dev/null 2>&1; then
log "错误: EMQX 进程未启动"
write_state_file 0 "$current_time"
return 1
fi
log "EMQX 进程已启动"
# 检查端口监听
if ! check_emqx_ports; then
log "错误: EMQX 端口未正常监听"
write_state_file 0 "$current_time"
return 1
fi
log "EMQX 端口监听正常"
# 检查服务状态
sleep $SERVICE_CHECK_WAIT
local status_output
status_output=$(docker exec uemqx emqx_ctl status 2>&1)
if echo "$status_output" | grep -q "is started"; then
log "EMQX 服务状态验证通过"
write_state_file 0 "$current_time"
return 0
else
log "错误: 无法启动 EMQX 容器。"
log "错误: EMQX 服务状态异常"
write_state_file 0 "$current_time"
return 1
fi
else
log "错误: 容器 'uemqx' 不存在!"
log "信息: 您可能需要手动重新创建 EMQX 容器。"
log "错误: 无法启动容器 'uemqx'"
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 服务状态异常:emqx is not running"
log "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "错误: 连续失败达到阈值($MAX_FAILURES次),触发容器重启"
write_state_file 0 "$last_restart_time"
# 检查冷却期
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}秒),跳过本次重启"
log "失败计数器保持为 $failure_count,等待冷却期过后重新检查"
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
#!/bin/bash
#===============================================================================
# 脚本名称:monitor_external_api_services_v2.sh
# 功能描述:外部API服务监测与自愈脚本
# 版本:V1.1
# 创建日期:2026-01-27
# 更新日期:2026-03-30
# 基于文档:_PRD_预定系统外部API服务监控需求文档.md (V1.1)
#
# 监测对象:
# 1. ubains-meeting-api-1.0-SNAPSHOT.jar
# 2. malan服务
#
# 恢复策略:
# 1. 连续失败阈值判断(默认3次)
# 2. 每个服务独立维护失败计数器
# 3. 达到阈值时启动服务
#
# 使用方法:
# chmod +x monitor_external_api_services_v2.sh
# ./monitor_external_api_services_v2.sh
#
# 定时任务示例:
# */5 * * * * /opt/scripts/monitor_external_api_services_v2.sh
#===============================================================================
# --- 配置区域 ---
# 日志文件路径
LOG_FILE="/var/log/scripts/monitor_external_api_services.log"
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=30 # 日志保留天数
PID_FILE="/var/log/scripts/.external_api_monitor.pid"
# 状态文件配置
STATE_DIR="/var/log/scripts/external_api_state"
MAX_FAILURES=3
# 定义要监控的服务及其相关信息
# 格式: "服务名:目录路径:启动脚本路径"
......@@ -18,18 +50,102 @@ API_DATA='{
"password": "test"
}'
# --- 函数定义 ---
#===============================================================================
# 函数定义
#===============================================================================
# 获取锁函数(防止并发执行)
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 "monitor_external_api_services.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# 记录日志的函数
log_message() {
local message="$1"
echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" >> "$LOG_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S') - $message" | tee -a "$LOG_FILE"
}
# 获取服务状态文件路径
get_service_state_file() {
local service_name="$1"
# 服务名中的特殊字符替换为下划线
local safe_name=$(echo "$service_name" | tr -cd '[:alnum:]_-')
echo "${STATE_DIR}/${safe_name}.state"
}
# 读取服务状态文件
read_service_state() {
local service_name="$1"
local state_file
state_file=$(get_service_state_file "$service_name")
if [ -f "$state_file" ]; then
source "$state_file"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
# 写入服务状态文件
write_service_state() {
local service_name="$1"
local count="$2"
local state_file
state_file=$(get_service_state_file "$service_name")
# 确保状态目录存在
mkdir -p "$STATE_DIR"
cat > "$state_file" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
# 检查服务是否正常(通过 API 请求)
is_service_healthy() {
local service_name="$1"
# 使用 curl 发送 POST 请求
# -k: 忽略 SSL 证书检查
# -s: 静默模式
......@@ -73,7 +189,7 @@ start_service() {
# 切换到服务目录并执行启动脚本
(
cd "$dir_path" || { log_message "错误: 无法切换到目录 '$dir_path'"; return 1; }
# 检查启动脚本是否存在且可执行
if ! is_script_executable "$script_path"; then
log_message "错误: 启动脚本 '$script_path' 不存在或不可执行。"
......@@ -82,11 +198,11 @@ start_service() {
# 执行启动脚本
nohup "./$(basename "$script_path")" > startup.log 2>&1 &
# 等待一段时间,让服务有足够时间启动并响应 API
log_message "等待服务启动 (300秒)..."
sleep 300
# 再次检查服务是否正常
if is_service_healthy "$service_name"; then
log_message "服务 '$service_name' 启动并验证成功。"
......@@ -98,7 +214,15 @@ start_service() {
)
}
# --- 主逻辑 ---
#===============================================================================
# 主逻辑
#===============================================================================
# 获取锁(防止并发执行)
acquire_lock
# 执行日志轮转
rotate_logs
# 初始化日志
log_message "=== 服务监控脚本开始执行 (API 检查模式) ==="
......@@ -114,6 +238,10 @@ for service_info in "${SERVICES[@]}"; do
log_message "检查服务: $service_name"
# 读取当前失败次数
failure_count=$(read_service_state "$service_name")
log_message "读取失败计数器: 当前失败次数 = $failure_count"
# 检查目录是否存在
if ! is_directory_exists "$dir_path"; then
log_message "跳过服务 '$service_name': 目录 '$dir_path' 不存在。"
......@@ -122,11 +250,28 @@ for service_info in "${SERVICES[@]}"; do
# 检查服务是否正常
if is_service_healthy "$service_name"; then
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_service_state "$service_name" 0
log_message "检查成功,重置失败计数器为 0"
fi
log_message "服务 '$service_name' 运行正常 (HTTP 200)。"
else
log_message "服务 '$service_name' 响应异常或未运行。"
# 尝试启动服务
start_service "$service_name" "$dir_path" "$script_path"
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_service_state "$service_name" "$failure_count"
log_message "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log_message "错误: 连续失败达到阈值($MAX_FAILURES次),触发服务启动"
write_service_state "$service_name" 0
# 尝试启动服务
start_service "$service_name" "$dir_path" "$script_path"
else
log_message "警告: 未达到启动阈值,等待下次检查确认"
fi
fi
done
......
......@@ -3,15 +3,21 @@
#===============================================================================
# 脚本名称:monitor_inner_api_services.sh
# 功能描述:预定系统后端服务监测与自愈脚本
# 版本:V1.0
# 版本:V1.1
# 创建日期:2026-01-27
# 基于文档:_PRD_预定系统后端服务监测需求文档.md
# 更新日期:2026-03-30
# 基于文档:_PRD_预定系统后端服务监测需求文档.md (V1.1)
#
# 监测对象:
# 1. 平台API可用性 (https://SERVER_IP/api/system/getVerifyCode)
# 2. 容器ujava*内服务进程 (meeting2.0),支持ujava2/ujava3/ujava6等模糊匹配
# 3. 宿主机6060端口malan服务
#
# 恢复策略:
# 1. 连续失败阈值判断(默认3次)
# 2. 每个监测对象独立维护失败计数器
# 3. 达到阈值时触发恢复操作
#
# 使用方法:
# chmod +x monitor_inner_api_services.sh
# ./monitor_inner_api_services.sh
......@@ -39,6 +45,13 @@ MALAN_PORT="6060"
# 日志配置
LOG_FILE="/var/log/scripts/monitor-inner-api-services.log"
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=30 # 日志保留天数
PID_FILE="/var/log/scripts/.inner_api_monitor.pid"
# 状态文件配置
STATE_DIR="/var/log/scripts/inner_api_state"
MAX_FAILURES=3
# 超时配置
START_TIMEOUT=30
......@@ -62,6 +75,55 @@ START_SCRIPT="run.sh"
# 工具函数段
#-------------------------------------------------------------------------------
# 获取锁函数(防止并发执行)
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 "$(timestamp) - 警告: 上一次脚本执行仍在运行 (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
}
# 释放锁函数(可选,trap 会自动处理)
release_lock() {
rm -f "$PID_FILE"
}
# 日志轮转函数
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 "$(timestamp) - 日志文件超过 5MB,已自动轮转。" | tee -a "$LOG_FILE"
fi
fi
# 清理超过保留天数的旧日志文件
find "$(dirname "$LOG_FILE")" -name "monitor-inner-api-services.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# 获取格式化时间戳
# 返回格式:YYYY-MM-DD HH:MM:SS
timestamp() {
......@@ -76,6 +138,44 @@ log() {
echo "$(timestamp) $message" | tee -a "$LOG_FILE"
}
# 获取对象状态文件路径
get_object_state_file() {
local object_key="$1"
# object_key: platform_api, meeting2.0, malan
local safe_name=$(echo "$object_key" | tr -cd '[:alnum:]_-')
echo "${STATE_DIR}/${safe_name}.state"
}
# 读取对象状态文件
read_object_state() {
local object_key="$1"
local state_file
state_file=$(get_object_state_file "$object_key")
if [ -f "$state_file" ]; then
source "$state_file"
echo "${FAILURE_COUNT:-0}"
else
echo "0"
fi
}
# 写入对象状态文件
write_object_state() {
local object_key="$1"
local count="$2"
local state_file
state_file=$(get_object_state_file "$object_key")
# 确保状态目录存在
mkdir -p "$STATE_DIR"
cat > "$state_file" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
# 动态查找匹配的容器
# 支持模糊匹配:ujava2、ujava3、ujava6等
# 返回:找到的容器名称,未找到返回空
......@@ -160,16 +260,13 @@ check_platform_api() {
fi
# 检查响应内容是否包含常见的成功标识
# 尝试多个可能的成功标识,增加兼容性
if echo "$response_body" | grep -qE "(操作成功|success|成功|verify|验证码|OK|ok)"; then
log "[SUCCESS] 平台API检查通过 (响应包含成功关键字)"
return 0
fi
# 如果没有找到明显的成功标识,进一步检查响应是否为有效的JSON格式
# 如果是有效JSON且包含code字段,则检查code值
if echo "$response_body" | python3 -m json.tool &>/dev/null; then
# 尝试提取code字段
local code_value
code_value=$(echo "$response_body" | python3 -c "import sys, json; print(json.load(sys.stdin).get('code', 'missing'))" 2>/dev/null)
if [ "$code_value" = "200" ] || [ "$code_value" = "0" ]; then
......@@ -179,18 +276,10 @@ check_platform_api() {
fi
log "[WARNING] API响应格式不符合预期,但HTTP状态为200"
log "[DEBUG] API响应内容: $response_body"
# 添加一个配置选项,决定在这种情况下是否认为检查通过
# 默认为true,即如果HTTP状态码为200则认为服务正常
local treat_http_200_as_success="true"
if [ "$treat_http_200_as_success" = "true" ]; then
log "[SUCCESS] 平台API检查通过 (HTTP状态码200)"
return 0
fi
log "[ERROR] API检查失败,响应内容不符合预期格式"
return 1
# 默认HTTP状态码200认为服务正常
log "[SUCCESS] 平台API检查通过 (HTTP状态码200)"
return 0
}
# 检查6060端口是否监听
......@@ -225,37 +314,25 @@ check_port_6060() {
}
# 获取容器内服务进程PID
# 参数:$1 - jar包标识
# 返回:PID字符串(失败返回空)
get_service_pid() {
local jar_identifier="$1"
local pid
# 在容器内执行ps命令查找进程
pid=$(docker exec "$CONTAINER_NAME" ps aux 2>/dev/null | grep "$jar_identifier" | grep -v grep | awk '{print $2}' | head -n1)
echo "$pid"
}
# 检查服务运行状态
# 参数:$1 - 服务key
# 输出:状态信息到stdout(不含时间戳)
# 返回:0 - 服务运行中,1 - 服务未运行
check_service_status() {
local service_key="$1"
local jar_identifier="${SERVICES[$service_key]}"
# 获取PID
local pid
pid=$(get_service_pid "$jar_identifier")
# PID为空,服务未运行
if [ -z "$pid" ]; then
echo "[STOPPED] $service_key (未找到进程)"
return 1
fi
# 验证PID是否有效
if ! docker exec "$CONTAINER_NAME" ps -p "$pid" &> /dev/null; then
echo "[STOPPED] $service_key (PID: $pid 无效)"
return 1
......@@ -270,45 +347,25 @@ check_service_status() {
#-------------------------------------------------------------------------------
# 启动单个服务
# 参数:$1 - 服务key
# 返回:0 - 启动成功,1 - 启动失败
start_service() {
local service_key="$1"
local service_path="${SERVICE_PATHS[$service_key]}"
local start_script_path="${service_path}/${START_SCRIPT}"
log "[INFO] 正在启动服务: $service_key"
if [ "$DEBUG_MODE" = "true" ]; then
log "[DEBUG] 服务路径: $service_path"
log "[DEBUG] 启动脚本: $start_script_path"
log "[DEBUG] jar标识: ${SERVICES[$service_key]}"
fi
# 先检查服务目录是否存在
if ! docker exec "$CONTAINER_NAME" test -d "$service_path"; then
log "[ERROR] 服务目录不存在: $service_path"
log "[ERROR] 请检查脚本中 SERVICE_PATHS[$service_key] 的配置是否正确"
log "[ERROR] 排查命令: docker exec $CONTAINER_NAME ls -la /var/www/java/"
return 1
fi
# 检查run.sh是否存在
if ! docker exec "$CONTAINER_NAME" test -f "$start_script_path"; then
log "[ERROR] 启动脚本不存在: $start_script_path"
log "[ERROR] 目录内容如下:"
docker exec "$CONTAINER_NAME" ls -la "$service_path" 2>&1 | while read line; do
log "[ERROR] $line"
done
return 1
fi
# 在容器内执行启动脚本
if [ "$DEBUG_MODE" = "true" ]; then
log "[DEBUG] 执行命令: cd $service_path && ./$START_SCRIPT"
fi
docker exec "$CONTAINER_NAME" bash -c "cd '$service_path' && ./'$START_SCRIPT'" &> /dev/null
# 等待服务启动
local wait_time=0
while [ $wait_time -lt $START_TIMEOUT ]; do
local jar_identifier="${SERVICES[$service_key]}"
......@@ -329,16 +386,13 @@ start_service() {
}
# 强制重启关键服务
# 参数:$1 - 服务标识(如"inner")
restart_critical_services() {
local critical_service="$1"
log "[WARNING] ===== 开始强制重启关键服务: $critical_service ====="
# 查找meeting2.0服务并重启
for service_key in "${!SERVICES[@]}"; do
log "[INFO] 强制重启: $service_key"
# 先尝试停止进程
local jar_identifier="${SERVICES[$service_key]}"
local pid
pid=$(get_service_pid "$jar_identifier")
......@@ -348,7 +402,6 @@ restart_critical_services() {
sleep 2
fi
# 启动服务
if start_service "$service_key"; then
log "[SUCCESS] $service_key 重启成功"
else
......@@ -360,59 +413,20 @@ restart_critical_services() {
sleep "$RESTART_WAIT"
}
# 批量检查所有服务
# 返回:停止的服务数量
check_services() {
local problems=0
local status_output
for service_key in "${!SERVICES[@]}"; do
status_output=$(check_service_status "$service_key")
log "$status_output" # 记录服务状态日志
if [[ "$status_output" =~ \[STOPPED\] ]]; then
problems=$((problems + 1))
fi
done
echo "$problems"
}
# 重启问题服务
# 参数:$1 - 问题服务数量
restart_problem_services() {
local problems="$1"
log "[WARNING] 检测到 $problems 个服务停止,将重新启动"
for service_key in "${!SERVICES[@]}"; do
local jar_identifier="${SERVICES[$service_key]}"
local pid
pid=$(get_service_pid "$jar_identifier")
# 如果服务未运行,则启动
if [ -z "$pid" ]; then
start_service "$service_key"
fi
done
}
# 启动malan服务
# 返回:0 - 启动成功,1 - 启动失败
start_malan_service() {
log "[INFO] 正在启动malan服务..."
# 检查malan目录是否存在
if [ ! -d "$MALAN_DIR" ]; then
log "[ERROR] malan目录不存在: $MALAN_DIR"
return 1
fi
# 检查malan可执行文件是否存在
if [ ! -f "${MALAN_DIR}/malan" ]; then
log "[ERROR] malan可执行文件不存在: ${MALAN_DIR}/malan"
return 1
fi
# 启动malan
cd "$MALAN_DIR" || {
log "[ERROR] 无法进入目录: $MALAN_DIR"
return 1
......@@ -420,7 +434,6 @@ start_malan_service() {
bash -c "source /etc/profile && ./malan &" &> /dev/null
# 等待端口就绪
local wait_time=0
while [ $wait_time -lt $START_TIMEOUT ]; do
if check_port_6060 &> /dev/null; then
......@@ -442,6 +455,12 @@ start_malan_service() {
# 主函数
main() {
# 获取锁(防止并发执行)
acquire_lock
# 执行日志轮转
rotate_logs
# 确保日志目录存在
local log_dir
log_dir=$(dirname "$LOG_FILE")
......@@ -452,6 +471,9 @@ main() {
}
fi
# 确保状态目录存在
mkdir -p "$STATE_DIR" 2>/dev/null
log "[START] ===== 服务监测开始 ====="
# 自动获取服务器IP
......@@ -481,6 +503,8 @@ main() {
log "[DEBUG] API_URL: $API_URL"
log "[DEBUG] MALAN_DIR: $MALAN_DIR"
log "[DEBUG] MALAN_PORT: $MALAN_PORT"
log "[DEBUG] STATE_DIR: $STATE_DIR"
log "[DEBUG] MAX_FAILURES: $MAX_FAILURES"
log "[DEBUG] START_TIMEOUT: ${START_TIMEOUT}秒"
log "[DEBUG] RESTART_WAIT: ${RESTART_WAIT}秒"
for svc in "${!SERVICES[@]}"; do
......@@ -489,22 +513,83 @@ main() {
log "[DEBUG] =============================="
fi
# 步骤1:平台API检查
# 步骤1:平台API检查(带失败计数机制)
local api_failure_count
api_failure_count=$(read_object_state "platform_api")
log "[INFO] 读取失败计数器[platform_api]: 当前失败次数 = $api_failure_count"
if ! check_platform_api; then
restart_critical_services "inner"
api_failure_count=$((api_failure_count + 1))
write_object_state "platform_api" "$api_failure_count"
log "[WARNING] 失败计数器更新: $((api_failure_count - 1)) -> $api_failure_count (阈值: $MAX_FAILURES)"
if [ "$api_failure_count" -ge "$MAX_FAILURES" ]; then
log "[ERROR] 连续失败达到阈值($MAX_FAILURES次),触发内部服务重启"
write_object_state "platform_api" 0
restart_critical_services "inner"
else
log "[WARNING] 未达到重启阈值,等待下次检查确认"
fi
else
if [ "$api_failure_count" -gt 0 ]; then
write_object_state "platform_api" 0
log "[SUCCESS] 平台API检查通过,重置失败计数器为 0"
fi
fi
# 步骤2:容器内服务检查
# 步骤2:容器内服务检查(带失败计数机制)
log "[INFO] ===== $(timestamp) 服务状态检查 ====="
local problems
problems=$(check_services)
if [ "$problems" -gt 0 ]; then
restart_problem_services "$problems"
fi
for service_key in "${!SERVICES[@]}"; do
local service_failure_count
service_failure_count=$(read_object_state "$service_key")
log "[INFO] 读取失败计数器[$service_key]: 当前失败次数 = $service_failure_count"
local status_output
status_output=$(check_service_status "$service_key")
log "$status_output"
if [[ "$status_output" =~ \[STOPPED\] ]]; then
service_failure_count=$((service_failure_count + 1))
write_object_state "$service_key" "$service_failure_count"
log "[WARNING] 失败计数器更新: $((service_failure_count - 1)) -> $service_failure_count (阈值: $MAX_FAILURES)"
if [ "$service_failure_count" -ge "$MAX_FAILURES" ]; then
log "[ERROR] 连续失败达到阈值($MAX_FAILURES次),触发服务启动"
write_object_state "$service_key" 0
start_service "$service_key"
else
log "[WARNING] 未达到启动阈值,等待下次检查确认"
fi
else
if [ "$service_failure_count" -gt 0 ]; then
write_object_state "$service_key" 0
log "[SUCCESS] 服务 '$service_key' 正常,重置失败计数器为 0"
fi
fi
done
# 步骤3:6060端口检查(带失败计数机制)
local malan_failure_count
malan_failure_count=$(read_object_state "malan")
log "[INFO] 读取失败计数器[malan]: 当前失败次数 = $malan_failure_count"
# 步骤3:6060端口检查
if ! check_port_6060; then
start_malan_service
malan_failure_count=$((malan_failure_count + 1))
write_object_state "malan" "$malan_failure_count"
log "[WARNING] 失败计数器更新: $((malan_failure_count - 1)) -> $malan_failure_count (阈值: $MAX_FAILURES)"
if [ "$malan_failure_count" -ge "$MAX_FAILURES" ]; then
log "[ERROR] 连续失败达到阈值($MAX_FAILURES次),触发malan服务启动"
write_object_state "malan" 0
start_malan_service
else
log "[WARNING] 未达到启动阈值,等待下次检查确认"
fi
else
if [ "$malan_failure_count" -gt 0 ]; then
write_object_state "malan" 0
log "[SUCCESS] 6060端口已监听,重置失败计数器为 0"
fi
fi
log "[SUMMARY] ===== $(timestamp) 操作完成 ====="
......
......@@ -3,9 +3,10 @@
#===============================================================================
# 脚本名称:monitor_mysql_service.sh
# 功能描述:MySQL服务监测与自愈脚本
# 版本:V1.0
# 版本:V1.1
# 创建日期:2026-01-27
# 基于文档:_PRD_预定系统_MySQL服务监控需求文档.md
# 更新日期:2026-03-30
# 基于文档:_PRD_预定系统_MySQL服务监控需求文档.md (V1.1)
#
# 监测对象:
# 1. umysql容器运行状态
......@@ -14,12 +15,13 @@
# 4. 数据库基本功能验证
#
# 恢复策略:
# 1. 检查容器是否运行
# 2. 获取MySQL root密码
# 3. 测试mysqladmin ping连接
# 4. 验证SELECT 1查询执行
# 5. 异常时自动重启容器
# 6. 重启后验证服务恢复
# 1. 连续失败阈值判断(默认3次)
# 2. 检查容器是否运行
# 3. 获取MySQL root密码
# 4. 测试mysqladmin ping连接
# 5. 验证SELECT 1查询执行
# 6. 异常时自动重启容器
# 7. 重启后验证服务恢复
#
# 使用方法:
# chmod +x monitor_mysql_service.sh
......@@ -31,7 +33,63 @@
# 宿主机上的脚本:检查 MySQL,如果容器未运行则重启 umysql 容器
LOG_FILE="/var/log/scripts/monitor_mysql_service.log"
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=30 # 日志保留天数
PID_FILE="/var/log/scripts/.mysql_monitor.pid"
# 状态文件配置
STATE_FILE="/var/log/scripts/monitor_mysql_service.state"
MAX_FAILURES=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 "monitor_mysql_service.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# 日志记录函数
log() {
local message
message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
......@@ -40,24 +98,44 @@ log() {
echo "$message" >> "$LOG_FILE"
}
# 读取状态文件
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
cat > "$STATE_FILE" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
# 获取MySQL密码
get_mysql_password() {
docker exec umysql printenv MYSQL_ROOT_PASSWORD 2>/dev/null
}
# 检查MySQL服务状态
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查询"
......@@ -73,6 +151,7 @@ check_mysql() {
fi
}
# 重启MySQL容器
restart_mysql_container() {
log "MySQL 未运行。正在尝试重启容器 'umysql'..."
......@@ -80,11 +159,11 @@ restart_mysql_container() {
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
......@@ -92,22 +171,22 @@ restart_mysql_container() {
sleep 1
done
echo "" >> "$LOG_FILE"
# 检查启动后容器是否仍在运行
if docker ps --format '{{.Names}}' | grep -Fxq "umysql"; then
log "信息: MySQL 容器现在正在运行。"
# 等待一段时间后检查服务状态
log "信息: 等待 20 秒后检查服务状态..."
sleep 20
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 服务状态正常。"
......@@ -135,19 +214,52 @@ restart_mysql_container() {
fi
}
#===============================================================================
# 主逻辑
#===============================================================================
# 获取锁(防止并发执行)
acquire_lock
# 执行日志轮转
rotate_logs
log "开始检查 MySQL 服务状态..."
# 读取当前失败次数
failure_count=$(read_state_file)
log "读取失败计数器: 当前失败次数 = $failure_count"
# 检查MySQL服务
if check_mysql; then
log "MySQL 正在运行且状态正常。"
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_state_file 0
log "检查成功,重置失败计数器为 0"
fi
log "MySQL 服务状态正常"
else
log "MySQL 无响应或容器未运行。"
restart_mysql_container
# 检查重启后是否正常工作
if check_mysql; then
log "MySQL 已成功重启,现在状态正常。"
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_state_file "$failure_count"
log "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "错误: 连续失败达到阈值($MAX_FAILURES次),触发容器重启"
write_state_file 0
# 执行重启流程
restart_mysql_container
# 验证重启结果
if check_mysql; then
log "MySQL 服务已成功恢复"
else
log "错误: 重启后MySQL服务仍不可用"
log "信息: 请使用以下命令检查容器日志: docker logs umysql"
fi
else
log "错误: 重启尝试后 MySQL 仍无响应。"
log "信息: 请使用以下命令检查容器日志: docker logs umysql"
log "警告: 未达到重启阈值,等待下次检查确认"
fi
fi
\ No newline at end of file
fi
......@@ -3,9 +3,10 @@
#===============================================================================
# 脚本名称:monitor_redis_service.sh
# 功能描述:Redis服务监测与自愈脚本
# 版本:V1.0
# 版本:V1.1
# 创建日期:2026-01-27
# 基于文档:_PRD_预定系统_Redis服务监控需求文档.md
# 更新日期:2026-03-30
# 基于文档:_PRD_预定系统_Redis服务监控需求文档.md (V1.1)
#
# 监测对象:
# 1. uredis容器运行状态
......@@ -14,12 +15,13 @@
# 4. PING命令响应验证
#
# 恢复策略:
# 1. 检查容器是否运行
# 2. 测试无密码PING连接
# 3. 如需认证则使用预设密码测试
# 4. 异常时自动重启容器
# 5. 重启失败时清理数据目录重试
# 6. 重启后验证服务恢复
# 1. 连续失败阈值判断(默认3次)
# 2. 检查容器是否运行
# 3. 测试无密码PING连接
# 4. 如需认证则使用预设密码测试
# 5. 异常时自动重启容器
# 6. 重启失败时清理数据目录重试
# 7. 重启后验证服务恢复
#
# 使用方法:
# chmod +x monitor_redis_service.sh
......@@ -31,7 +33,66 @@
# 宿主机上的脚本:检查 Redis,如果容器未运行则重启 uredis 容器
LOG_FILE="/var/log/scripts/monitor_redis_service.log"
MAX_LOG_SIZE=$((5*1024*1024)) # 5MB 日志大小限制
LOG_RETENTION_DAYS=30 # 日志保留天数
PID_FILE="/var/log/scripts/.redis_monitor.pid"
# 状态文件配置
STATE_FILE="/var/log/scripts/monitor_redis_service.state"
MAX_FAILURES=3
# 直接在脚本中定义 Redis 密码
REDIS_PASSWORD="dNrprU&2S"
#===============================================================================
# 函数定义
#===============================================================================
# 获取锁函数(防止并发执行)
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 "monitor_redis_service.log.*" -mtime +$LOG_RETENTION_DAYS -exec rm -f {} \; 2>/dev/null
}
# 日志记录函数
log() {
local message
message="$(date '+%Y-%m-%d %H:%M:%S') - $1"
......@@ -40,28 +101,41 @@ log() {
echo "$message" >> "$LOG_FILE"
}
# 直接在脚本中定义 Redis 密码
REDIS_PASSWORD="dNrprU&2S"
# 读取状态文件
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
cat > "$STATE_FILE" << EOF
FAILURE_COUNT=$count
LAST_CHECK_TIME=$(date +%s)
EOF
}
# 检查Redis服务状态
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 服务状态正常"
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"
......@@ -83,6 +157,7 @@ check_redis() {
fi
}
# 重启Redis容器
restart_redis_container() {
log "Redis 未运行。正在尝试重启容器 'uredis'..."
......@@ -90,11 +165,11 @@ restart_redis_container() {
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
......@@ -102,15 +177,15 @@ restart_redis_container() {
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
# 不需要认证
......@@ -156,26 +231,26 @@ restart_redis_container() {
fi
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
......@@ -183,15 +258,15 @@ restart_redis_container() {
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
# 不需要认证
......@@ -247,19 +322,52 @@ restart_redis_container() {
fi
}
#===============================================================================
# 主逻辑
#===============================================================================
# 获取锁(防止并发执行)
acquire_lock
# 执行日志轮转
rotate_logs
log "开始检查 Redis 服务状态..."
# 读取当前失败次数
failure_count=$(read_state_file)
log "读取失败计数器: 当前失败次数 = $failure_count"
# 检查Redis服务
if check_redis; then
log "Redis 正在运行且状态正常。"
# 检查成功,清零计数器
if [ "$failure_count" -gt 0 ]; then
write_state_file 0
log "检查成功,重置失败计数器为 0"
fi
log "Redis 服务状态正常"
else
log "Redis 无响应或容器未运行。"
restart_redis_container
# 检查重启后是否正常工作
if check_redis; then
log "Redis 已成功重启,现在状态正常。"
# 检查失败,累加计数器
failure_count=$((failure_count + 1))
write_state_file "$failure_count"
log "失败计数器更新: $((failure_count - 1)) -> $failure_count (阈值: $MAX_FAILURES)"
# 判断是否达到阈值
if [ "$failure_count" -ge "$MAX_FAILURES" ]; then
log "错误: 连续失败达到阈值($MAX_FAILURES次),触发容器重启"
write_state_file 0
# 执行重启流程
restart_redis_container
# 验证重启结果
if check_redis; then
log "Redis 服务已成功恢复"
else
log "错误: 重启后Redis服务仍不可用"
log "信息: 请使用以下命令检查容器日志: docker logs uredis"
fi
else
log "错误: 重启尝试后 Redis 仍无响应。"
log "信息: 请使用以下命令检查容器日志: docker logs uredis"
log "警告: 未达到重启阈值,等待下次检查确认"
fi
fi
\ No newline at end of file
fi
# 预定系统定时任务配置说明(x86 架构)
> 版本:V1.0
> 更新日期:2026-03-17
> 适用范围:预定系统 x86 架构全套定时监控与维护脚本
> 部署路径:`/opt/scripts/`
> 日志路径:`/var/log/scripts/`
> 版本:V1.1
> 更新日期:2026-03-30
> 适用范围:预定系统 x86 架构全套定时监控与维护脚本
> 部署路径:`/opt/scripts/`
> 日志路径:`/var/log/scripts/`
> 状态文件路径:`/var/log/scripts/*.state` 或 `/var/log/scripts/*_state/`
---
## 📁 脚本文件清单
### 一、服务监控类脚本(每 10 分钟执行)
### 一、服务监控类脚本(每 5 分钟执行)
| 序号 | 脚本名称 | 监控对象 | 日志文件 | 功能描述 |
|-----|---------|---------|---------|---------|
| 1 | `monitor_emqx_service.sh` | EMQX 消息队列 | `/var/log/scripts/monitor_emqx_service.log` | 容器状态 + 进程检查 + 服务验证(emqx_ctl status) |
| 2 | `monitor_mysql_service.sh` | MySQL数据库 | `/var/log/scripts/monitor_mysql_service.log` | 容器状态 + 连接测试 + SQL验证(mysqladmin ping) |
| 3 | `monitor_redis_service.sh` | Redis缓存 | `/var/log/scripts/monitor_redis_service.log` | 容器状态 + 认证识别 + PING验证 |
| 4 | `monitor_external_api_services_v2.sh` | 外部API服务 | `/var/log/scripts/monitor_external_api_services_v2.log` | 进程监控 + 目录验证 + 自动启动 |
| 5 | `monitor_inner_api_services.sh` | 内部API服务 | `/var/log/scripts/monitor_inner_api_services.log` | API检查 + 容器服务 + 端口监控 |
| 序号 | 脚本名称 | 版本 | 监控对象 | 日志文件 | 状态文件 | 功能描述 |
|-----|---------|------|---------|---------|---------|---------|
| 1 | `monitor_emqx_service.sh` | V2.1 | EMQX 消息队列 | `/var/log/scripts/monitor_emqx_service.log` | `/var/log/scripts/.emqx_monitor_state` | 容器状态 + 进程检查 + 服务验证 + 连续失败阈值 + 重启冷却 |
| 2 | `monitor_mysql_service.sh` | V1.1 | MySQL数据库 | `/var/log/scripts/monitor_mysql_service.log` | `/var/log/scripts/monitor_mysql_service.state` | 容器状态 + 连接测试 + SQL验证 + 连续失败阈值 |
| 3 | `monitor_redis_service.sh` | V1.1 | Redis缓存 | `/var/log/scripts/monitor_redis_service.log` | `/var/log/scripts/monitor_redis_service.state` | 容器状态 + 认证识别 + PING验证 + 连续失败阈值 |
| 4 | `monitor_external_api_services_v2.sh` | V1.1 | 外部API服务 | `/var/log/scripts/monitor_external_api_services_v2.log` | `/var/log/scripts/external_api_state/` | 进程监控 + 目录验证 + 自动启动 + 连续失败阈值 |
| 5 | `monitor_inner_api_services.sh` | V1.1 | 内部API服务 | `/var/log/scripts/monitor_inner_api_services.log` | `/var/log/scripts/inner_api_state/` | API检查 + 容器服务 + 端口监控 + 连续失败阈值 |
### 二、数据备份类脚本(每日执行)
......@@ -54,32 +55,73 @@ EMQX_CONTAINER="uemqx"
# 日志配置
LOG_DIR="/var/log/scripts/" # 统一日志目录
LOG_FORMAT="[YYYY-MM-DD HH:MM:SS] 日志内容"
LOG_FORMAT="[YYYY-MM-DD HH:MM:SS] - 日志内容"
MAX_LOG_SIZE="5MB" # 日志轮转阈值
LOG_RETENTION_DAYS=7 # 日志保留天数
# 监控配置(新增)
STATE_DIR="/var/log/scripts" # 状态文件目录
MAX_FAILURES=3 # 最大连续失败次数阈值
```
### 2. 监控配置
#### 2.1 EMQX服务监控
#### 2.1 连续失败阈值机制(统一机制)
```bash
# 核心参数
MAX_FAILURES=3 # 最大连续失败次数,达到才触发重启/启动
STATE_FILE_PREFIX=".state" # 状态文件前缀
STATE_DIR="_state/" # 多服务状态目录后缀
# 工作原理
# 1. 单次检查失败时,仅累加计数器,不立即重启
# 2. 连续失败达到 MAX_FAILURES 次时,才触发恢复操作
# 3. 检查成功时,自动清零计数器
# 4. 状态持久化保存,进程重启不丢失
```
#### 2.2 EMQX服务监控
```bash
TARGET_KEY="ubains-INFO-AND-ERROR" # 日志关键字
CHECK_INTERVAL="*/10 * * * *" # 每 10 分钟
CHECK_INTERVAL="*/5 * * * *" # 每 5 分钟(更新)
HEALTH_CHECK="emqx_ctl status" # 健康检查命令
STATE_FILE=".emqx_monitor_state" # 状态文件
COOLDOWN_PERIOD=1800 # 重启冷却时间(30分钟)
```
#### 2.2 MySQL服务监控
#### 2.3 MySQL服务监控
```bash
DB_USER="root" # 数据库用户
CHECK_METHOD="mysqladmin ping" # 连接检查
SQL_TEST="SELECT 1" # SQL 测试语句
STATE_FILE="monitor_mysql_service.state" # 状态文件(新增)
```
#### 2.3 Redis服务监控
#### 2.4 Redis服务监控
```bash
AUTH_ENABLED=true # 是否启用认证
CHECK_METHOD="redis-cli ping" # PING 检查
EXPECTED_RESPONSE="PONG" # 期望响应
STATE_FILE="monitor_redis_service.state" # 状态文件(新增)
REDIS_PASSWORD="dNrprU&2S" # Redis访问密码
```
#### 2.5 外部API服务监控
```bash
API_URL="https://127.0.0.1/exapi/system/v2/login" # API检查地址
STATE_DIR="external_api_state" # 状态文件目录(新增)
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"
)
```
#### 2.6 内部API服务监控
```bash
API_URL_TEMPLATE="https://{SERVER_IP}/api/system/getVerifyCode" # 平台API
STATE_DIR="inner_api_state" # 状态文件目录(新增)
MALAN_PORT="6060" # Malan服务端口
SERVICES["meeting2.0"]="ubains-meeting-inner-api" # 容器内服务
```
### 3. 备份配置
......@@ -133,12 +175,12 @@ crontab -e
# 预定系统定时任务配置
# ============================================
# ========== 服务监控类(每 10 分钟) ==========
*/10 * * * * /opt/scripts/monitor_emqx_service.sh
*/10 * * * * /opt/scripts/monitor_mysql_service.sh
*/10 * * * * /opt/scripts/monitor_redis_service.sh
*/10 * * * * /opt/scripts/monitor_external_api_services_v2.sh
*/10 * * * * /opt/scripts/monitor_inner_api_services.sh
# ========== 服务监控类(每 5 分钟,错峰执行) ==========
*/5 * * * * /opt/scripts/monitor_emqx_service.sh
*/5 * * * * sleep 60 && /opt/scripts/monitor_mysql_service.sh
*/5 * * * * sleep 120 && /opt/scripts/monitor_redis_service.sh
*/5 * * * * sleep 180 && /opt/scripts/monitor_external_api_services_v2.sh
*/5 * * * * sleep 240 && /opt/scripts/monitor_inner_api_services.sh
# ========== 数据备份类(每日凌晨) ==========
0 1 * * * /opt/scripts/backup_mysql_databases.sh
......@@ -151,19 +193,31 @@ crontab -e
# ============================================
```
### 错峰执行说明
为避免多个监控脚本同时执行造成资源竞争,各脚本间隔1分钟执行:
| 脚本 | 执行偏移 | 实际执行时间 |
|------|---------|-------------|
| monitor_emqx_service.sh | +0秒 | 0, 5, 10, 15... |
| monitor_mysql_service.sh | +60秒 | 1, 6, 11, 16... |
| monitor_redis_service.sh | +120秒 | 2, 7, 12, 17... |
| monitor_external_api_services_v2.sh | +180秒 | 3, 8, 13, 18... |
| monitor_inner_api_services.sh | +240秒 | 4, 9, 14, 19... |
### 执行时间安排图
```
时间轴 (24 小时)
├─ 00:00 - 01:00 → 监控任务(每 10 分钟
├─ 00:00 - 01:00 → 监控任务(每 5 分钟,错峰执行
├─ 01:00 - 01:30 → MySQL数据库备份 ★
├─ 01:30 - 02:00 → 监控任务(每 10 分钟
├─ 01:30 - 02:00 → 监控任务(每 5 分钟,错峰执行
├─ 02:00 - 02:30 → MySQL日志备份 ★
├─ 02:30 - 03:00 → 监控任务(每 10 分钟
├─ 02:30 - 03:00 → 监控任务(每 5 分钟,错峰执行
├─ 03:00 - 03:30 → Nginx日志备份 ★
├─ 03:30 - 04:00 → 监控任务(每 10 分钟
├─ 03:30 - 04:00 → 监控任务(每 5 分钟,错峰执行
├─ 04:00 - 04:30 → 已删除文件清理 ★
└─ 04:30 - 24:00 → 监控任务(每 10 分钟
└─ 04:30 - 24:00 → 监控任务(每 5 分钟,错峰执行
★ = 重量级任务,错峰执行
```
......@@ -193,6 +247,11 @@ sudo chmod +x /opt/scripts/*.sh
# 设置日志目录权限
sudo mkdir -p /var/log/scripts
sudo chmod 755 /var/log/scripts
# 创建状态文件目录(新增)
sudo mkdir -p /var/log/scripts/external_api_state
sudo mkdir -p /var/log/scripts/inner_api_state
sudo chmod 755 /var/log/scripts/*_state
```
### 3. 依赖检查
......@@ -241,10 +300,32 @@ sudo systemctl status cron
tail -f /var/log/scripts/*.log
# 查看错误日志
grep "❌\|ERROR\|错误" /var/log/scripts/*.log
grep "❌\|错误\|ERROR" /var/log/scripts/*.log
# 统计异常发生频率
grep "警告\|错误\|WARNING\|ERROR" /var/log/scripts/*.log | wc -l
# 查看失败计数变化趋势(新增)
grep "失败计数器更新" /var/log/scripts/*.log
# 统计今日日志量
wc -l /var/log/scripts/*.log
# 统计触发恢复的次数(新增)
grep "连续失败达到阈值" /var/log/scripts/*.log | wc -l
```
### 2. 状态文件监控(新增)
```bash
# 查看所有状态文件
ls -la /var/log/scripts/*.state
ls -la /var/log/scripts/*_state/
# 查看特定服务失败次数
cat /var/log/scripts/monitor_mysql_service.state
cat /var/log/scripts/.emqx_monitor_state
# 查看多服务状态目录
ls -la /var/log/scripts/external_api_state/
ls -la /var/log/scripts/inner_api_state/
```
### 2. 执行状态检查
......@@ -344,6 +425,36 @@ find /opt/mysql -name "*.sql.gz" -mtime +30 -delete
RETENTION_DAYS=15 # 改为 15 天
```
#### 6. 状态文件异常(新增)
```bash
# 检查状态文件权限
ls -la /var/log/scripts/*.state
ls -la /var/log/scripts/*_state/
# 状态文件损坏时手动重置
echo "FAILURE_COUNT=0" > /var/log/scripts/monitor_mysql_service.state
echo "FAILURE_COUNT=0
LAST_RESTART_TIME=0" > /var/log/scripts/.emqx_monitor_state
# 删除状态文件让脚本自动重建
rm -f /var/log/scripts/*.state
rm -rf /var/log/scripts/*_state/
```
#### 7. 服务频繁重启(新增)
```bash
# 检查失败计数器值
cat /var/log/scripts/monitor_mysql_service.state
# 查看重启历史
grep "连续失败达到阈值" /var/log/scripts/*.log | tail -10
# 临时提高失败阈值(编辑脚本)
MAX_FAILURES=5 # 从3改为5,给予更多容忍度
```
---
## 📈 性能优化建议
......@@ -450,7 +561,21 @@ grep CRON /var/log/syslog | tail -20
# 5. 检查备份文件
ls -lh /opt/mysql/$(date +%Y%m%d)/
# 6. 紧急重启所有容器
# 6. 查看服务失败计数器(新增)
cat /var/log/scripts/monitor_mysql_service.state
cat /var/log/scripts/monitor_redis_service.state
cat /var/log/scripts/.emqx_monitor_state
ls -la /var/log/scripts/external_api_state/
ls -la /var/log/scripts/inner_api_state/
# 7. 手动重置失败计数器(新增)
echo "FAILURE_COUNT=0" > /var/log/scripts/monitor_mysql_service.state
echo "FAILURE_COUNT=0" > /var/log/scripts/monitor_redis_service.state
# 8. 查看重启历史(新增)
grep "连续失败达到阈值" /var/log/scripts/*.log | tail -10
# 9. 紧急重启所有容器
docker restart ujava2 umysql uredis uemqx
# ===================================
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论