实现一个健康检查模块

     参考 SpringBoot 的健康检查设计,在平台层实现一套健康检查机制。挺优雅。

1、健康检查器接口类设计

1
2
3
4
5
6
7
8
/**
* IHealthChecker
*
*/
public interface IHealthChecker {

Health doHealth();
}

2、健康检查器抽象实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* AbstractHealthChecker
*
* 有一些公共逻辑需要定义在这一层抽象层,所有的自检实现都必须具备,例如开关、
*
*/
public abstract class AbstractHealthChecker implements IHealthChecker {

/**
* 模板方法确保 Health 初始状态为 up
*
* @return
*/
public Health doHealth() {
Health.Builder builder = Health.up();
return health(builder);
}

public abstract Health health(Health.Builder builder);

// 自检开关
private boolean toggle = true;

// 严格模式,严格模式下的健康检查器状态,将影响最终健康状态
private boolean strict = true;

// 省略了 getter/setter
}

3、健康状态的枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* 检查状态
*
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public final class Status {

public static final Status UNKNOWN = new Status("UNKNOWN");

public static final Status UP = new Status("UP");


public static final Status DOWN = new Status("DOWN");


public static final Status OUT_OF_SERVICE = new Status("OUT_OF_SERVICE");

private final String code;

private final String description;

public Status(String code) {
this(code, "");
}


public Status(String code, String description) {
this.code = code;
this.description = description;
}

@JsonProperty("status")
public String getCode() {
return this.code;
}


@JsonInclude(JsonInclude.Include.NON_EMPTY)
public String getDescription() {
return this.description;
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Status) {
return ObjectUtils.nullSafeEquals(this.code, ((Status) obj).code);
}
return false;
}

@Override
public int hashCode() {
return this.code.hashCode();
}

@Override
public String toString() {
return this.code;
}

}

4、健康信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
* Health
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public final class Health {

private final Status status;

private final Map<String, Object> details;

private Health(Builder builder) {
this.status = builder.status;
this.details = Collections.unmodifiableMap(builder.details);
}

@JsonUnwrapped
public Status getStatus() {
return this.status;
}

public Map<String, Object> getDetails() {
return this.details;
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Health) {
Health other = (Health) obj;
return this.status.equals(other.status) && this.details.equals(other.details);
}
return false;
}

@Override
public int hashCode() {
int hashCode = this.status.hashCode();
return 13 * hashCode + this.details.hashCode();
}

@Override
public String toString() {
return getStatus() + " " + getDetails();
}

public static Builder unknown() {
return status(Status.UNKNOWN);
}


public static Builder up() {
return status(Status.UP);
}

public static Builder down(Exception ex) {
return down().withException(ex);
}


public static Builder down() {
return status(Status.DOWN);
}


public static Builder outOfService() {
return status(Status.OUT_OF_SERVICE);
}


public static Builder status(String statusCode) {
return status(new Status(statusCode));
}

public static Builder status(Status status) {
return new Builder(status);
}

public static class Builder {

private Status status;

private final Map<String, Object> details;

public Builder() {
this.status = Status.UNKNOWN;
this.details = new LinkedHashMap<>();
}

public Builder(Status status) {
this.status = status;
this.details = new LinkedHashMap<>();
}

public Builder(Status status, Map<String, ?> details) {
this.status = status;
this.details = new LinkedHashMap<>(details);
}

public Builder withException(Throwable ex) {
return withDetail("error", ex.getClass().getName() + ": " + ex.getMessage());
}


public Builder withDetail(String key, Object value) {
this.details.put(key, value);
return this;
}

public Builder withDetails(Map<String, ?> details) {
this.details.putAll(details);
return this;
}

public Builder unknown() {
return status(Status.UNKNOWN);
}

public Builder up() {
return status(Status.UP);
}

public Builder down(Throwable ex) {
return down().withException(ex);
}

public Builder down() {
return status(Status.DOWN);
}


public Builder outOfService() {
return status(Status.OUT_OF_SERVICE);
}

public Builder status(String statusCode) {
return status(new Status(statusCode));
}

public Builder status(Status status) {
this.status = status;
return this;
}

public Health build() {
return new Health(this);
}

}

}

5、健康检查器注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/**
* HealthCheckerRegistry
*
*/
@Component
public class HealthCheckerRegistry {

/**
* 同步锁
*/
private final Object monitor = new Object();

/**
* 健康检查器集合
*/
private Map<String, AbstractHealthChecker> checkers = new LinkedHashMap<>();

@Autowired
public void setCheckers(Map<String, AbstractHealthChecker> checkers) {
this.checkers = checkers;
}


/**
* 注册检查者
*
* @param name
* @param checker
*/
public void register(String name, AbstractHealthChecker checker) {
synchronized (this.monitor) {
AbstractHealthChecker existing = this.checkers.putIfAbsent(name, checker);
if (existing != null) {
throw new AresRuntimeException("健康检查器{}已存在,请排查", name);
}
}

}

/**
* 卸载检查器
*
* @param name
* @return
*/
public AbstractHealthChecker unregister(String name) {
Assert.notNull(name, "检查器名称不能为空");
synchronized (this.monitor) {
return this.checkers.remove(name);
}
}

/**
* 获取所有的检查器
*
* @return
*/
public Map<String, AbstractHealthChecker> getAllCheckers() {
synchronized (this.monitor) {
return Collections.unmodifiableMap(new LinkedHashMap<>(this.checkers));
}
}
}

