一、背景
服务加了springboot的actuator监控,运维脚本访问 服务的 actuator/health时报error日志
报错堆栈
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.springframework.boot.actuate.elasticsearch.ElasticsearchHealthIndicator$1
at org.springframework.boot.actuate.elasticsearch.ElasticsearchHealthIndicator.doHealthCheck(ElasticsearchHealthIndicator.java:81)
at org.springframework.boot.actuate.health.AbstractHealthIndicator.health(AbstractHealthIndicator.java:84)
at org.springframework.boot.actuate.health.CompositeHealthIndicator.health(CompositeHealthIndicator.java:68)
at org.springframework.boot.actuate.health.HealthEndpointWebExtension.getHealth(HealthEndpointWebExtension.java:50)
at sun.reflect.GeneratedMethodAccessor1596.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:223)
at org.springframework.boot.actuate.endpoint.invoke.reflect.ReflectiveOperationInvoker.invoke(ReflectiveOperationInvoker.java:76)
at org.springframework.boot.actuate.endpoint.annotation.AbstractDiscoveredOperation.invoke(AbstractDiscoveredOperation.java:61)
at org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$$Lambda$909/1296277943.invoke(Unknown Source)
at org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$ServletWebOperationAdapter.handle(AbstractWebMvcEndpointHandlerMapping.java:243)
at org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping$OperationHandler.handle(AbstractWebMvcEndpointHandlerMapping.java:299)
at sun.reflect.GeneratedMethodAccessor1595.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:991)
... 63 common frames omitted
二、解析问题
2.1 首先查找了关于NoClassDefFoundError 的报错原因:
参看:关于怎么解决java.lang.NoClassDefFoundError错误
](https://blog.csdn.net/u013452335/article/details/84102972)
也就是说 NoClassDefFoundError 是Java虚拟机在编译时能找到合适的类,而在运行时不能找到合适的类导致的错误。
2.2 然后再查找springboot github的issues
找到这个https://github.com/spring-projects/spring-boot/issues/17934,给我了启示可能由于版本的问题导致出现的。
2.3 查看maven仓库spring-boot-starter-actuator
我的服务采用的是
org.springframework.boot
spring-boot-actuator
2.0.3.RELEASE
所以查看该版本下的jar包依赖发现与当前elasticsearch版本相差较大,当前版本为1.3.8,如图:
2.4 对比采用elasticsearch 1.3.8 和 elasticsearch 5.6.10版本的两个服务,访问/actuator/health
服务A依赖jar包
org.elasticsearch
elasticsearch
5.6.10
服务B依赖jar包
org.elasticsearch.client
transport
1.3.8
分别启动服务A和B,访问/actuator/health
服务A 可以正常得到 UP,
服务B 会报
{"status":10000,"desc":"Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.springframework.boot.actuate.elasticsearch.ElasticsearchHealthIndicator$1","data":null}
通过上诉对比已经可以初步得出是因为所依赖的elasticsearch版本不同导致的。
2.5 源码分析
根据堆栈定位到org.springframework.boot.actuate.elasticsearch.ElasticsearchHealthIndicator的doHealthCheck方法,
报错位置为switch (response.getStatus()) 。
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
ClusterHealthRequest request = Requests.clusterHealthRequest(
ObjectUtils.isEmpty(this.indices) ? ALL_INDICES : this.indices);
ClusterHealthResponse response = this.client.admin().cluster().health(request)
.actionGet(this.responseTimeout);
switch (response.getStatus()) {
case GREEN:
case YELLOW:
builder.up();
break;
case RED:
default:
builder.down();
break;
}
builder.withDetail("clusterName", response.getClusterName());
builder.withDetail("numberOfNodes", response.getNumberOfNodes());
builder.withDetail("numberOfDataNodes", response.getNumberOfDataNodes());
builder.withDetail("activePrimaryShards", response.getActivePrimaryShards());
builder.withDetail("activeShards", response.getActiveShards());
builder.withDetail("relocatingShards", response.getRelocatingShards());
builder.withDetail("initializingShards", response.getInitializingShards());
builder.withDetail("unassignedShards", response.getUnassignedShards());
}
debug代码的时候,可以通过idea evaluate expression获取response.getStatus() 的值,但进行switch时会报出NoClassDefFoundError异常。
查看了elasticsearch 两个版本的ClusterHealthStatus 类,发现类的路径和代码都有变动。
三、 问题原因
堆栈报错的NoClassDefFoundError,原因是因为采用的spring-boot-actuator jar包和elasticsearch jar 包版本不兼容引起的。
四、解决办法
两种解决办法:
1.关闭actuator 的es检查,不推荐。这种解决办法适合短期不再报error日志,不适合作为永久解决办法。
配置如下:
management:
health:
elasticsearch:
enabled: false
2.升级es版本到 5.6.10以上,实测es版本5.6.8 也是可以用的,前提是还是使用TransportClient方式调用es。
五、注意事项
上面提到的把es 版本升级到 5.6.10以上,但是会面临另一个问题,actuator监控es 生效原理是根据服务Bean实例中是否存在
org.elasticsearch.client.Client 类和其实例,如果没有则不会生成ElasticsearchHealthIndicator(actuator自带的eshealth indicator),也就不会对es进行监控监控了。
源码:
@Configuration
@ConditionalOnClass(Client.class)
@ConditionalOnBean(Client.class)
@EnableConfigurationProperties(ElasticsearchHealthIndicatorProperties.class)
static class ElasticsearchClientHealthIndicatorConfiguration extends
CompositeHealthIndicatorConfiguration {
private final Map clients;
private final ElasticsearchHealthIndicatorProperties properties;
ElasticsearchClientHealthIndicatorConfiguration(Map clients,
ElasticsearchHealthIndicatorProperties properties) {
this.clients = clients;
this.properties = properties;
}
@Bean
@ConditionalOnMissingBean(name = "elasticsearchHealthIndicator")
public HealthIndicator elasticsearchHealthIndicator() {
return createHealthIndicator(this.clients);
}
@Override
protected ElasticsearchHealthIndicator createHealthIndicator(Client client) {
Duration responseTimeout = this.properties.getResponseTimeout();
return new ElasticsearchHealthIndicator(client,
responseTimeout != null ? responseTimeout.toMillis() : 100,
this.properties.getIndices());
}
}
而5.6版本以后官网推荐使用 RestHighLevelClient,不推荐再使用TransportClient方式。所以还是会导致actuator监控不到es。
解决方法:
就是定义自己的health indicator,来完成对es 的监控。
可以参考此链接的代码:
springboot --自定义健康检查
六、相关链接:
github elasticsearch源码
github spring-boot-actuator源码
Spring Boot Actuator:健康检查、审计、统计和监控