# 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
```