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
我的服务采用的是

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency>

所以查看该版本下的jar包依赖发现与当前elasticsearch版本相差较大,当前版本为1.3.8,如图:

spring-boot-actuator依赖elasticsearch版本

2.4 对比采用elasticsearch 1.3.8 和 elasticsearch 5.6.10版本的两个服务,访问/actuator/health
服务A依赖jar包

        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>5.6.10</version>
        </dependency>

服务B依赖jar包

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>transport</artifactId>
            <version>1.3.8</version>
        </dependency>

分别启动服务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<ElasticsearchHealthIndicator, Client> {

        private final Map<String, Client> clients;

        private final ElasticsearchHealthIndicatorProperties properties;

        ElasticsearchClientHealthIndicatorConfiguration(Map<String, Client> 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:健康检查、审计、统计和监控

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容