alibaba / nacos

an easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications.
https://nacos.io
Apache License 2.0
30.31k stars 12.85k forks source link

(稳定复现)客户端注册到 nacos 之后, 此时客户端断网很长时间后, nacos 服务端一直无法下线服务 (稳定复现) #10909

Closed EddyChina closed 1 year ago

EddyChina commented 1 year ago

nacos-client.2.2.3 nacos-server.2.2.3 (cluster与 standalone 都存在这个现象) spring-boot.3.1.2 nacos 服务端 jdk: 1.8 nacos 客户端 jdk: 17

相关 issue 也已经提到: https://github.com/alibaba/spring-cloud-alibaba/issues/3415

步骤:

  1. 启动服务端 -m standalone
  2. 启动客户端(springboot ), 在 nacos web 控制台可以看到注册成功
  3. 客户端断网 3 分钟(断网 1 分钟后, 停止客户端)
  4. 查看服务端控制台, 可以看到服务和实例一直存在, 无法下线

相关 pom `

com.alibaba.cloud spring-cloud-alibaba-dependencies 2022.0.0.0 pom import

com.alibaba.nacos nacos-client

com.alibaba.nacos nacos-client 5.2.3 org.springframework.cloud spring-cloud-dependencies 2022.0.3 pom import com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery `

