springboot actuator: java.lang.NoClassDefFoundError: Could not initialize ElasticsearchHealthIndi...

一、背景
服务加了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,如图:

spring-boot-actuator依赖elasticsearch版本

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 类,发现类的路径和代码都有变动。


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:健康检查、审计、统计和监控

你可能感兴趣的:(springboot actuator: java.lang.NoClassDefFoundError: Could not initialize ElasticsearchHealthIndi...)