[特殊字符] SpringBoot 自定义系统健康检测:数据库、Redis、表统计、更新时长、系统性能全链路监控

🚀 SpringBoot 自定义系统健康检测:数据库、Redis、表统计、更新时长、系统性能全链路监控

在生产环境中,我们常常需要一个统一的健康检查接口,用来监控系统运行状态、关键依赖服务、以及各业务表的更新情况。

相比 Spring Actuator 的简单 "UP / DOWN",我们往往需要 更全面、更细粒度、更贴近业务场景的健康监控系统

本文将基于以下大纲构建一个可直接落地的健康监控模块:


📌 文章大纲

✔ 数据库健康检测

✔ Redis 连通性检测

✔ 表统计(总量 / 今日新增 / 未更新天数)

✔ 每张表的最新更新时间

✔ 系统运行时长(uptime)、CPU、内存信息

✔ 自动格式化数字与时间(万、百万、亿 / 刚刚、几分钟前)

最终你将得到一个 完整的、可上线的健康监控接口


🧱 一、健康检查核心结构设计

使用 SystemHealthVO 作为返回对象:

java 复制代码
@Data
public class SystemHealthVO {

    private boolean dbHealthy;
    private boolean redisHealthy;
    private boolean systemHealthy;

    private List<String> healthDetails = new ArrayList<>();

    private long uptimeDays;
    private long jvmMemoryUsedMb;
    private long jvmMemoryTotalMb;
    private String cpuLoadPercent;

    private List<TableStats> tableStats;

    private String totalBusinessRows;
    private long totalTodayChange;
    private String totalTodayChangeStr;

    private String latestUpdateAgo;
    private String mostOutdatedTable;
    private Integer mostOutdatedDays;

    @Data
    public static class TableStats {
        private String tableName;
        private long totalRows;
        private String totalRowsStr;
        private long todayIncrement;
        private Integer daysNoUpdate;
        private String lastUpdateAgo;
    }
}

🗄 二、数据库健康检查(最严格方式)

最简单也最可靠的方式:查询一张必然存在的表。

java 复制代码
private boolean checkDatabase(SystemHealthVO vo) {
    try {
        healthMapper.countTable("sys_user");
        return true;
    } catch (Exception e) {
        vo.getHealthDetails().add("数据库连接异常:" + e.getMessage());
        return false;
    }
}

📦 三、Redis 连通性检测

写入 + 删除一个轻量 Key,即可验证。

java 复制代码
private boolean checkRedis(SystemHealthVO vo) {
    try {
        stringRedisTemplate.opsForValue().set("health_check_key", "ok", 3, TimeUnit.SECONDS);
        stringRedisTemplate.delete("health_check_key");
        return true;
    } catch (Exception e) {
        vo.getHealthDetails().add("Redis 连接异常");
        return false;
    }
}

⚙ 四、系统性能信息:CPU、JVM、运行时长

java 复制代码
private void fillSystemInfo(SystemHealthVO vo) {
    long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
    vo.setUptimeDays(uptime / 86_400_000L);

    Runtime r = Runtime.getRuntime();
    vo.setJvmMemoryUsedMb((r.totalMemory() - r.freeMemory()) >> 20);
    vo.setJvmMemoryTotalMb(r.totalMemory() >> 20);

    var os = (com.sun.management.OperatingSystemMXBean)
            ManagementFactory.getOperatingSystemMXBean();
    double load = os.getProcessCpuLoad();
    vo.setCpuLoadPercent(load >= 0 ? String.format("%.1f%%", load * 100) : "N/A");
}

📊 五、核心功能:业务表统计(总量 / 今日新增 / 未更新天数 / 最近更新时间)

定义表信息结构:

java 复制代码
private static class TableInfo {
    String tableName;
    String displayName;
    String timeColumn;

    TableInfo(String tableName, String displayName, String timeColumn) {
        this.tableName = tableName;
        this.displayName = displayName;
        this.timeColumn = timeColumn;
    }
}

示例定义业务表常量:

java 复制代码
private static final List<TableInfo> TABLES = List.of(
        new TableInfo("order_info", "订单表", "update_time"),
        new TableInfo("product", "商品表", "modify_time"),
        new TableInfo("user_log", "用户日志", "create_time")
);

⭐ 核心统计逻辑