客户端是 springboot 启动, 如果正常关闭, 服务下线也正常. 但是如果客户端在注册成功后断网, 服务列表会一直存在. 是临时类型实例.调按照 nacos 文档, 使用 API: DELETE /nacos/v1/ns/instance返回“ok”, 但在 nacos web 控制台仍然可以查到这个服务(查看 nacos 日志, 发现是找不到 ip#port这个 instance, nacos 代码里直接 return 了, 并没有报异常) nacos 注册中心的相关配置如下: spring: cloud: nacos: username: ${boot.parameters.nacos.username} password: ${boot.parameters.nacos.password} discovery: server-addr: ${boot.parameters.nacos.server-addr} namespace: ${boot.parameters.nacos.namespace} group: ${boot.parameters.nacos.group} service: ${spring.application.name} heart-beat-interval: 5000 heart-beat-timeout: 15000 # 客户端断网超过这个时间后, 还是可以查到, 且 health=true naming-load-cache-at-start: false register-enabled: true

并且, 设置logging.level.com.alibaba=debug后, 看到控制台有如下报错: ` 2023-08-03 10:45:09.792 DEBUG [pirate-arsenal,,] 69773 --- [] c.a.n.s.i.g.i.ManagedChannelImplBuilder : Unable to apply census stats

java.lang.ClassNotFoundException: com.alibaba.nacos.shaded.io.grpc.census.InternalCensusStatsAccessor at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:375) at com.alibaba.nacos.shaded.io.grpc.internal.ManagedChannelImplBuilder.getEffectiveInterceptors(ManagedChannelImplBuilder.java:652) at com.alibaba.nacos.shaded.io.grpc.internal.ManagedChannelImplBuilder.build(ManagedChannelImplBuilder.java:631) at com.alibaba.nacos.shaded.io.grpc.internal.AbstractManagedChannelImplBuilder.build(AbstractManagedChannelImplBuilder.java:297) at com.alibaba.nacos.common.remote.client.grpc.GrpcClient.createNewManagedChannel(GrpcClient.java:190) at com.alibaba.nacos.common.remote.client.grpc.GrpcClient.connectToServer(GrpcClient.java:325) at com.alibaba.nacos.common.remote.client.RpcClient.start(RpcClient.java:363) at com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy.start(NamingGrpcClientProxy.java:105) at com.alibaba.nacos.client.naming.remote.gprc.NamingGrpcClientProxy.(NamingGrpcClientProxy.java:98) at com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate.(NamingClientProxyDelegate.java:78) at com.alibaba.nacos.client.naming.NacosNamingService.init(NacosNamingService.java:102) at com.alibaba.nacos.client.naming.NacosNamingService.(NacosNamingService.java:85) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480) at com.alibaba.nacos.api.naming.NamingFactory.createNamingService(NamingFactory.java:59) at com.alibaba.nacos.api.NacosFactory.createNamingService(NacosFactory.java:77) at org.apache.dubbo.registry.nacos.NacosConnectionManager.createNamingService(NacosConnectionManager.java:123) at org.apache.dubbo.registry.nacos.NacosConnectionManager.(NacosConnectionManager.java:69) at org.apache.dubbo.registry.nacos.util.NacosNamingServiceUtils.createNamingService(NacosNamingServiceUtils.java:116) at org.apache.dubbo.registry.nacos.NacosRegistryFactory.createRegistry(NacosRegistryFactory.java:48) at org.apache.dubbo.registry.support.AbstractRegistryFactory.getRegistry(AbstractRegistryFactory.java:95) at org.apache.dubbo.registry.RegistryFactoryWrapper.getRegistry(RegistryFactoryWrapper.java:33) at org.apache.dubbo.registry.RegistryFactory$Adaptive.getRegistry(RegistryFactory$Adaptive.java) at org.apache.dubbo.registry.integration.RegistryProtocol.getRegistry(RegistryProtocol.java:432) at org.apache.dubbo.registry.integration.RegistryProtocol.export(RegistryProtocol.java:262) at org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper.export(ProtocolListenerWrapper.java:66) at org.apache.dubbo.qos.protocol.QosProtocolWrapper.export(QosProtocolWrapper.java:80) at org.apache.dubbo.rpc.protocol.ProtocolSecurityWrapper.export(ProtocolSecurityWrapper.java:83) at org.apache.dubbo.rpc.cluster.filter.ProtocolFilterWrapper.export(ProtocolFilterWrapper.java:58) at org.apache.dubbo.rpc.protocol.ProtocolSerializationWrapper.export(ProtocolSerializationWrapper.java:47) at org.apache.dubbo.rpc.protocol.InvokerCountWrapper.export(InvokerCountWrapper.java:42) at org.apache.dubbo.rpc.Protocol$Adaptive.export(Protocol$Adaptive.java) at org.apache.dubbo.config.ServiceConfig.doExportUrl(ServiceConfig.java:845) at org.apache.dubbo.config.ServiceConfig.exportRemote(ServiceConfig.java:814) at org.apache.dubbo.config.ServiceConfig.exportUrl(ServiceConfig.java:755) at org.apache.dubbo.config.ServiceConfig.doExportUrlsFor1Protocol(ServiceConfig.java:549) at org.apache.dubbo.config.ServiceConfig.lambda$doExportUrls$5(ServiceConfig.java:528) at org.apache.dubbo.metrics.event.MetricsEventBus.post(MetricsEventBus.java:80) at org.apache.dubbo.metrics.event.MetricsEventBus.post(MetricsEventBus.java:62) at org.apache.dubbo.config.ServiceConfig.doExportUrls(ServiceConfig.java:517) at org.apache.dubbo.config.ServiceConfig.doExport(ServiceConfig.java:488) at org.apache.dubbo.config.ServiceConfig.export(ServiceConfig.java:317) at org.apache.dubbo.config.deploy.DefaultModuleDeployer.exportServiceInternal(DefaultModuleDeployer.java:431) at org.apache.dubbo.config.deploy.DefaultModuleDeployer.exportServices(DefaultModuleDeployer.java:386) at org.apache.dubbo.config.deploy.DefaultModuleDeployer.startSync(DefaultModuleDeployer.java:167) at org.apache.dubbo.config.deploy.DefaultModuleDeployer.start(DefaultModuleDeployer.java:148) at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onContextRefreshedEvent(DubboDeployApplicationListener.java:150) at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onApplicationEvent(DubboDeployApplicationListener.java:139) at org.apache.dubbo.config.spring.context.DubboDeployApplicationListener.onApplicationEvent(DubboDeployApplicationListener.java:50) at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:437) at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:370) at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:961) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:611) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:436) at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) at com.tcghl.pirate.arsenal.PirateArsenalApplication.main(PirateArsenalApplication.java:10) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) `

stone-98 commented 1 year ago

我本地下载windows的2.2.3版本的包,也复现了这个问题,但是在最新的develop分支中没有复现出来。

EddyChina commented 1 year ago

我本地下载windows的2.2.3版本的包,也复现了这个问题,但是在最新的develop分支中没有复现出来。

我用 2.2.1 版本的 server 和 client 也复现了.

KomachiSion commented 1 year ago
  1. 这个日志是debug级别, 应该是grpc在初始化时判断环境和适配版本的逻辑使用的,尝试加载某个class,不存在的话会使用别的逻辑。对结果没有影响
  2. 断网也分种类,有的断网对存量连接没有影响,存量连接不会断开且不会影响请求,因此客户端和服务端认为此连接还是健康的。这样就不会移除。如果是连接会断开,或者请求发不到服务端, 服务端有一个health check的逻辑,在2.2.0重构限流插件的时候好像出了点bug, 导致这个逻辑没有生效, 最新分支应该已经修复了。
  3. 调DELETE接口删除实例的问题, 其他issue也解答过不少, 其实就是删除时必须是注册的客户端操作才行, 否则连接和心跳会进行补偿,最终结果就是无法移除。持久化实例和客户端,连接状态不绑定,因此可以通过DELETE直接删除。
EddyChina commented 1 year ago
  1. 这个日志是debug级别, 应该是grpc在初始化时判断环境和适配版本的逻辑使用的,尝试加载某个class,不存在的话会使用别的逻辑。对结果没有影响

    1. 断网也分种类,有的断网对存量连接没有影响,存量连接不会断开且不会影响请求,因此客户端和服务端认为此连接还是健康的。这样就不会移除。如果是连接会断开,或者请求发不到服务端, 服务端有一个health check的逻辑,在2.2.0重构限流插件的时候好像出了点bug, 导致这个逻辑没有生效, 最新分支应该已经修复了。

    2. 调DELETE接口删除实例的问题, 其他issue也解答过不少, 其实就是删除时必须是注册的客户端操作才行, 否则连接和心跳会进行补偿,最终结果就是无法移除。持久化实例和客户端,连接状态不绑定,因此可以通过DELETE直接删除。

十分感谢.

期待最新分支release. 顺便问一下, 最新分支啥时候 release?

EddyChina commented 1 year ago
  1. 这个日志是debug级别, 应该是grpc在初始化时判断环境和适配版本的逻辑使用的,尝试加载某个class,不存在的话会使用别的逻辑。对结果没有影响

    1. 断网也分种类,有的断网对存量连接没有影响,存量连接不会断开且不会影响请求,因此客户端和服务端认为此连接还是健康的。这样就不会移除。如果是连接会断开,或者请求发不到服务端, 服务端有一个health check的逻辑,在2.2.0重构限流插件的时候好像出了点bug, 导致这个逻辑没有生效, 最新分支应该已经修复了。

    2. 调DELETE接口删除实例的问题, 其他issue也解答过不少, 其实就是删除时必须是注册的客户端操作才行, 否则连接和心跳会进行补偿,最终结果就是无法移除。持久化实例和客户端,连接状态不绑定,因此可以通过DELETE直接删除。

关于 DELETE, 这里 followup 一下: 刚才复现后, 使用注册的客户端去发起 DELETE 请求, 返回 ok 但仍然无法下线(控制台可以查到). 通过一个 WARN 日志回查代码, 走到了这里:

Screenshot 2023-08-04 at 15 50 23

估计还是您说的那个 bug 导致数据状态不一致.

KomachiSion commented 1 year ago

数据和连接绑定,连接状态和连接的心跳绑定, 使用非注册服务的客户端进行删除,只能删除持久化实例。否则删除不了, 这个是设计的保护逻辑。 否则误操作会导致预期外服务被删除引发故障。

要下线实例可以使用更新实例,把enabled改为false。

KomachiSion commented 1 year ago

Fixed by https://github.com/alibaba/nacos/pull/10509

EddyChina commented 1 year ago

Fixed by #10509

下个版本计划啥时候发呀?

xiohuchi commented 12 months ago

下个版本计划啥时候发呀?

xiohuchi commented 12 months ago

我都很急,希望快点发版修复