继续上一篇文章,猿Why最近与Consul杠上了。也是因为有这样一个需求:“服务启动(注册)失败请款下,要求Consul通过HTTP方式对服务进行健康检查,检查结果是critical”。我对这个“失败”的想法是比较多的,比如:中间件、数据源访问不通,我也可以认为是失败。那情况不就复杂了吗?
最开始的路线是通过一个事件(失败、异常事件)去触发:builder.down()。对,依然是要基于
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
找到一篇很不错的文章,介绍Actuator。这一part猿Why就不重复记录了。
Actuator如何支持自定义健康检查
实现HealthIndicator或者直接(间接)继承AbstractHealthIndicator。至于如何实现,参考代码如下:
public class ConsulHealthIndicator extends AbstractHealthIndicator {
private ConsulClient consul;
public ConsulHealthIndicator(ConsulClient consul) {
this.consul = consul;
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
final Response<String> leaderStatus = this.consul.getStatusLeader();
final Response<Map<String, List<String>>> services = this.consul
.getCatalogServices(QueryParams.DEFAULT);
builder.up().withDetail("leader", leaderStatus.getValue()).withDetail("services",
services.getValue());
}
}
自定义的健康检查如何生效
核心API:org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorRegistryBeans#get(ApplicationContext applicationContext)
思路:在Actuator加载初始化的时候,会根据类型HealthIndicator找到Bean,放到一个Map<String, HealthIndicator>。健康检查API被调用的时候,遍历这个Map,是生成健康检查的结果。
final class HealthIndicatorRegistryBeans {
private HealthIndicatorRegistryBeans() {
}
public static HealthIndicatorRegistry get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
indicators.putAll(applicationContext.getBeansOfType(HealthIndicator.class));
if (ClassUtils.isPresent("reactor.core.publisher.Flux", null)) {
new ReactiveHealthIndicators().get(applicationContext).forEach(indicators::putIfAbsent);
}
HealthIndicatorRegistryFactory factory = new HealthIndicatorRegistryFactory();
return factory.createHealthIndicatorRegistry(indicators);
}
private static class ReactiveHealthIndicators {
public Map<String, HealthIndicator> get(ApplicationContext applicationContext) {
Map<String, HealthIndicator> indicators = new LinkedHashMap<>();
applicationContext.getBeansOfType(ReactiveHealthIndicator.class)
.forEach((name, indicator) -> indicators.put(name, adapt(indicator)));
return indicators;
}
private HealthIndicator adapt(ReactiveHealthIndicator indicator) {
return () -> indicator.health().block();
}
}
}
健康检查结果是如何构造的
核心API:org.springframework.boot.actuate.health.Health.Builder
源码有点长,我就不全部粘贴过来占篇幅了。Builder内部有一个status状态,和details。status用于标记此项健康检查是否通过,details用于存放一些具体信息。
public static class Builder {
private Status status;
private Map<String, Object> details;
//……此处省略很多代码
}
然后来看一下我的Demo效果(实验做完的时候有两个小知识点):
如果要显示其中一项,自然也是可以:
知识点一
/actuator/health/discoveryComposite 是服务发现配置的自定义健康检查。
核心API:org.springframework.boot.actuate.health.CompositeHealthIndicator#health
这个方法导致了最后的健康检查结果的状态为“DOWN”
知识点二
/actuator/health/ 检查结果,details中可以看出,如果有一个status是“DOWN”,那么最后的结果就是“DOWN”。这个“DOWN”是如何的来的呢?
读完源码之后知道:
核心API:org.springframework.boot.actuate.health.OrderedHealthAggregator#aggregateStatus
@Override
protected Status aggregateStatus(List<Status> candidates) {
// Only sort those status instances that we know about
List<Status> filteredCandidates = new ArrayList<>();
for (Status candidate : candidates) {
if (this.statusOrder.contains(candidate.getCode())) {
filteredCandidates.add(candidate);
}
}
// If no status is given return UNKNOWN
if (filteredCandidates.isEmpty()) {
return Status.UNKNOWN;
}
// Sort given Status instances by configured order
filteredCandidates.sort(new StatusComparator(this.statusOrder));
return filteredCandidates.get(0);
}
原来是org.springframework.boot.actuate.health.CompositeHealthIndicator#health中的算法,挑选了第一个众多健康检查项中的第一个检查结果作为/actuator/health的最终结果。猿Why认为这样依然有一定的随机性。如果是所有检查项来一个与操作会更加合适。参考API:org.springframework.boot.actuate.health.CompositeHealthIndicator。可以对最后这个选择状态计算进行自定义。
consul dev
如果说是为了做DEMO练习,就完全没有必要去搭建一个集群模式的Consul服务。开发模式启动一个即可。
consul下载:官方链接 启动命令(以Windows cmd为例):
E:\consul_1.9.0_windows_amd64>consul.exe agent -dev -log-file="\data\consul.log"