记一次生产环境elasticsearch排查问题的排查None of the configured nodes are available

今天其他组的同事过来说他们es生产环境报了一个错误,因为对es有一些了解所以叫我帮忙看看具体错误如下

这里写图片描述

以前我自己在测试的时候也有发现这个错,但是那时候是由于自己本地模拟集群环境,虚拟机的网络没调好,但是过一会就恢复了没在意,所以和他们说也许过一会就会恢复,但是到了中午他们正式环境还是有这个问题,一直不能解决。我第一个感觉是不是es挂了,于是上head插件排查了。由于这个组是后台的人员查询使用定时任务导入es,所以es只有一个节点,登入head后发现集群状态为黄色,而且后台query能查出数据,基本排除了服务端的问题
这里写图片描述
这里写图片描述

那么现在就往客户端方向排查了,我的第一感觉是可能客户端和服务端的网络环境有问题是不是2台机器的网络不通,但是询问过运维的同事之后,他们告诉我这2台的网络并没有问题,生产环境并没有很具体的日志,而且不能重启,断点,我想彻底验证下是不是2台机器网络出了问题,于是我快速的建立了个spring boot结合es的testjar打包上生产运行下,简单的写几行测试

这里写图片描述

快速的把jar放到和生产一台主机上部署一下(不得不说springboot真的很方便,和各种组件的结合直接加入start就好了,可以快速结合各种插件)。在浏览器输出restcontroller的地址

这里写图片描述

使用springboot进行连接连接es完全没有问题,这时候没有其他办法了,因为当初使用es的时候没出现什么问题并没有深入的去查看源码,查看tomcat的日志查询日志报错是这行话请求执行的时候经过TransportClientNodesService这个类

这里写图片描述

我们使用的es是2.34版本上github 下载TransportClientNodesService.java查看ensureNodesAreAvailable
这里写图片描述

这里写图片描述

这里得知在请求发现的时候他是获取TransportClientNodesService nodes属性确认可用的节点,如果this.nodes属性为空就报错了,这时候我们来看下TransportClientNodesService 的有哪些属性,看看nodes到底怎么获取。

这里写图片描述

nodesSampler这个属性引起了我的注意变量名节点探测器,线程池5秒后执行任务ScheduledNodeSampler我们来看看ScheduledNodeSampler到底代码是怎么写的
这里写图片描述

由于我并没有配置client.transport.sniff所以应该使用的是默认的SimpleNodeSampler 实际上sample方法调用的就是 SimpleNodeSampler doSample方法

  `  @Override
    protected void doSample() {
        HashSet<DiscoveryNode> newNodes = new HashSet<>();
        HashSet<DiscoveryNode> newFilteredNodes = new HashSet<>();
        for (DiscoveryNode listedNode : listedNodes) {
            if (!transportService.nodeConnected(listedNode)) {
                try {
                    // its a listed node, light connect to it...
                    logger.trace("connecting to listed node (light) [{}]", listedNode);
                    transportService.connectToNodeLight(listedNode);
                } catch (Throwable e) {
                    hostFailureListener.onNodeDisconnected(listedNode, e);
                    logger.info("failed to connect to node [{}], removed from nodes list", e, listedNode);
                    continue;
                }
            }
            try {
                LivenessResponse livenessResponse = transportService.submitRequest(listedNode, TransportLivenessAction.NAME,
                        headers.applyTo(new LivenessRequest()),
                        TransportRequestOptions.builder().withType(TransportRequestOptions.Type.STATE).withTimeout(pingTimeout).build(),
                        new FutureTransportResponseHandler<LivenessResponse>() {
                            @Override
                            public LivenessResponse newInstance() {
                                return new LivenessResponse();
                            }
                        }).txGet();
                if (!ignoreClusterName && !clusterName.equals(livenessResponse.getClusterName())) {
                    logger.warn("node {} not part of the cluster {}, ignoring...", listedNode, clusterName);
                    newFilteredNodes.add(listedNode);
                } else if (livenessResponse.getDiscoveryNode() != null) {
                    // use discovered information but do keep the original transport address, so people can control which address is exactly used.
                    DiscoveryNode nodeWithInfo = livenessResponse.getDiscoveryNode();
                    newNodes.add(new DiscoveryNode(nodeWithInfo.name(), nodeWithInfo.id(), nodeWithInfo.getHostName(), nodeWithInfo.getHostAddress(), listedNode.address(), nodeWithInfo.attributes(), nodeWithInfo.version()));
                } else {
                    // although we asked for one node, our target may not have completed initialization yet and doesn't have cluster nodes
                    logger.debug("node {} didn't return any discovery info, temporarily using transport discovery node", listedNode);
                    newNodes.add(listedNode);
                }
            } catch (Throwable e) {
                logger.info("failed to get node info for {}, disconnecting...", e, listedNode);
                transportService.disconnectFromNode(listedNode);
                hostFailureListener.onNodeDisconnected(listedNode, e);
            }
        }

        nodes = validateNewNodes(newNodes);
        filteredNodes = Collections.unmodifiableList(new ArrayList<>(newFilteredNodes));
    }
}`

这里一共干了这几件事情
1、循环初始注册的地址列表listedNodes属性先去缓存里面取地址列表(ConcurrentMap)查询有就直接跳下一步请求节点具体信息,如果没有先进行一次轻连接(数据包较小的连接底层使用netty通信)如果timeout跳出本次循环,抛弃本次的连接地址
2、如果地址缓存列表向服务器请求livenessResponse(netty封装的网络通信)得到livenessResponse信息后查看是否有配置ignoreClusterName如果有把他加入 filteredNodes 里面属于不可用节点,连接失败的话移出已经有的节点缓存
3、把上面得到的新的节点列表赋值给nodes使得client得到最新的地址列表

按照上面的代码就算暂时节点不能用在下一次5秒任务的时候也会自动恢复,而且已经测试过不是网络问题,这时候我严重怀疑这个任务并没有在运行,于是上生产环境打印jstack
这里写图片描述
多次打印都在time_wating 看了下代码任务触发条件

这里写图片描述

严重怀疑是生产的连接被关了导致任务没有运行,于是询问开发人员,果然在代码里发现这么一行话 到网上搜索了下
这里写图片描述
这里写图片描述

感觉有可能是这句话出现了问题,于是在本地试了下关掉client果然重现了这个问题。总结了下出现这个错误有可能是1.esclient被关闭 2.网络pingout超时导致,具体可能要根据生产系统的现象进行排查

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页