Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A instance cannot be list by openapi if ClientRegisterServiceEvent miss #12930

Closed
Jack1007 opened this issue Dec 6, 2024 · 12 comments
Closed
Labels
kind/question Category issues related to questions or problems

Comments

@Jack1007
Copy link

Jack1007 commented Dec 6, 2024

Describe the bug
In my Nacos cluster, I found a instance in a service can not be list by openapi from one nacos node. I found a defect in the source code

    private void handleClientOperation(ClientOperationEvent event) {
        Service service = event.getService();
        String clientId = event.getClientId();
        if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
            addPublisherIndexes(service, clientId);
        } else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {
            removePublisherIndexes(service, clientId);
        } else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {
            addSubscriberIndexes(service, clientId);
        } else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {
            removeSubscriberIndexes(service, clientId);
        }
    }
    
    private void addPublisherIndexes(Service service, String clientId) {
        publisherIndexes.computeIfAbsent(service, key -> new ConcurrentHashSet<>());
        publisherIndexes.get(service).add(clientId);
        NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
    }

If the node miss the ClientRegisterServiceEvent because of the network fault temporary, then publisherIndexes will not put the clientid in it, so the instance list api cannot list this client never again.

在网络抖动的情况下,节点可能会丢失客户端信息,后续如果又错过了客户端注册事件,那么publisherIndexes里面将不在有该客户端信息,而service和instance的查询服务都是从publisherIndexes Map里面获取实例信息,就会导致该节点无法查询到该实例。

@KomachiSion
Copy link
Collaborator

  1. ClientOperationEvent是内存操作,不会丢失,和网络都懂没关系
  2. 如果网络抖动情况下, 客户端注册的请求丢失了,那么就是注册失败,服务端不应该加入到index中,需要等待客户端自行重试。
  3. 如果网络抖动情况下,服务端的同步请求丢失了,服务端有重试机制和对账机制,AP协议最终能够保持一致性,也不会丢失(AP协议无法保证绝对一致性,请自行查询CAP相关资料)。

综上所述,你所描述的情况确实可能在某个中间时刻发生,但是这是AP协议所允许和预期内的, 服务发现本身比较适合AP场景,在状态正常的情况下,能保证实时性、可用性和一致性,在出现故障时(如网络故障)会出现一定程度的不一致性,但最终在恢复后能够保持一致。

@KomachiSion KomachiSion added the kind/question Category issues related to questions or problems label Dec 9, 2024
@Jack1007
Copy link
Author

Jack1007 commented Dec 9, 2024

  1. ClientOperationEvent是内存操作,不会丢失,和网络都懂没关系

    1. 如果网络抖动情况下, 客户端注册的请求丢失了,那么就是注册失败,服务端不应该加入到index中,需要等待客户端自行重试。

    2. 如果网络抖动情况下,服务端的同步请求丢失了,服务端有重试机制和对账机制,AP协议最终能够保持一致性,也不会丢失(AP协议无法保证绝对一致性,请自行查询CAP相关资料)。

综上所述,你所描述的情况确实可能在某个中间时刻发生,但是这是AP协议所允许和预期内的, 服务发现本身比较适合AP场景,在状态正常的情况下,能保证实时性、可用性和一致性,在出现故障时(如网络故障)会出现一定程度的不一致性,但最终在恢复后能够保持一致。

