提交 65e7d726 authored 作者: 陈泽健's avatar 陈泽健

feat(scripts): 优化定时任务脚本和监控脚本

- 支持多个定时任务配置,新增每天凌晨1点执行的服务健康检查任务
- 实现参数化控制功能,通过--enable/--disable参数控制任务启停
- 添加重试机制到服务监测脚本,避免网络波动导致错误判断
- 引入文件锁机制防止定时任务并发执行冲突
- 实现自动清理功能,脚本不存在时自动移除对应的crontab条目
- 添加详细日志记录和错误处理机制
- 支持参数冲突检测和默认向后兼容行为
上级 2a283bbe
......@@ -10,12 +10,31 @@
### 需求描述
#### 调用逻辑
- 当前是只配置了一个定时任务,需要修改为可配置多个定时任务。
- 不需要通过主脚本传参控制定时任务配置,主脚本只需要调用这个`auto_crontab_settings.sh`脚本,脚本内部实现多个定时任务配置。
- 支持通过参数控制每个定时任务的启用/禁用,便于部署阶段的不同选择。
- 主脚本调用方式:`./auto_crontab_settings.sh [参数]`
- 新增定时任务:
- 定时周期:每天凌晨01:00执行
- 定时任务执行脚本:`/data/services/scripts/check_server_health.sh`
- 需要先判断该路径是否存在脚本,若不存在则不进行配置,打印相关日志记录。
#### 参数控制设计
- **参数方式:** 使用 `--enable-xxx` / `--disable-xxx` 开关式参数
- **默认行为:** 不传参数时默认全部启用(向后兼容)
- **参数示例:**
```bash
# 默认全部启用
./auto_crontab_settings.sh
# 只启用 check_server_health
./auto_crontab_settings.sh --disable-ujava2
# 禁用 check_server_health
./auto_crontab_settings.sh --disable-check-health
# 显式启用指定任务
./auto_crontab_settings.sh --enable-ujava2 --enable-check-health
```
## 疑问与解答记录
### 疑问1:现有定时任务是否保留?
......@@ -38,6 +57,42 @@
- **问题:** 计划将现有硬编码的 `add_crontab_job()` 重构为通用任务添加函数(接受脚本路径、cron 表达式、日志路径作为参数),然后在脚本内部分别调用两次来配置两个任务。
- **解答:** 改为通用函数,后期还会有其他定时任务脚本增加,需要预留扩展能力。
### 疑问6:执行用户确认?
- **问题:** 现有任务 `*/3 * * * * ujava2-startup.sh` 没有显式指定执行用户。新增任务的执行用户应与现有任务保持一致?
- **解答:** 执行用户与现有任务保持一致。
### 疑问7:日志输出格式?
- **问题:** 需求提到"打印相关日志记录",但没有明确日志输出到哪里、格式是什么。
- **解答:** 无需打印日志,ujava2-startup.sh 脚本自身会打印。
### 疑问8:Crontab 注释标记?
- **问题:** 是否需要在 crontab 中为每个定时任务添加注释标记以便识别和管理?
- **解答:** 增加对应注释标记。
### 疑问9:重复运行保护?
- **问题:** 服务健康检查脚本如果上次执行未完成,是否需要防止重复运行?
- **解答:** 无需进行重复运行保护。
### 疑问10:脚本删除后的清理机制?
- **问题:** 如果 `check_server_health.sh` 最初存在并被配置,但后续被删除了,crontab 中的任务条目是否需要自动清理?
- **解答:** 选择自动清理方式,保持 crontab 与实际文件的一致性。脚本每次运行时检查所有配置的任务,若脚本不存在则移除对应 crontab 条目。
### 疑问11:是否需要参数化控制?
- **问题:** 主脚本调用 `auto_crontab_settings.sh` 时,是否需要通过参数控制每个定时任务的启用/禁用?
- **解答:** 需要参数化控制,以便在部署阶段进行不同选择。
### 疑问12:参数方式选择?
- **问题:** 参数化控制有环境变量、配置文件、命令行参数等多种方式,选择哪种?
- **解答:** 选择方式A,使用 `--enable-xxx / --disable-xxx` 开关式命令行参数。
### 疑问13:默认行为是什么?
- **问题:** 如果调用时不传任何参数,默认应该启用哪些任务?
- **解答:** 默认全部启用(向后兼容现有行为)。
### 疑问14:参数冲突如何处理?
- **问题:** 如果同时传入矛盾的参数(如 `--enable-xxx``--disable-xxx`),如何处理?
- **解答:** 如果重复传参(同时启用和禁用同一任务),则跳过该任务配置并打印警告日志。
## 规范文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 问题总结: `Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
......
# 服务监测脚本优化需求
## 代码路径
- 代码路径:[自动化部署脚本/x86架构/新统一平台/定时脚本/ujava2-startup.sh]
- 原始需求文档路径:[Docs/PRD/自动化部署脚本/新统一平台/需求文档/_PRD_新统一平台后端服务监测_需求文档.md]
## 功能需求
### 功能目标
**目标:** 针对服务监测逻辑中的`GET https://192.168.5.44/platform/api/code` 请求补充重试机制,避免因网络波动或临时性故障导致错误判断。
### 需求描述
-`GET https://192.168.5.44/platform/api/code` 逻辑中补充重试机制,避免出现错误判断。
- 在现有代码基础上补充重试逻辑。
#### 重试机制配置
| 配置项 | 值 | 说明 |
| --- | --- | --- |
| 最大重试次数 | 3次 | 包含首次请求共4次机会 |
| 单次请求超时 | 10秒 | 与原始需求保持一致 |
| 重试间隔 | 30秒 | 从上次请求失败返回后开始计算 |
| 请求命令 | `curl -k -m 10` | 保持原有参数 |
#### 重试触发条件
**触发重试的情况**(临时性故障):
- 网络层面错误:连接超时、DNS解析失败、连接被拒绝
- HTTP 5xx服务器错误(500-599)
- HTTP状态码为200但响应内容不符合预期(不包含"操作成功"或code:200)
**不触发重试的情况**(确定性失败):
- HTTP 4xx客户端错误(400-499),表示请求本身有问题,重试无意义
#### 重试逻辑流程
```
首次请求
失败且满足触发条件 → 等待30秒 → 第1次重试
↓ ↓
失败且满足触发条件 失败且满足触发条件
↓ ↓
等待30秒 → 第2次重试 等待30秒 → 第3次重试
↓ ↓
失败且满足触发条件 失败且满足触发条件
↓ ↓
等待30秒 → 第3次重试 触发强制重启关键服务
失败且满足触发条件 → 触发强制重启关键服务
```
#### 重试过程日志记录
每次重试必须记录以下信息:
**格式示例**
```bash
2026-01-27 10:00:00 [RETRY] 第1次重试 platform API检查失败: HTTP 500
2026-01-27 10:00:30 [RETRY] 第2次重试 platform API检查失败: 连接超时
2026-01-27 10:01:00 [RETRY] 第3次重试 platform API检查成功
2026-01-27 10:01:30 [RETRY] platform API检查重试3次后仍失败,触发强制重启关键服务
```
**日志标识说明**
- `[RETRY]` - 重试相关日志标识
- 必须包含重试次数(第N次重试)
- 必须包含失败原因(HTTP状态码、curl错误信息)
- 最终结果(成功/失败及后续动作)
#### 重试结果处理
**重试成功**(任意一次重试成功):
- 记录成功日志:`[RETRY] 第N次重试 platform API检查成功`
- 继续执行后续检查流程(容器内服务检查、extapi检查、6060端口检查)
**重试全部失败**(3次重试后仍失败):
- 记录失败日志:`[RETRY] platform API检查重试3次后仍失败,触发强制重启关键服务`
- 触发 **强制重启关键服务**(见原始需求文档 4.3 节)
- 依次重启:gateway、auth、system
- 重启完成后,**继续执行后续检查流程**(容器内服务检查、extapi检查、6060端口检查)
---
## 配置参数
### 可配置变量
在脚本开头定义以下配置变量,便于后续调整:
| 变量名 | 默认值 | 说明 |
| --- | --- | --- |
| `RETRY_MAX_COUNT` | 3 | 最大重试次数 |
| `RETRY_INTERVAL` | 30 | 重试间隔(秒) |
| `API_REQUEST_TIMEOUT` | 10 | API请求超时(秒) |
| `API_URL` | `https://192.168.5.44/platform/api/code` | API检查地址 |
**实现示例**
```bash
# ========== 配置参数 ==========
RETRY_MAX_COUNT=3 # 最大重试次数
RETRY_INTERVAL=30 # 重试间隔(秒)
API_REQUEST_TIMEOUT=10 # API请求超时(秒)
API_URL="https://192.168.5.44/platform/api/code" # API检查地址
# =================================
```
---
## 实现细节
### curl命令输出控制
为避免污染日志,curl命令使用以下参数:
| 参数 | 说明 |
| --- | --- |
| `-k` | 忽略SSL证书验证(保持原有) |
| `-m` | 设置超时时间(秒) |
| `-s` | 静默模式(不显示进度条) |
| `-S` | 显示错误信息(配合-s使用) |
**完整命令**
```bash
curl -k -m ${API_REQUEST_TIMEOUT} -s -S "${API_URL}"
```
---
## 脚本并发控制
### 文件锁机制
为防止定时任务间隔较短导致脚本并发执行冲突,添加文件锁机制。
**实现要求**
- 使用 `flock` 命令或PID文件方式
- 锁文件路径:`/tmp/ujava2-startup.lock`
- 获取锁失败时直接退出,不执行任何操作
**实现示例(flock方式)**
```bash
LOCK_FILE="/tmp/ujava2-startup.lock"
exec 200>"$LOCK_FILE"
flock -n 200 || exit 1 # 获取锁失败则退出
# 脚本退出时自动释放锁
```
**实现示例(PID文件方式)**
```bash
LOCK_FILE="/tmp/ujava2-startup.lock"
PID_FILE="/tmp/ujava2-startup.pid"
# 检查是否已有实例在运行
if [ -f "$PID_FILE" ]; then
OLD_PID=$(cat "$PID_FILE")
if ps -p "$OLD_PID" > /dev/null 2>&1; then
echo "$(timestamp) [WARNING] 另一个实例正在运行 (PID: $OLD_PID),退出"
exit 1
fi
fi
# 记录当前PID
echo $$ > "$PID_FILE"
# 脚本结束时清理PID文件
trap 'rm -f "$PID_FILE"' EXIT
```
## 规范文档
- 代码规范: `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
#!/bin/bash
# ========== 脚本并发控制 ==========
LOCK_FILE="/tmp/ujava2-startup.lock"
exec 200>"$LOCK_FILE"
flock -n 200 || {
echo "$(date '+%Y-%m-%d %H:%M:%S') [WARNING] 另一个实例正在运行,退出" | tee -a "$LOG_FILE"
exit 1
}
# 脚本退出时自动释放锁
# ====================================
LOG_FILE="/data/logs/ujava2-service-manager.log"
unacos_LOG="/data/logs/unacos_restart.log"
# ========== API检查重试配置参数 ==========
RETRY_MAX_COUNT=3 # 最大重试次数
RETRY_INTERVAL=30 # 重试间隔(秒)
API_REQUEST_TIMEOUT=10 # API请求超时(秒)
API_URL="https://192.168.5.44/platform/api/code" # API检查地址
# ========================================
timestamp() {
date "+%Y-%m-%d %H:%M:%S"
}
......@@ -28,39 +46,125 @@ declare -A SERVICE_SCRIPT_MAP=(
["message"]="/var/www/java/api/java-meeting/java-message-scheduling/run.sh"
)
# 平台API检查函数(通用方法,不依赖jq)
check_platform_api() {
local api_url="https://192.168.5.44/platform/api/code"
local http_code
local response
# 获取HTTP状态码和响应内容
response=$(curl -s -k -m 10 -w "\n%{http_code}" "$api_url" 2>/dev/null)
http_code=$(echo "$response" | tail -1)
response=$(echo "$response" | sed '$d')
# 首先检查HTTP状态码
if [ "$http_code" != "200" ]; then
echo "$(timestamp) [ERROR] 平台API检查失败: HTTP状态码 $http_code" | tee -a "$LOG_FILE"
# 函数名:check_api_request
# 功能:执行单次API请求
# 返回:0=成功,1=失败
# 输出:设置全局变量 API_HTTP_CODE 和 API_RESPONSE_BODY
check_api_request() {
# 执行curl请求,使用 -s 静默模式,-S 显示错误信息
local response=$(curl -k -m ${API_REQUEST_TIMEOUT} -s -S -w "\n%{http_code}" "${API_URL}" 2>&1)
local curl_exit_code=$?
# 分离响应体和状态码
API_HTTP_CODE=$(echo "$response" | tail -n1)
API_RESPONSE_BODY=$(echo "$response" | head -n-1)
# 判断curl命令执行结果
if [ $curl_exit_code -ne 0 ]; then
# curl命令执行失败(网络错误、连接超时等)
return 1
fi
# 检查响应是否为空
if [ -z "$response" ]; then
echo "$(timestamp) [ERROR] 平台API检查失败: 响应为空" | tee -a "$LOG_FILE"
# 判断HTTP状态码
if [ "$API_HTTP_CODE" = "200" ]; then
# 检查响应内容是否包含预期字符串
if echo "$API_RESPONSE_BODY" | grep -q '"操作成功"' && \
echo "$API_RESPONSE_BODY" | grep -qE '"code"[[:space:]]*:[[:space:]]*200'; then
return 0
else
# 状态码200但响应内容不符合预期
return 1
fi
fi
# 使用grep检查JSON响应中的关键字段(所有Linux系统都有grep)
# 检查是否包含 "操作成功" 和 "code":200 或 "code": 200
if echo "$response" | grep -q '"操作成功"' && \
echo "$response" | grep -qE '"code"[[:space:]]*:[[:space:]]*200'; then
# HTTP状态码非200
return 1
}
# 函数名:check_should_retry
# 功能:判断是否应该重试
# 参数:$1=http_code
# 返回:0=应该重试,1=不应该重试
check_should_retry() {
local http_code="$1"
# HTTP 4xx客户端错误(400-499),不重试
if [ -n "$http_code" ] && [ "$http_code" -ge 400 ] && [ "$http_code" -lt 500 ] 2>/dev/null; then
return 1 # 不重试
fi
# 其他情况(网络错误、5xx错误、响应内容不符)都应该重试
return 0 # 应该重试
}
# 函数名:check_platform_api
# 功能:执行平台API检查,支持重试机制
# 返回:0=成功,1=失败
check_platform_api() {
local retry_count=0
local should_retry=false
local final_result=1
local http_code=""
local response_body=""
# 首次请求
if check_api_request; then
echo "$(timestamp) [INFO] 平台API检查正常" | tee -a "$LOG_FILE"
return 0
fi
# 首次请求失败,获取错误信息
http_code="$API_HTTP_CODE"
response_body="$API_RESPONSE_BODY"
# 判断是否应该重试
if check_should_retry "$http_code"; then
should_retry=true
# 重试循环
while [ $retry_count -lt $RETRY_MAX_COUNT ] && [ "$should_retry" = "true" ]; do
retry_count=$((retry_count + 1))
# 记录重试日志
if [ -z "$http_code" ]; then
# 网络层面的错误(无HTTP状态码)
local error_info=$(echo "$response_body" | head -c 100)
echo "$(timestamp) [RETRY] 第${retry_count}次重试 platform API检查失败: ${error_info}" | tee -a "$LOG_FILE"
else
echo "$(timestamp) [ERROR] 平台API检查失败: ${response:0:200}..." | tee -a "$LOG_FILE"
return 1
# 有HTTP状态码的错误
echo "$(timestamp) [RETRY] 第${retry_count}次重试 platform API检查失败: HTTP ${http_code}" | tee -a "$LOG_FILE"
fi
# 等待重试间隔
sleep $RETRY_INTERVAL
# 执行重试请求
if check_api_request; then
# 重试成功
echo "$(timestamp) [RETRY] 第${retry_count}次重试 platform API检查成功" | tee -a "$LOG_FILE"
return 0
fi
# 重试仍然失败,更新错误信息
http_code="$API_HTTP_CODE"
response_body="$API_RESPONSE_BODY"
# 判断是否继续重试
if ! check_should_retry "$http_code"; then
should_retry=false
fi
done
# 重试全部失败
if [ $retry_count -eq $RETRY_MAX_COUNT ]; then
echo "$(timestamp) [RETRY] platform API检查重试${RETRY_MAX_COUNT}次后仍失败,触发强制重启关键服务" | tee -a "$LOG_FILE"
fi
else
# 不应该重试(4xx错误)
echo "$(timestamp) [ERROR] 平台API检查失败: HTTP ${http_code}(确定性失败,不重试)" | tee -a "$LOG_FILE"
fi
return 1
}
......@@ -385,20 +489,21 @@ check_and_start_malan() {
}
# 主流程
# 先检查平台API
# 1. 先检查平台API(带重试机制)
if ! check_platform_api; then
restart_critical_services
# 重启完成后继续执行后续流程
fi
# 原有服务检查逻辑
# 2. 原有服务检查逻辑
check_services || restart_problem_services
# 宿主机对外服务检查(新增)
# 3. 宿主机对外服务检查
if ! check_host_extapi_service; then
restart_host_extapi_service
fi
# 6060端口监测(新增)
# 4. 6060端口监测
check_and_start_malan
echo "===== $(timestamp) 操作完成 =====" | tee -a "$LOG_FILE"
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论