现象
微服务项目启动出现com.alibaba.nacos.api.exception.NacosException: Client not connected, current status:STARTING异常
原因
临时实例使用GrpcClient进行注册和心跳检测,通过看报错信息打印,可以看到nacos使用了GrpcClient,提示连接失败,查阅资料发现nacos从2.x版本开始,对于临时实例注册使用GrpcClient,通过grpc方式进行通信,区别于1.x版本的HttpClient,通过http接口的方式进行通信
其中grpc通信端口默认设置是与主端口偏移1000和1001
查看nacos-client2.2.0的源码,在GrpcClient.java中的connectToServer方法定义了端口,端口为在主端口的基础上进行偏移得到
查看rpcPortOffset方法,发现是RpcClient.java这一个抽象类的抽象方法。其中GrpcClient类继承于RpcClient类,RpcClient类中有rpcPortOffset抽象方法,GrpcClient中也继承了rpcPortOffset抽象方法,GrpcClient抽象类的实现类有两个,分别是GrpcSdkClient和GrpcClusterClient
可以看到GrpcSdkClient默认端口偏移为1000,GrpcClusterClient默认端口偏移1001,通过配置项nacos.server.grpc.port.offset可以设置端口偏移,需要注意这里的配置没法根据不同的GrpcClient实现进行区分
GrpcClient的不同实现类使用工厂模式进行创建,RpcClientFactory为工厂类,其中存在createClient和createClusterClient两个静态方法分别用于创建GrpcSdkClient和GrpcClusterClient对象,在nacos-client2.2.0的源码中没有看到使用创建GrpcClusterClient对象方法的地方,就是说RpcClient使用的就是GrpcSdkClient
nacos注册分析
在nacos进行注册时,使用了NamingClientProxy接口中的registerService方法进行注册,具体注册操作在两个实现类NamingGrpcClientProxy和NamingHttpClientProxy中定义,这里用到了静态代理模式,代理类NamingClientProxyDelegate对registerService方法进行代理,根据是否临时实例选择不同的实现,临时实例使用NamingGrpcClientProxy,非临时实例使用NamingHttpClientProxy
配置类中ephemeral定义了是否是临时实例,对应配置spring.cloud.nacos.discovery.ephemeral,默认为true
NamingGrpcClientProxy中根据前面提到的方式使用工厂模式对RpcClient进行创建
解决
通过上面的分析,知道可以有两种解决思路
1.开放grpc通信端口,端口号为nacos主端口+1000。我是因为docker容器启动的nacos,导致开放端口缺失
2.修改服务配置,spring.cloud.nacos.discovery.ephemeral = false,将实例改为持久化实例,这样使用HttpClient进行注册和心跳,无需额外开放rpc端口