java 复制代码
private RealTimeData loadTableStatistics() {

    RealTimeData data = new RealTimeData();
    data.stats = new ArrayList<>();

    long totalRows = 0;
    long todayTotal = 0;
    Timestamp latestUpdate = null;

    String mostOutdatedTable = null;
    Integer mostOutdatedDays = null;

    LocalDate today = LocalDate.now();
    Date dayStart = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant());
    Date dayEnd = Date.from(today.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());

    for (TableInfo info : TABLES) {
        SystemHealthVO.TableStats s = new SystemHealthVO.TableStats();
        s.setTableName(info.displayName);

        try {
            long cnt = Optional.ofNullable(healthMapper.countTable(info.tableName)).orElse(0L);
            s.setTotalRows(cnt);
            s.setTotalRowsStr(formatNumber(cnt));
            totalRows += cnt;

            Timestamp last = healthMapper.getMaxTime(info.tableName, info.timeColumn);
            if (last != null) {
                s.setLastUpdateAgo(formatTimeAgo(last));

                LocalDate lastDay = last.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                int days = (int) ChronoUnit.DAYS.between(lastDay, today);
                s.setDaysNoUpdate(days);

                if (mostOutdatedDays == null || days > mostOutdatedDays) {
                    mostOutdatedDays = days;
                    mostOutdatedTable = info.displayName;
                }

                if (latestUpdate == null || last.after(latestUpdate)) {
                    latestUpdate = last;
                }
            }

            long todayIncrement = Optional.ofNullable(
                    healthMapper.countToday(info.tableName, info.timeColumn, dayStart, dayEnd)
            ).orElse(0L);

            s.setTodayIncrement(todayIncrement);
            todayTotal += todayIncrement;

        } catch (Exception e) {
            log.error("统计表 {} 失败", info.tableName, e);
        }

        data.stats.add(s);
    }

    data.totalAllRows = totalRows;
    data.totalTodayAll = todayTotal;
    data.latestUpdateTime = latestUpdate;
    data.mostOutdatedName = mostOutdatedTable;
    data.mostOutdatedDays = mostOutdatedDays;

    return data;
}

🔢 六、数字格式化(万 / 百万 / 亿)

java 复制代码
private String formatNumber(long num) {
    if (num < 10_000) return "" + num;
    if (num < 100_000_000) return String.format("%.1f万", num / 10000.0).replace(".0万", "万");
    if (num < 100_000_000_000L) return String.format("%.1f百万", num / 1e8).replace(".0百万", "百万");
    return String.format("%.1f亿", num / 1e11).replace(".0亿", "亿");
}

⏳ 七、人性化时间格式(几分钟前 / 几天前)

java 复制代码
private String formatTimeAgo(Timestamp ts) {
    LocalDateTime t = ts.toLocalDateTime();
    Duration d = Duration.between(t, LocalDateTime.now());

    if (d.toMinutes() < 5) return "刚刚";
    if (d.toHours() < 1) return d.toMinutes() + "分钟前";
    if (d.toDays() < 1) return d.toHours() + "小时前";
    if (d.toDays() < 30) return d.toDays() + "天前";
    if (d.toDays() < 365) return (d.toDays() / 30) + "个月前";
    return (d.toDays() / 365) + "年前";
}

🧩 八、最终健康检查接口

java 复制代码
@Override
public SystemHealthVO getHealthInfo() {

    SystemHealthVO vo = new SystemHealthVO();

    vo.setHealthDetails(new ArrayList<>());

    vo.setDbHealthy(checkDatabase(vo));
    vo.setRedisHealthy(checkRedis(vo));

    vo.setSystemHealthy(vo.isDbHealthy() && vo.isRedisHealthy() && vo.getHealthDetails().isEmpty());

    fillSystemInfo(vo);

    RealTimeData data = loadTableStatistics();

    vo.setTableStats(data.stats);

    vo.setTotalBusinessRows(formatNumber(data.totalAllRows));
    vo.setTotalTodayChange(data.totalTodayAll);
    vo.setTotalTodayChangeStr(data.totalTodayAll > 0 ? "+" + data.totalTodayAll : "" + data.totalTodayAll);

    vo.setMostOutdatedTable(data.mostOutdatedName);
    vo.setMostOutdatedDays(data.mostOutdatedDays);

    vo.setLatestUpdateAgo(
            data.latestUpdateTime == null ? "从未更新" : formatTimeAgo(data.latestUpdateTime)
    );

    return vo;
}
相关推荐
小猿姐3 小时前
实测对比:哪款开源 Kubernetes MySQL Operator 最值得用?(2026 深度评测)
数据库·mysql·云原生
一灯架构5 小时前
90%的人答错!一文带你彻底搞懂ArrayList
java·后端
倔强的石头_5 小时前
从 “存得下” 到 “算得快”:工业物联网需要新一代时序数据平台
数据库
Y4090016 小时前
【多线程】线程安全(1)
java·开发语言·jvm
TDengine (老段)6 小时前
TDengine IDMP 可视化 —— 分享
大数据·数据库·人工智能·时序数据库·tdengine·涛思数据·时序数据
布局呆星6 小时前
SpringBoot 基础入门
java·spring boot·spring
风吹迎面入袖凉7 小时前
【Redis】Redisson的可重入锁原理
java·redis
GottdesKrieges7 小时前
OceanBase数据库备份配置
数据库·oceanbase
w6100104667 小时前
cka-2026-ConfigMap
java·linux·cka·configmap