[特殊字符] 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;
}
相关推荐
程序员黑豆2 小时前
Java类型推断完全指南:从var到菱形运算符,掌握使用限制与最佳实践
java·前端·ai编程
风流 少年3 小时前
Spring AI 2.0:Memory
java·后端·spring
码匠许师傅3 小时前
【C++ 面试真题】聊聊 C++ 的移动语义与右值引用
java·c++·面试
2603_965148113 小时前
如何解析JSON数据?API返回的商品信息处理教程
开发语言·数据库·python·自动化·json·api
ltl3 小时前
RocksDB 经典故障排查:L0、compaction 与 write stall
数据库
小席是个热心肠4 小时前
AI相关的自我学习
java·人工智能·学习
xfhuangfu4 小时前
Oracle中建立到CDB和PDB的连接
数据库·oracle·rpc
小小龙学IT5 小时前
DuckDB 深度实战:用 C++ 在进程内跑一个「分析型数据库」
数据库·c++
NineData5 小时前
DTCC 2026 预告|NineData CEO& 创始人叶正盛:面向 AI Agent 的数据库 DevOps 与数据复制实践
数据库·人工智能·数据库开发·devops·ninedata·数据库技术·dtcc
J_bean5 小时前
MySQL 事务是否必须手动开启?
数据库·mysql·数据库事务·自动提交·手动提交