fix: health 接口 DB 探测改后台缓存,避免阻塞被误判宕机

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-09 09:35:26 +08:00
co-authored by Claude Code
parent ae2b585df9
commit a3566c1f0c
2 changed files with 56 additions and 5 deletions
@@ -23,6 +23,7 @@ import org.noear.solon.core.handle.MethodType;
import com.hospital.front.adapter.HospitalRouter;
import com.hospital.front.dto.Result;
import com.hospital.front.infra.DbHealthMonitor;
import com.hospital.front.infra.exception.BusinessException;
/**
@@ -43,6 +44,10 @@ public class AdminApi {
@Inject
private HospitalRouter router;
/** DB 健康缓存监测(health 接口读缓存秒回,避免 DB 慢时阻塞被 Agent 误判宕机) */
@Inject
private DbHealthMonitor dbHealthMonitor;
/** 日志目录(与 logback.xml 的 log.path 保持一致) */
private File getLogDir() {
return new File(Solon.cfg().get("log.path", "./log"));
@@ -59,20 +64,21 @@ public class AdminApi {
// ================= 1. 健康与指标 =================
/** 健康检查(含数据库探测 */
/** 健康检查(进程健康即时返回;DB 状态读后台探测缓存,不阻塞 */
@Mapping("/health")
public Result<Map<String, Object>> health(Context ctx) {
checkToken(ctx);
Runtime mem = Runtime.getRuntime();
Result<Boolean> db = router.active().healthCheck();
Result<Boolean> db = dbHealthMonitor.last();
Map<String, Object> out = new LinkedHashMap<>();
out.put("status", db.isSuccess() ? "UP" : "DEGRADED");
// db=null 表示后台首轮探测未完成(启动/唤醒初期),进程本身健康,报 STARTING 而非 DEGRADED
out.put("status", db == null ? "STARTING" : (db.isSuccess() ? "UP" : "DEGRADED"));
out.put("hospital", router.activeCode());
out.put("hospitalName", router.active().displayName());
out.put("registeredHospitals", router.registeredCodes());
Map<String, Object> database = new HashMap<>();
database.put("ok", db.isSuccess());
database.put("msg", db.getMsg());
database.put("ok", db != null && db.isSuccess());
database.put("msg", db != null ? db.getMsg() : "DB 探测进行中");
out.put("database", database);
Map<String, Object> jvm = new HashMap<>();
jvm.put("totalMemoryMb", mem.totalMemory() / 1048576);
@@ -0,0 +1,45 @@
package com.hospital.front.infra;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import org.noear.solon.scheduling.annotation.Scheduled;
import com.hospital.front.adapter.HospitalRouter;
import com.hospital.front.dto.Result;
import java.util.concurrent.atomic.AtomicReference;
/**
* 数据库健康后台监测(缓存式)。
*
* 后台周期探测 DB(默认 30 秒),/api/admin/health 直接读缓存秒回——
* 避免 DB 建连慢(如机器休眠唤醒后网络未就绪,实测可达 4 分钟)时
* 健康接口长时间阻塞,导致 Agent 看门狗 30s 超时误判主程序宕机而反复重启。
*
* 启动初期缓存为空视为 STARTING(探测线程尚未跑完第一轮),不算故障。
*/
@Component
public class DbHealthMonitor {
@Inject
private HospitalRouter router;
/** 最近一次探测结果(null = 尚未完成首轮探测) */
private final AtomicReference<Result<Boolean>> last = new AtomicReference<>();
/** 周期探测(首轮延迟由调度器决定,失败不影响后续轮次) */
@Scheduled(cron = "*/30 * * * * *")
public void probe() {
try {
last.set(router.active().healthCheck());
} catch (Exception e) {
// healthCheck 内部已捕获,此处兜底防止调度线程中断
last.set(Result.error("探测异常:" + e.getMessage()));
}
}
/** 最近一次探测结果;未完成首轮探测时返回 null(调用方按 STARTING 处理) */
public Result<Boolean> last() {
return last.get();
}
}