fix: health 接口 DB 探测改后台缓存,避免阻塞被误判宕机
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user