Spring Cloud 注册 Nacos2.x 版本RPC客户端连接抛 Server check fail, please check server ** 异常解决方案
作者:mmseoamin日期:2023-12-05

在 nacos-client:2.x 中,如果启动了naocs2.x的服务不修改任何端口的情况下是可以正常连接的。

在docker下运行了 nacos 并且指定跟主机绑定 “8848:8848” 端口

cloud 中配置

spring.cloud.nacos.discovery.server-addr=http://172.1.6.41:8848
spring.cloud.nacos.config.server-addr=http://172.1.6.41:8848

在nacos服务启动正常下,Spring Cloud 连接会抛出一个异常为:

c.a.n.c.remote.client.grpc.GrpcClient    :Server check fail, please check server 172.1.6.41 ,port 9848 is available , error ={}
java.util.concurrent.ExecutionException: com.alibaba.nacos.shaded.io.grpc.StatusRuntimeException: UNAVAILABLE: io exception

那是因为Nacos2.x版本相比1.X新增了gRPC的通信方式,因此需要增加2个端口。新增端口是在配置的主端口(server.port)基础上,进行一定偏移量自动生成

端口与主端口的偏移量描述
98481000客户端gRPC请求服务端端口,用于客户端向服务端发起连接和请求
98491001服务端gRPC请求服务端端口,用于服务间同步等

那只是因为docker映射的端口缺少了,把剩下两个也补齐就正常了

    ports:
      - "8848:8848"
      - "9848:9848"
      - "9849:9849"

如果需要修改 8848 的端口,比如尝试把 8848修改成38848

ports:
  - "38848:8848"
  - "39848:9848"
  - "39849:9849"

修改成这样之后连接配置就变成了38848 也是没有问题的

spring.cloud.nacos.discovery.server-addr=http://172.1.6.41:38848
spring.cloud.nacos.config.server-addr=http://172.1.6.41:38848

如果39848 端口被其他应用占用,则需要设置一个其他端口

ports:
  - "38848:8848"
  - "39849:9848"
  - "39849:9849"

但是此时cloud连接nacos之后client 会根据 38848 进行偏移1000,而我们端口已经改成了39849,rpc是连接不上的,所以我们可以看看rpc的偏移到底是如何实现

我们可以从错误信息中获取到他是 c.a.n.c.remote.client.grpc.GrpcClient 这个类型下抛出来的错误。把这个类型名字直接放入IDEA,让智能提示帮我们找到这个类型是 import com.alibaba.nacos.common.remote.client.grpc.GrpcClient;,从结构上看一共有以下这些方法

com.alibaba.nacos.common.remote.client.grpc.GrpcClient#GrpcClient(java.lang.String)
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#GrpcClient(java.util.Properties)
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#GrpcClient(com.alibaba.nacos.common.remote.client.grpc.GrpcClientConfig)
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#GrpcClient(com.alibaba.nacos.common.remote.client.grpc.GrpcClientConfig, com.alibaba.nacos.common.remote.client.ServerListFactory)
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#GrpcClient(java.lang.String, java.lang.Integer, java.lang.Integer, java.util.Map)
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#getConnectionType
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#createGrpcExecutor
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#shutdown
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#createNewChannelStub
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#createNewManagedChannel
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#shuntDownChannel
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#serverCheck
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#bindRequestStream
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#sendResponse
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#connectToServer
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#LOGGER
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#clientConfig
com.alibaba.nacos.common.remote.client.grpc.GrpcClient#grpcExecutor

找到 public Connection connectToServer(ServerInfo serverInfo)方法

@Override
    public Connection connectToServer(ServerInfo serverInfo) {
        try {
            if (grpcExecutor == null) {
                this.grpcExecutor = createGrpcExecutor(serverInfo.getServerIp());
            }
            int port = serverInfo.getServerPort() + rpcPortOffset();
            ManagedChannel managedChannel = createNewManagedChannel(serverInfo.getServerIp(), port);
            RequestGrpc.RequestFutureStub newChannelStubTemp = createNewChannelStub(managedChannel);
            if (newChannelStubTemp != null) {
          
                Response response = serverCheck(serverInfo.getServerIp(), port, newChannelStubTemp);
                if (response == null || !(response instanceof ServerCheckResponse)) {
                    shuntDownChannel(managedChannel);
                    return null;
                }
          
                BiRequestStreamGrpc.BiRequestStreamStub biRequestStreamStub = BiRequestStreamGrpc
                        .newStub(newChannelStubTemp.getChannel());
                GrpcConnection grpcConn = new GrpcConnection(serverInfo, grpcExecutor);
                grpcConn.setConnectionId(((ServerCheckResponse) response).getConnectionId());
          
                //create stream request and bind connection event to this connection.
                StreamObserver payloadStreamObserver = bindRequestStream(biRequestStreamStub, grpcConn);
              

可以看到 port 是通过serverInfo.getServerPort() + rpcPortOffset(); 拼接起来的,serverInfo.getServerPort()就是通过配置文件读取过来的,那就只需要知道 rpcPortOffset(); 方法是获取值就可以了,它是一个抽象方法

    public abstract int rpcPortOffset();

这个方法由 GrpcSdkClient 和 GrpcClusterClient 两个类型分别实现

GrpcClusterClient
    @Override
    public int rpcPortOffset() {
        return Integer.parseInt(System.getProperty(GrpcConstants.NACOS_SERVER_GRPC_PORT_OFFSET_KEY,
                String.valueOf(Constants.CLUSTER_GRPC_PORT_DEFAULT_OFFSET)));
    }
GrpcSdkClient
    @Override
    public int rpcPortOffset() {
        return Integer.parseInt(System.getProperty(GrpcConstants.NACOS_SERVER_GRPC_PORT_OFFSET_KEY,
                String.valueOf(Constants.SDK_GRPC_PORT_DEFAULT_OFFSET)));
    }

两个类型实现都是先从环境变量的 GrpcConstants.NACOS_SERVER_GRPC_PORT_OFFSET_KEY = nacos.server.grpc.port.offset 进行获取偏移量,如果获取不到则设置成常量Constants.SDK_GRPC_PORT_DEFAULT_OFFSET =1000

public static final Integer SDK_GRPC_PORT_DEFAULT_OFFSET = 1000;

所以只需要设置一下nacos.server.grpc.port.offset 配置即可,偏移量就是RPC端口 39849- 38848 的出来的数值,nacos.server.grpc.port.offset = RPC端口 - Nacos端口