我前面提出的是微观和局部的问题,我把整个问题描述一遍。
在一个三节点的nacos集群上面,我有一个服务启动2个实例并注册(临时方式)到了nacos集群,所有nacos节点都能查询到该服务的2个实例,正常运行了一段时间后,通过监控,发现其中一个nacos节点,查询到的实例数,少了一个,且后续一直无法恢复(也就是我质疑的最终一致性无法保证)。针对这个现象,我首先查看了少了的那个实例的日志(即nacos客户端日志),发现"receive invalid redirect request from peer x.x.x.x"的日志,查了下代码,发现是nacos做责任节点转发的时候,只会转发一次,再次转发会被拒绝,也就是报这个错。那么问题是为什么责任节点会被再次转发呢,原因是nacos服务的member list发生了变动,于是观察nacos服务端日志naming-server.log,发现确实有"healthy server list changed"的日志,且日志里面新成员列表有变化,再观察nacos.log日志,发现有"Send request fail"和"java.util.concurrent.TimeoutException: Waited 3000 milliseconds"的日志,这看起来是网络故障。
以上信息,我有了两个问题:
1、为什么会"java.util.concurrent.TimeoutException: Waited 3000 milliseconds",是因为nacos服务的机器负载或者网络问题吗。
2、为什么后续客户端和服务端正常了(客户端心跳和服务端心跳都正常了),但是其中一个nacos却少了一个实例的信息,且后续一直查不到?
针对第1个问题,我自己写了一个3节点的心跳服务,在nacos的节点上运行,心跳服务启动后互为客户和服务,采用TCP长连接,后续模拟nacos服务之间的心跳,5秒发一次PING PONG,3秒超时。在nacos故障时间期间内未发生过一次TCP故障,因此问题1的网络和机器资源问题就排除了,那么到底是为什么nacos会报TimeoutException,还未知。
至于问题2,就是我最开始贴的代码里面的质疑,就是member list变更后,会有一些注册和解除注册的一些事件,但是错过了处理这些事件的情况下,就有可能未更新publisherIndexes 而导致无法通过openapi查询到,且后续客户端心跳只是维持健康,并不会再发送注册事件了,最终一致性没有得到保证。

@KomachiSion
Copy link
Collaborator

  1. 超时算是老生常谈问题: 无非就3种可能,客户端(请求发起测资源不足,无法及时处理response)、网络问题,比如丢包,高延迟、服务端资源不足,无法及时响应request。 但是无论哪种,都是需要环境owner自行排查的

  2. 一直查不到,说明某个节点可能一直和其它节点没组成集群, 同步信息才会一直丢失,"receive invalid redirect request from peer x.x.x.x"的日志完全可以佐证这一点,根绝distro协议的计算原则, 说明必然存在两个节点中的healthy nodes列表不一致。

综上所述, distro协议AP会保证在故障恢复后的数据一致性, 但是如果节点一直处于故障状态,会存在一定程度的数据分区以保证服务的可用性;先对而言zk等CP协议会让故障节点直接报错,保证数据一致但牺牲可用性,具体可以查询CAP协议相关资料。

然后根据信息反馈,可以确定,server节点之间必然存在一定故障,可能是cluster.conf的配置存在错误,也可能是节点间的网络故障,也可能是某一个server node本身存在故障,但是具体是哪种,需要您自行排查。

@Jack1007
Copy link
Author

  1. 超时算是老生常谈问题: 无非就3种可能,客户端(请求发起测资源不足,无法及时处理response)、网络问题,比如丢包,高延迟、服务端资源不足,无法及时响应request。 但是无论哪种,都是需要环境owner自行排查的

    1. 一直查不到,说明某个节点可能一直和其它节点没组成集群, 同步信息才会一直丢失,"receive invalid redirect request from peer x.x.x.x"的日志完全可以佐证这一点,根绝distro协议的计算原则, 说明必然存在两个节点中的healthy nodes列表不一致。

综上所述, distro协议AP会保证在故障恢复后的数据一致性, 但是如果节点一直处于故障状态,会存在一定程度的数据分区以保证服务的可用性;先对而言zk等CP协议会让故障节点直接报错,保证数据一致但牺牲可用性,具体可以查询CAP协议相关资料。

然后根据信息反馈,可以确定,server节点之间必然存在一定故障,可能是cluster.conf的配置存在错误,也可能是节点间的网络故障,也可能是某一个server node本身存在故障,但是具体是哪种,需要您自行排查。

