【轻松入门SpringBoot】actuator健康检查(中)

系列文章:

【轻松入门SpringBoot】从0到1搭建web 工程(上)-使用SpringBoot框架

【轻松入门SpringBoot】从0到1搭建web 工程(中) -使用Spring框架

【轻松入门SpringBoot】从0到1搭建web 工程(下)-在实践中对比SpringBoot和Spring框架

【轻松入门SpringBoot】actuator健康检查(上)

目录

系列文章:

前言:

计划:

准备:

实现:

[1、建表:city 表](#1、建表:city 表)

2、缓存类

CityCacheService

3、健康检查

CityCacheHealthIndicator

配置:

4、运行结果

5、调试中

总结:


前言:

上篇文章介绍了 Springboot框架用来做健康检查功能的actuator,actuator 支持从多个维度检查系统的健康程度,并且支持自定义,帮助开发者或维护者快速感知系统异常,及早控制影响。actuator 默认能检查数据库、磁盘、ping 是否正常,后面我们看看actuator自定义能还能检查哪些?

计划:

检查本地缓存是否加载成功?

检查 Redis连接是否正常?

监控线程池是否正常?

其他

准备:

因为前面几篇文章使用的 Springboot:2.0.4.RELEASE 版本,版本比较低,没有actuator 官网说的"支持从"存活"和"就绪"的维度提供安全检查"功能、健康组功能(可以对监控项分组,分为核心组、非核心组,使用者可以对不同组的异常采取不同的措施)等,所以我将 SpringBoot 升级到了3.3.0 版本,Java 版本升级到 17。

多版本并行问题:我的工作项目使用的 Java8,但 demo 需要 Java17,所以电脑上需要Java多版本并行,这里最后使用SDKMAN实现的。SDKMAN(全称 Software Development Kit Manager,曾用名 GVM)是一款开源、轻量的命令行工具,专为类 Unix 系统(macOS、Linux、WSL)设计,核心用于一键安装、切换、管理多版本 JVM 生态 SDK 与开发工具,自动配置环境变量,大幅简化开发环境维护SDKMAN!。非常好操作,按照豆包整理的教程很快就能安好了。一开始遇到了一些网络问题,怎么都下载不下来,借助豆包最后也很快解决了,有多版本并行需求的可以试试。

实现:

检查本地缓存是否加载成功:项目启动时加载数据库 city 表数据缓存到本地

1、建表:city 表

sql 复制代码
CREATE TABLE `city` (
  `city_id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '城市ID(主键)',
  `city_name` VARCHAR(50) NOT NULL COMMENT '城市名称',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` TINYINT UNSIGNED DEFAULT 0 COMMENT '是否删除(0=未删,1=已删)',
  PRIMARY KEY (`city_id`) USING BTREE, -- 主键索引(InnoDB默认聚簇索引)
  UNIQUE KEY `uk_city_name` (`city_name`) USING BTREE, -- 城市名称唯一索引(避免重复)
  KEY `idx_is_deleted` (`is_deleted`) USING BTREE -- 软删除字段索引(查询优化)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='城市基础信息表';

2、缓存类

CityCacheService

java 复制代码
package com.example.springbootuniq.cache;

import com.example.springbootuniq.entity.City;
import com.example.springbootuniq.repository.CityRepository;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class CityCacheService {
    // 本地缓存 <city,cityName>
    private final Cache<Integer, String> cityCache;
    // 缓存加载标记(用于健康检查)
    private volatile boolean cacheLoaded = false;
    // 缓存加载时间戳
    private long loadTimestamp;

    private final CityRepository cityRepository;

    // 构造器注入 cityRepository,初始化 caffeine 缓存
    public CityCacheService(CityRepository cityRepository) {
        this.cityRepository = cityRepository;
        this.cityCache = Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(1000)
                .expireAfterWrite(30, TimeUnit.SECONDS)
                .build();
    }

    // 项目启动后自动加载数据到缓存(bean初始化后执行)
    @PostConstruct
    public void loadCityCache() {
        try {
            // 1.从数据库中查询所有城市
            List<City> cityList = cityRepository.findAll();
            if(CollectionUtils.isEmpty(cityList)){
                cacheLoaded = false;
                return;
            }
            // 2.加载缓存到本地
            cityList.forEach(city -> cityCache.put(city.getCityId(), city.getCityName()));
            cacheLoaded = true;
            loadTimestamp = System.currentTimeMillis();
            System.out.println("缓存加载完成,共加载:" +  cityList.size() + "条数据");
        } catch (Exception e) {
            cacheLoaded = false;
            System.out.println("缓存加载失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
    // 对外提供查询接口
    public String getCityName(Integer cityId) {
        if (!cacheLoaded) {
            throw new RuntimeException("缓存未加载完成"); // 缓存未加载完成,抛出异常
        }
        return cityCache.getIfPresent(cityId);
    }

    // 供健康检查调用:加载缓存加载状态
    public boolean isCacheLoaded() {
        return cacheLoaded;
    }
    // 供健康检查调用:获取缓存大小
    public long getCacheSize(){
        return cityCache.estimatedSize();
    }
    // 供健康检查调用:获取缓存加载时间
    public long getLoadTimestamp() {
        return loadTimestamp;
    }
}

3、健康检查

CityCacheHealthIndicator

java 复制代码
package com.example.springbootuniq.config.health;

import com.example.springbootuniq.cache.CityCacheService;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class CityCacheHealthIndicator implements HealthIndicator {
    private final CityCacheService cityCacheService;

    public CityCacheHealthIndicator(CityCacheService cityCacheService) {
        this.cityCacheService = cityCacheService;
    }
    @Override
    public Health health() {
        boolean isLoaded = cityCacheService.isCacheLoaded();
        long cacheSize = cityCacheService.getCacheSize();
        if(isLoaded && cacheSize > 0){
            // 缓存加载成功 -> 健康状态 up
            return Health.up().withDetail("提示", "城市缓存配置加载成功")
                    .withDetail("缓存大小", cacheSize)
                    .withDetail("加载时间(时间戳)", cityCacheService.getLoadTimestamp())
                    .withDetail("查询示例(城市 Id=1)",cityCacheService.getCityName(1)) // 模拟调用
                    .build();
        }else{
            return Health.down().withDetail("提示", "城市缓存配置加载失败")
                    .withDetail("缓存大小", cacheSize)
                    .withDetail("缓存加载状态", isLoaded)
                    .withDetail("缓存数据量",cacheSize)
                    .build();
//            return Health.up().withDetail("提示", "城市缓存配置加载失败")
//                    .withDetail("缓存大小", cacheSize)
//                    .withDetail("缓存加载状态", isLoaded)
//                    .withDetail("缓存数据量",cacheSize)
//                    .withDetail("factStatus","OUT_OF_SERVICE")
//                    .build();
        }
    }
}

配置:

XML 复制代码
# Actuator 配置(与 spring 平级,统一 2 个空格缩进)
management:
  endpoints:
    web:
      exposure:
        include: health, info, livenessstate, readinessstate
  endpoint:
    health:
      show-details: always
  # 3.3.0 新格式:健康组直接在 management.health.group 下定义
  health:
    primary-group: core # 强制默认组为 core
    include: core
    # 定义健康组(3.3.0 新写法)
    group:
      core:
        # 核心组包含的组件
        include: db,diskSpace,ping
        # 核心组的状态优先级
        status:
          order: DOWN, OUT_OF_SERVICE, UP, UNKNOWN
      non-core:
        # 非核心组包含的组件
        include: cityCache
        # 非核心组的状态优先级
        status:
          order: DOWN, OUT_OF_SERVICE, UP, UNKNOWN
    status:
      order: DOWN, OUT_OF_SERVICE, UP, UNKNOWN

4、运行结果

http://localhost:8080/actuator/health

java 复制代码
{
    "status": "UP",
    "components": {
        "cityCache": {
            "status": "UP",
            "details": {
                "提示": "城市缓存配置加载成功",
                "缓存大小": 5,
                "加载时间(时间戳)": 1766929107938,
                "查询示例(城市 Id=1)": "南京"
            }
        },
        "db": {
            "status": "UP",
            "details": {
                "database": "MySQL",
                "validationQuery": "isValid()"
            }
        },
        "diskSpace": {
            "status": "UP",
            "details": {
                "total": 994662584320,
                "free": 835691266048,
                "threshold": 10485760,
                "path": "/spring-boot-uniq/.",
                "exists": true
            }
        },
        "ping": {
            "status": "UP"
        }
    }
}

从结果可以看出,缓存已经加载完成,最外层status 是 up。

等缓存失效,再请求:返回结果

java 复制代码
{
    "status": "DOWN",
    "components": {
        "cityCache": {
            "status": "DOWN",
            "details": {
                "提示": "城市缓存配置加载失败",
                "缓存大小": 0,
                "缓存加载状态": true,
                "缓存数据量": 0
            }
        },
        "db": {
            "status": "UP",
            "details": {
                "database": "MySQL",
                "validationQuery": "isValid()"
            }
        },
        "diskSpace": {
            "status": "UP",
            "details": {
                "total": 994662584320,
                "free": 835683192832,
                "threshold": 10485760,
                "path": "/spring-boot-uniq/.",
                "exists": true
            }
        },
        "ping": {
            "status": "UP"
        }
    }
}

5、调试中

我想给检查项目划分一些级别,就像 autuator 中的描述:DOWN, OUT_OF_SERVICE, UP, UNKNOWN。级别高的有问题时,status=down,服务启动失败;级别低的抛出异常,但服务正常启动。比如上面的 city 缓存允许在服务启动时不加载完成,等定时任务同步,所以需要用到group-组的功能:cityCache 属于非核心组,当 cityCache 的 status 是 down 时,最外层的 status 也是 up.但目前运行的结果是 down,还在调试中。。。

总结:

这篇我们学习了使用 actuator 自定义健康检查-加载本地缓存,升级了 SpringBoot 和 Java 版本,并使用sdkman 实现多版本并行管理。分组的问题还在排查中,希望能在下周顺利解决掉。

这篇文章内容有点稀疏,一开始本来想先试 Redis 缓存的,但电脑的域名解析异常,搞了半天没搞好。改成先写本地缓存,想试试分组的概念,因为"一刀切"对复杂的生产环境不友好,而且Springboot也提供了这样的功能。于是开始进行版本升级,虽然没怎么踩坑,但版本升级这件事本身是需要花时间的,需要把报错的低版本的引用挨个替换掉。升级了 SpringBoot 版本,又发现 Java 版本不够,又开始升级 Java 版本,解决多版本并行问题。。。总之,都是成长。

bye~

相关推荐
咕噜咕噜啦啦3 小时前
Java速通(应用程序)
java·开发语言
爱学习的小可爱卢3 小时前
JavaEE进阶——Spring Bean与Java Bean的核心区别
java·后端·java-ee
期待のcode3 小时前
Java Object 类
java·开发语言
悟能不能悟4 小时前
如何处理 丢失更新(不可重复读)
java
李拾叁的摸鱼日常4 小时前
Java Optional 最佳实践+注意事项+避坑指南
java·后端·面试
雨中飘荡的记忆4 小时前
MyBatis配置解析模块详解
java·mybatis
qq_12498707534 小时前
基于微信小程序的科技助农系统的设计与实现(源码+论文+部署+安装)
java·大数据·spring boot·后端·科技·微信小程序·毕业设计
狂奔小菜鸡4 小时前
Day35 | Java多线程入门
java·后端·java ee
『六哥』4 小时前
IntelliJ IDEA 安装教程
java·ide·intellij-idea·intellij idea