6、健康检查服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* HealthCheckService 收集并组合所有的健康状态信息
*
*/
@Component
public class HealthCheckService {

@Resource
private HealthCheckerRegistry checkerRegistry;

/**
* 执行健康检查
*
* @return
*/
public Health doHealthCheck() {
return compositeHealth(checkerRegistry.getAllCheckers());
}

/**
* 聚合自检信息
*
* @param allCheckers
* @return
*/
public Health compositeHealth(Map<String, AbstractHealthChecker> allCheckers) {
Health.Builder builder = Health.up();
for (Map.Entry<String, AbstractHealthChecker> entry : allCheckers.entrySet()) {
String name = entry.getKey();
AbstractHealthChecker checker = entry.getValue();

// 如果当前检查器没有打开则跳过
if (!checker.isEnable()) {
continue;
}

// 执行自检逻辑
try {
Health health = checker.doHealth();
builder.withDetail(name, health);
if (health.getStatus() != Status.UP && checker.isStrict()) {
// 只要有一个健康检查器检查失败并且当前检查器处于严格模式,就设置为失败
builder.status(Status.DOWN);
}
} catch (Exception e) {
// 如果自检异常,则将该健康自检信息记录下来
builder.withDetail(name, Health.down(e).build());
// 如果检查器处于严格模式,DOWN之
if(checker.isStrict()){
// 并标记为 DOWN
builder.status(Status.DOWN);
}
}
}

return builder.build();
}

}

7、检查器实现案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
* RedisHealthChecker
* <p>
* 缓存健康检查
*
*/
@Component
public class RedisHealthChecker extends AbstractHealthChecker {

@Value("${xxxx.cache.health.checker.enable:false}")
private boolean enable;

@Value("${xxxx.cache.health.checker.strict:true}")
private boolean strict;

// 数据缓存
@Resource
private RedisService cacheService;

private final static String PONG = "PONG";


@Override
public Health health(Health.Builder builder) {

// 数据缓存就绪性自检
if (null == cacheService) {
return builder.down().withDetail("redisHealthChecker", "cacheService not prepared").build();
}

RedisTemplate<String, Object> cacheTemplate = cacheService.getRedisTemplate();

if (null == cacheTemplate) {
return builder.down().withDetail("redisHealthChecker", "cacheTemplate not prepared").build();
}

RedisConnectionFactory cacheConnectionFactory = cacheTemplate.getConnectionFactory();
if (null == cacheConnectionFactory || cacheConnectionFactory.getConnection().isClosed()) {
return builder.down().withDetail("redisHealthChecker", "cache server is closed").build();
}

String cachePong = cacheConnectionFactory.getConnection().ping();

if (!StringUtil.isEmpty(cachePong) &&
PONG.equalsIgnoreCase(cachePong)) {
return builder.up().withDetail("redisHealthChecker", "session server is ready").build();
}

// 未知状态
return builder.up().withDetail("redisHealthChecker", "UNKNOWN").build();
}

@PostConstruct
public void init() {
// 检查开关
if (enable) {
enable();
} else {
disable();
}

// 严格模式
if (strict) {
enStrict();
} else {
deStrict();
}
}
}

8、Restful 方式接入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
*
*/
@RestController
public class HealthHandler {

// 聚合检查信息详情的服务
@Resource
private HealthCheckService healthCheckService;


ObjectMapper mapper = new ObjectMapper();

/**
* 存活性健康检查
*
* @return
*/
@RequestMapping(value = {"/liveness"}, produces = {"text/plain;charset=UTF-8"})
public String liveness() {
return "ok";
}

/**
* 就绪性健康检查
*
* @return
*/
@RequestMapping(value = {"/readiness"}, produces = {"text/plain;charset=UTF-8"})
public String readiness(HttpServletResponse response) {
Health health = healthCheckService.doHealthCheck();

// 如果就绪检查失败,则修改返回状态码
if(health.getStatus() == null || health.getStatus() != Status.UP){
response.setStatus(HttpStatus.SC_INTERNAL_SERVER_ERROR);
}

String jsonStr;
try {
jsonStr = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(health);
} catch (JsonProcessingException e) {
throw new AresRuntimeException("健康检查模块执行失败");
}

return jsonStr;
}
}
打赏
  • Copyrights © 2017 - 2025 杨海波
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信