Nacos报错:Server check fail, please check server 127.0.0.1 ,port 9848 is available , error ={}

问题背景:

Spring Cloud Alibaba的项目启动,Nacos报错:java.util.concurrent.ExecutionException: com.alibaba.nacos.shaded.io.grpc.StatusRuntimeException: UNAVAILABLE: io exception

com.alibaba.nacos.api.exception.NacosException: Client not connected, current status:STARTING

 

解决办法:

先说结论,主要原因是由于pom引入Nacos的配置文件重复,一般使用spring-cloud-starter-alibaba-nacos-discovery即可,不需要引入config:

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>com.alibaba.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
<!--</dependency>-->

另外yml配置如下,不需要配置spring.cloud.nacos.config前缀下的属性:

spring.application.name=service-gateway
spring.cloud.nacos.discovery.server-addr=192.168.7.11:8848

spring.cloud.nacos.username=nacos
spring.cloud.nacos.password=nacos

 原理:

经过作者排查,发现是由于Springboot项目启动过程中,在容器中注入了2份Nacos的配置文件,更神奇的是,两份配置文件同时生效了。

说下怎么排查的,根据日志的一句info信息:

com.alibaba.nacos.common.remote.client   : [89ee5c0e-e593-40ff-a8c5-89836770cc62_config-0] Fail to connect server, after trying 200 times, last try server is {serverIp = '127.0.0.1', server main port = 8848}, error = unknown

 笔者找到了client包下,类RpcClient的报错,然后断点定位到了报错的原因。

LoggerUtils.printIfInfoEnabled(LOGGER, "[{}] Fail to connect server, after trying {} times, last try server is {}, error = {}", new Object[]{this.rpcClientConfig.name(), reConnectTimes, serverInfo, lastException == null ? "unknown" : lastException});

其中的serverInfo就是 {serverIp = '127.0.0.1', server main port = 8848},明显与我们配置的不一样。

那么它是如何获取的呢?

