锦州市网站建设_网站建设公司_域名注册_seo优化
2025/12/29 5:01:38 网站建设 项目流程

系列文章:


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

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

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

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

目录

系列文章:

前言:

计划:

准备:

实现:

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 表

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

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

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(); } } }

配置:

# 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

{ "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。

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

{ "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~

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询