# spring-health-check-example **Repository Path**: logtail/spring-health-check-example ## Basic Information - **Project Name**: spring-health-check-example - **Description**: 自定义实现health-check - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-01-08 - **Last Updated**: 2022-01-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 自定义RedisHealthCheck ### 背景 因为生产环境的是k8s部署,pod是否存活基于healthCheck中的状态.默认情况下,如果数据服务redis挂了会导致 pod状态为down状态,触发容器重启规则。本质上Redis链接不是不会影响服务大多数接口, management.health.redis.enabled=false 之后不会打印任何错误日志,问题不好排查 因此需要重新定义RedisHealthCheck ### 引入pom ```xml org.springframework.boot spring-boot-starter-actuator org.apache.commons commons-pool2 org.springframework.boot spring-boot-starter-data-redis ``` ### 自定义 RedisHealthIndicator ```java package cn.zwx.spring.health.check.example.health; import cn.hutool.core.util.StrUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.health.AbstractReactiveHealthIndicator; import org.springframework.boot.actuate.health.Health; import org.springframework.data.redis.RedisConnectionFailureException; import org.springframework.data.redis.connection.ClusterInfo; import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisConnectionUtils; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import reactor.core.publisher.Mono; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Properties; /** * @description: 自定义Redis health逻辑 * @projectName:services-manager-example * @see:cn.zwx.services.manager.example.common.health * @author:zhangwenxue * @createTime:2022/1/7 16:15 * @version:1.0 */ @Component("redisHealthIndicator") public class RedisHealthIndicator extends AbstractReactiveHealthIndicator { private static final Logger logger = LoggerFactory.getLogger(RedisHealthIndicator.class); private static final String VERSION = "version"; private static final String REDIS_VERSION = "redis_version"; private static final String ERROR = "error"; private final RedisConnectionFactory redisConnectionFactory; public RedisHealthIndicator(RedisConnectionFactory connectionFactory) { Assert.notNull(connectionFactory, "ConnectionFactory must not be null"); this.redisConnectionFactory = connectionFactory; } @Override protected Mono doHealthCheck(Health.Builder builder) { RedisConnection connection = null; String errorMsg = ""; try { // 把测试连接这个代码catch住 connection = RedisConnectionUtils .getConnection(this.redisConnectionFactory); if (connection instanceof RedisClusterConnection) { ClusterInfo clusterInfo = ((RedisClusterConnection) connection) .clusterGetClusterInfo(); builder.up().withDetail("cluster_size", Objects.requireNonNull(clusterInfo.getClusterSize())) .withDetail("slots_up", Objects.requireNonNull(clusterInfo.getSlotsOk())) .withDetail("slots_fail", Objects.requireNonNull(clusterInfo.getSlotsFail())); } else { Properties info = connection.info(); builder.up().withDetail(VERSION, info.getProperty(REDIS_VERSION)); } }catch (RedisConnectionFailureException e){ // 打印错误日志 logger.error(e.toString()); errorMsg = e.toString(); } finally { if (Objects.nonNull(connection)){ RedisConnectionUtils.releaseConnection(connection, this.redisConnectionFactory); } } // 处理返回逻辑 if (StrUtil.isBlank(errorMsg)){ return Mono.just(builder.build()); }else { Map map = new HashMap<>(4); map.put(ERROR,errorMsg); return Mono.just(Health.down().withDetails(map).build()); } } } ``` ### 自定义 MyHealthEndpointWebExtension ```java package cn.zwx.spring.health.check.example.health; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.endpoint.ApiVersion; import org.springframework.boot.actuate.endpoint.web.annotation.EndpointWebExtension; import org.springframework.boot.actuate.health.*; import org.springframework.stereotype.Component; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.Set; /** * @description: 自定义返回 * @projectName:services-manager-example * @see:cn.zwx.services.manager.example.common.health * @author:zhangwenxue * @createTime:2022/1/7 17:37 * @version:1.0 */ @Component @EndpointWebExtension(endpoint = HealthEndpoint.class) public class MyHealthEndpointWebExtension extends HealthEndpointWebExtension { private final Logger logger = LoggerFactory.getLogger(MyHealthEndpointWebExtension.class); private final String PING = "ping"; private final String REDIS = "redis"; /** * Create a new {@link HealthEndpointWebExtension} instance. * * @param registry the health contributor registry * @param groups the health endpoint groups */ public MyHealthEndpointWebExtension(HealthContributorRegistry registry, HealthEndpointGroups groups) { super(registry, groups); } /** * @description 重新写返回逻辑 * @return org.springframework.boot.actuate.health.HealthComponent * @author zhangwenxue * @createTime 2022/1/7 18:31 **/ @Override protected HealthComponent aggregateContributions(ApiVersion apiVersion, Map contributions, StatusAggregator statusAggregator, boolean showComponents, Set groupNames) { CompositeHealth compositeHealth = super.getCompositeHealth(apiVersion, contributions, statusAggregator, showComponents, groupNames); Map components = compositeHealth.getComponents(); HealthComponent healthComponent = components.get(PING); HealthComponent healthComponentForRedis = components.get(REDIS); Status statusForRedis = healthComponentForRedis.getStatus(); Status statusPing = healthComponent.getStatus(); CompositeHealth myCompositeHealth = null; if (statusForRedis.equals(Status.DOWN)){ // 如果Redis 挂了不影响healthCheck 整体状态 try { Class clazz = CompositeHealth.class; Constructor constructor = clazz.getDeclaredConstructor(ApiVersion.class, Status.class, Map.class); constructor.setAccessible(Boolean.TRUE); myCompositeHealth = constructor.newInstance(apiVersion, statusPing, components); } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) { logger.error("convert CompositeHealth obj error !",e); } return myCompositeHealth; }else { return compositeHealth; } } } ``` ### 最终返回结果 ```json { "status":"UP", "components":{ "diskSpace":{ "status":"UP", "details":{ "total":333935800320, "free":278025031680, "threshold":10485760, "exists":true } }, "ping":{ "status":"UP" }, "redis":{ "status":"DOWN", "details":{ "error":"org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is org.springframework.data.redis.connection.PoolException: Could not get a resource from the pool; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379" } } } } ``` ```text 2022-01-08 00:33:18.741 ERROR 5712 --- [192.168.199.136] c.z.s.h.c.e.health.RedisHealthIndicator : org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is org.springframework.data.redis.connection.PoolException: Could not get a resource from the pool; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:63792022-01-08 00:33:18.741 ERROR 5712 --- [192.168.199.136] c.z.s.h.c.e.health.RedisHealthIndicator : org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is org.springframework.data.redis.connection.PoolException: Could not get a resource from the pool; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to localhost:6379 ```