ServerInfo serverInfo = null;
Exception lastException;
  try {
     serverInfo = recommendServer.get() == null ? this.nextRpcServer() : 
     (ServerInfo)recommendServer.get();
     ......

 通过this.nextRpcServer(),我们继续看:

protected ServerInfo nextRpcServer() {
   String serverAddress = this.getServerListFactory().genNextServer();
   return this.resolveServerInfo(serverAddress);
}

 通过断点我们看到重点在serverAddress,注意它的获取方式:

this.getServerListFactory().genNextServer()

 先看this.getServerListFactory():

public ServerListFactory getServerListFactory() {
        return this.serverListFactory;
    }

注意以上的代码都在类com.alibaba.nacos.common.remote.client.RpcClient种。

从上面的代码,很清晰的看到,主要是通过serverListFactory的genNextServer()方法来获取Nacos的ip信息。

那么什么是serverListFactory?

public abstract class RpcClient implements Closeable {
    private static final Logger LOGGER = LoggerFactory.getLogger("com.alibaba.nacos.common.remote.client");
    private ServerListFactory serverListFactory;
    .......

它是RpcClient的一个私有属性,具体是什么呢?

public interface ServerListFactory {
    String genNextServer();
    String getCurrentServer();
    List<String> getServerList();
}

主要是获取服务器信息,看样子可以获取多个。再看一下它的继承类图:

 发现其有两种实现方式,一种是ServerListManager,一种是ClientWorker的内部类ConfigRpcTransportClient的匿名内部类实现。

然后笔者就发现,报错的原因是由于ServerListManager和ConfigRpcTransportClient的匿名内部类都进行了实例化,导致ServerListFactory的genNextServer()方法被调用了两次,这样我真正想要访问的Nacos其实已经注册访问上了,但是由于ConfigRpcTransportClient的匿名内部类的实例化也调用了genNextServer(),导致注册了一个127.0.0.1的nacos。最终会导致程序一直报错。

下面通过代码来演示,首先是拿到我们想要的serverList 信息,是这样加载的:

public ServerListManager(NacosClientProperties properties, String namespace) {
        ......
        this.namespace = namespace;
        this.initServerAddr(properties);
        if (this.getServerList().isEmpty()) {
            throw new NacosLoadException("serverList is empty,please check configuration");
        } else {
            this.currentIndex.set((new Random()).nextInt(this.getServerList().size()));
        }
    }

 重点在initServerAddr:

private void initServerAddr(NacosClientProperties properties) {
        this.endpoint = InitUtils.initEndpoint(properties);
        String serverListFromProps;
        if (StringUtils.isNotEmpty(this.endpoint)) {
            ........
        } else {
            //通过配置文件夹获取指定nacos信息
            serverListFromProps = properties.getProperty("serverAddr");
            if (StringUtils.isNotEmpty(serverListFromProps)) {
                //获取多个nacos配置
                this.serverList.addAll(Arrays.asList(serverListFromProps.split(",")));
                if (this.serverList.size() == 1) {
                    this.nacosDomain = serverListFromProps;
                }
            }
        }

    }

 然后根据前面的配置文件信息,是可以正常获取到想要的nacos信息的。

这里简要交代下ServerListManager是如何初始化的,在Springboot容器的启动过程中,调用到onRefresh方法的时候,通过多播器调用了nacos的监听器,然后在监听器的onApplicationEvent方法中调用register方法把本项目注册到nacos的配置中心去,在注册的过程中初始化了ServerListManager。

有问题的是在ConfigRpcTransportClient里面。

可以看到它是如何获取127开头的nacos配置的:

rpcClientInner.serverListFactory(new ServerListFactory() {
     public String genNextServer() {
             return ClientWorker.ConfigRpcTransportClient.access$401(ConfigRpcTransportClient.this).getNextServerAddr();
     }

 那么具体的配置信息怎么获取的呢?在ConfigRpcTransportClient的初始化中:

public class ConfigRpcTransportClient extends ConfigTransportClient {
        ......
        public ConfigRpcTransportClient(NacosClientProperties properties, ServerListManager serverListManager) {
            super(properties, serverListManager);
        }

通过断点,启动后可以看到它的配置到底是哪里取的:

进入最后一行:

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnProperty(
    name = {"spring.cloud.nacos.config.enabled"},
    matchIfMissing = true
)
public class NacosConfigBootstrapConfiguration {
    public NacosConfigBootstrapConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean
    public NacosConfigProperties nacosConfigProperties() {
        return new NacosConfigProperties();
    }

    @Bean
    @ConditionalOnMissingBean
    public NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {
        return new NacosConfigManager(nacosConfigProperties);
    }
......

 然后看到NacosConfigProperties:

@ConfigurationProperties("spring.cloud.nacos.config")
public class NacosConfigProperties {
    public static final String PREFIX = "spring.cloud.nacos.config";
    public static final String COMMAS = ",";
    public static final String SEPARATOR = "[,]";
    public static final String DEFAULT_NAMESPACE = "public";
    public static final String DEFAULT_ADDRESS = "127.0.0.1:8848";
    private static final Pattern PATTERN = Pattern.compile("-(\\w)");
    private static final Logger log = LoggerFactory.getLogger(NacosConfigProperties.class);

至此,一切都清晰了,这就是 spring.cloud.nacos.config配置以及127.0.0.1的由来,因为没有配置以spring.cloud.nacos.config为前缀的信息,然而又引入了NacosConfigBootstrapConfiguration,使得NacosConfigProperties使用了代码配置的默认ip信息,从而导致报错。

所以文章一开始就说了的解决办法去掉spring-cloud-starter-alibaba-nacos-config的pom依赖。

为什么要这样呢?

因为NacosConfigBootstrapConfiguration就在这个starter中,所以去掉config依赖才可以解决这个问题。

小结

最后,这里呼吁下大家,很多工程官方都有标准示例,可以直接参考,可以节省很多的排错时间。

这是例子:springcloud alibaba示例

  • 25
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值