sofa-rpc icon indicating copy to clipboard operation
sofa-rpc copied to clipboard

AllConnectConnectionHolder心跳重试线程优化讨论

Open shawnliang1124 opened this issue 6 months ago • 0 comments

Your question

Consumer中,ConnectConnectionHolder负责管理所有provider的长连接。 其中,com.alipay.sofa.rpc.client.AllConnectConnectionHolder#startReconnectThread 函数,会启动一个异步线程,每隔一段时间去检查的长连接是否健康。

如果AllConnectConnectionHolder中的aliveConnections(存活的客户端列表)无法连接,就会将该长连接从aliveConnections中移除,加入到retryConnections(失败待重试的客户端列表)中,假如retryConnections中的连接恢复正常,会把该长连接从retryConnections中移除,加回至 aliveConnections中。 这段逻辑见:com.alipay.sofa.rpc.client.AllConnectConnectionHolder#doReconnect 函数。

private void doReconnect() {
 // 省略代码 

        // 检查可用连接 
        for (Map.Entry<ProviderInfo, ClientTransport> alive : aliveConnections.entrySet()) {
            ClientTransport connection = alive.getValue();
            if (connection != null && !connection.isAvailable()) {
                // 加入至retryConnections
                aliveToRetry(alive.getKey(), connection);
            }
        }

//  检查失败待重试的客户端列表
        for (Map.Entry<ProviderInfo, ClientTransport> entry : getRetryConnections()
                .entrySet()) {

            // 省略代码。。
            transport.connect();
        }

    }

如果retryConnection一直没连上, transport.connect();函数底层会调用至com/alipay/sofa/rpc/transport/bolt/ReuseBoltClientConnectionManager#getConnection方法,该方法会打印warn日志: get connection failed in url

正常情况,很难去执行到AllConnectConnectionHolder的重试线程,因为provider从注册中心下线,注册中心会向consumer主动推送,consumer会去摘除该长连接。

但我们在实战中,有两个场景碰到了此类问题,会导致以上get connection failed in url的warn日志疯狂打印,造成困扰。

Your scenes

目前,我们服务都是以容器化部署,若宿主机的资源不足,在重启provider服务的时候,原provider的容器ip可能会发生变化。

情况1:

假如consumer没收到注册中心老provider ip下线节点的通知,就会复现get connection failed in url 疯狂打印的场景。 补充说明:因为宿主机ip分配问题,老的provider ip已经永远消失,provider是以一个新的ip注册到注册中心,但老的provider ip仍一直保留在AllConnectConnectionHolder中的retryConnections中,所以会导致warn日志疯狂打印。

情况2:

注册中心摘除provider节点代码设计不合理。
背景:灰度环境和测试环境集群只有一台provider 通过分析源码,定位到consumer摘除provider下线节点的代码在: com.alipay.sofa.rpc.client.AbstractCluster#updateProviders 函数。

但是该函数做了一个判断:

 public void updateProviders(ProviderGroup providerGroup) {
        // 代码省略。。。


        if (ProviderHelper.isEmpty(providerGroup)) {
            addressHolder.updateProviders(providerGroup);
            if (!ProviderHelper.isEmpty(oldProviderGroup)) {
                if (LOGGER.isWarnEnabled(consumerConfig.getAppName())) {
                    LOGGER.warnWithApp(consumerConfig.getAppName(), "Provider list is emptied, may be all " +
                        "providers has been closed, or this consumer has been add to blacklist");
                    closeTransports();
                }
            }
        } else {
            addressHolder.updateProviders(providerGroup);
            connectionHolder.updateProviders(providerGroup);
        }
         // 代码省略。。。
        }
    }

如果provider中一个节点都没有,只做了 addressHolder.updateProviders(providerGroup);并没有去做 connectionHolder.updateProviders(providerGroup); 而connectionHolder.updateProviders(providerGroup); 底层是会把下线节点从aliveConnections或者retryConnections中删除。 删除的代码在:AllConnectConnectionHolder#remove函数中。

这样会导致,即使consumer收到了provider下线的通知,因为provider在灰度和测试环境只有一台机器,provider重启过程中,就命中了ProviderHelper.isEmpty(providerGroup) 分支,并不会去执行connectionHolder.updateProviders(providerGroup);函数。 该下线的IP一直还在 AllConnectConnectionHolder中的retryConnections属性中。

假如灰度环境或者测试环境的provider ip发生了变化,但老的provider ip仍一直保留在AllConnectConnectionHolder中的retryConnections中,也会导致warn日志疯狂打印。

Your advice

1、建议com.alipay.sofa.rpc.client.AllConnectConnectionHolder#startReconnectThread 机制加上最大重试次数,若失败次数超过x.则将该连接彻底移除。
2、没懂为何provider一台机器没有的情况下,不去执行connectionHolder.updateProviders(providerGroup)函数,希望作者可以解释下如此设计的原因。

Environment

  • SOFARPC version:5.6
  • jdk 8 -windows/mac
  • Maven version:3.9
  • IDE version:2024/01

shawnliang1124 avatar Aug 22 '24 15:08 shawnliang1124