1、超时问题:针对这个问题我上面提到我有去排除节点主机资源和网络资源问题,通过我自己的tcp长连接心跳服务可以证明,节点主机资源和网络环境都没有问题,那么只有可能是nacos节点内部的问题,我也看过jvm gc,是正常的,节点进程的线程池根据cpu核心算的,也是够大的(超过100个线程),而每分钟的集群总的请求量是2000,三个节点每分钟平均处理700个心跳事件,不应该线程并发能力不够。到这里我没有什么头绪查到超时的具体原因是什么了。
2、服务后续一直查不到:您提到是某个节点一直没有组成集群,这个是不成立的,因为超时问题只是偶尔发生,一分钟左右就自行恢复了,那些报错日志都是一分钟期间报的,报错并没有持续,后续集群状态是正常的,但是因为集群的这个超时导致的状态不一致的抖动,有些服务的注册信息会在某个节点丢失,而后续即使集群恢复正常,这些服务也没有被心跳补偿,依然在某些节点查不到。这个我觉得是需要有兜底方案进行弥补的。

@KomachiSion
Copy link
Collaborator

  1. 超时问题只能根据具体环境具体查看, 我这边所有环境出现此问题时均是那3类问题, 且多数情况时客户端压力导致,其次是网络原因导致,server节点导致的情况很少,且是server节点导致的时候伴随的问题会更多。

  2. 需要查看你的server版本, 如果是老版本,有可能因为revision机制的缺失导致, 新版本中定时的verify会验证revision,数据不一致的情况下会重新触发同步,如果您确认集群绝对部署正确,没有网络和性能异常,那么新版本最终数据会保持一致,至少我运维的大量集群都是如此。

@KomachiSion
Copy link
Collaborator

具体的情况还是得根据部署的环境和环境的具体内容来排查,至少目前最新版本里,Distro协议的数据同步机制没有发现什么太大的问题。可以考虑升级最新版本运行一段时间试试。

@Jack1007
Copy link
Author

Jack1007 commented Dec 17, 2024

  1. 超时问题只能根据具体环境具体查看, 我这边所有环境出现此问题时均是那3类问题, 且多数情况时客户端压力导致,其次是网络原因导致,server节点导致的情况很少,且是server节点导致的时候伴随的问题会更多。

    1. 需要查看你的server版本, 如果是老版本,有可能因为revision机制的缺失导致, 新版本中定时的verify会验证revision,数据不一致的情况下会重新触发同步,如果您确认集群绝对部署正确,没有网络和性能异常,那么新版本最终数据会保持一致,至少我运维的大量集群都是如此。

1、超时问题:我这里超时指的就是nacos服务端节点的MemberReportRequest超时,不是客户端超时。因为服务端member心跳超时导致了healthy server list changed,也就导致了问题2概率性发生。因此这里核心问题还是为啥服务端发送MemberReportRequest心跳的时候会超时。问题2反而不是那么重要了,因为问题1只要足够稳定,问题2发生的概率就很低。但是现在问题1经常发生,grpc 3秒心跳超时,这个并不是一个很重的操作,不知道为啥就频繁超时。服务端版本2.2.3。
一条超时日志:
ERROR Send request fail, request = MemberReportRequest{headers={}, requestId='null'}, retryTimes = 0, errorMessage = java.util.concurrent.TimeoutException: Waited 3000 milliseconds

@Jack1007
Copy link
Author

Jack1007 commented Dec 17, 2024

具体的情况还是得根据部署的环境和环境的具体内容来排查,至少目前最新版本里,Distro协议的数据同步机制没有发现什么太大的问题。可以考虑升级最新版本运行一段时间试试。

目前发现一个环境上的问题点,就是微服务程序(nacos1.2.x客户端)是在k8s内部的,nacos集群(2.2.x)是在k8s的外部服务器的,那微服务的udp端口都是无法收到nacos服务端的push数据的,因为微服务是用k8s内部ip和端口注册的,外部nacos无法访问k8s内部微服务的ip和端口。

@KomachiSion
Copy link
Collaborator

  1. 超时问题只能根据具体环境具体查看, 我这边所有环境出现此问题时均是那3类问题, 且多数情况时客户端压力导致,其次是网络原因导致,server节点导致的情况很少,且是server节点导致的时候伴随的问题会更多。

    1. 需要查看你的server版本, 如果是老版本,有可能因为revision机制的缺失导致, 新版本中定时的verify会验证revision,数据不一致的情况下会重新触发同步,如果您确认集群绝对部署正确,没有网络和性能异常,那么新版本最终数据会保持一致,至少我运维的大量集群都是如此。

1、超时问题:我这里超时指的就是nacos服务端节点的MemberReportRequest超时,不是客户端超时。因为服务端member心跳超时导致了healthy server list changed,也就导致了问题2概率性发生。因此这里核心问题还是为啥服务端发送MemberReportRequest心跳的时候会超时。问题2反而不是那么重要了,因为问题1只要足够稳定,问题2发生的概率就很低。但是现在问题1经常发生,grpc 3秒心跳超时,这个并不是一个很重的操作,不知道为啥就频繁超时。服务端版本2.2.3。 一条超时日志: ERROR Send request fail, request = MemberReportRequest{headers={}, requestId='null'}, retryTimes = 0, errorMessage = java.util.concurrent.TimeoutException: Waited 3000 milliseconds

超时问题一样,server间通信本质也是client和server之间的request和response。只是走了不同的端口和worker线程池。

@KomachiSion
Copy link
Collaborator

具体的情况还是得根据部署的环境和环境的具体内容来排查,至少目前最新版本里,Distro协议的数据同步机制没有发现什么太大的问题。可以考虑升级最新版本运行一段时间试试。

目前发现一个环境上的问题点,就是微服务程序(nacos1.2.x客户端)是在k8s内部的,nacos集群(2.2.x)是在k8s的外部服务器的,那微服务的udp端口都是无法收到nacos服务端的push数据的,因为微服务是用k8s内部ip和端口注册的,外部nacos无法访问k8s内部微服务的ip和端口。

1.x的udp推送只是增加时效性的手段,不是强制依赖,1.x的订阅主要方式是轮询查询,默认每10s会对订阅的服务进行一次查询请求,并和缓存中的数据做对比,存在变化则会回调listener。

如果你现在在排查的问题是server间的数据不一致,可能和这个没有关系,但是不能完全排除, 可以查看naming-server的日志, 看下这个不一致的ip,是不是经常在变化。

@Jack1007
Copy link
Author

具体的情况还是得根据部署的环境和环境的具体内容来排查,至少目前最新版本里,Distro协议的数据同步机制没有发现什么太大的问题。可以考虑升级最新版本运行一段时间试试。

目前发现一个环境上的问题点,就是微服务程序(nacos1.2.x客户端)是在k8s内部的,nacos集群(2.2.x)是在k8s的外部服务器的,那微服务的udp端口都是无法收到nacos服务端的push数据的,因为微服务是用k8s内部ip和端口注册的,外部nacos无法访问k8s内部微服务的ip和端口。

1.x的udp推送只是增加时效性的手段,不是强制依赖,1.x的订阅主要方式是轮询查询,默认每10s会对订阅的服务进行一次查询请求,并和缓存中的数据做对比,存在变化则会回调listener。

如果你现在在排查的问题是server间的数据不一致,可能和这个没有关系,但是不能完全排除, 可以查看naming-server的日志, 看下这个不一致的ip,是不是经常在变化。

嗯,实际表现确实1.x的udp推送机制哪怕有问题,也不会影响服务订阅,这个应该不是导致服务端之间grpc超时的原因。我现在排查的核心问题就是为什么服务端成员之间一个3秒超时的grpc(MemberReportRequest),会经常报超时。等我找到更多信息,再来反馈,谢谢。

@KomachiSion KomachiSion closed this as not planned Won't fix, can't repro, duplicate, stale Jan 2, 2025
@Dario-s-j
Copy link

Dario-s-j commented Jan 3, 2025 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/question Category issues related to questions or problems
Projects
None yet
Development

No branches or pull requests

3 participants