sofastack / sofa-rpc

SOFARPC is a high-performance, high-extensibility, production-level Java RPC framework.
https://www.sofastack.tech/sofa-rpc/docs/Home
Apache License 2.0
3.82k stars 1.17k forks source link

Docker应用,服务端重启后客户端服务调用失败 #1052

Closed antlers-lv closed 2 years ago

antlers-lv commented 3 years ago

服务端(provider)应用restart后,客户端再次调用时,出现如下错误

com.alipay.sofa.rpc.core.exception.SofaRouteException: RPC-020020009: The service addresses [bolt://192.168.10.82:22201?accepts=100000&appName=metadata&weight=100&language=java&pid=1&interface=cn.amberdata.metadata.facade.CategoryFacade&timeout=3000&serialization=hessian2&protocol=bolt&delay=-1&dynamic=true&startTime=1625212061088&id=rpc-cfg-7&uniqueId=categoryFacade&rpcVer=50706,] of service [cn.amberdata.metadata.facade.CategoryFacade:1.0:categoryFacade] is not available,or specify url not exist in providers

原因 客户端连接池ConnectionPool缓存未被清除,但是ConnectionPool中的connection却已被清除,导致根据ip:port作为key取出的ConnectionPool永远拿不到对应的connection

public Connection getAndCreateIfAbsent(Url url) throws InterruptedException, RemotingException { // get and create a connection pool with initialized connections. ConnectionPool pool = this.getConnectionPoolAndCreateIfAbsent(url.getUniqueKey(), new ConnectionPoolCall(url)); if (null != pool) { return pool.get(); } else { logger.error("[NOTIFYME] bug detected! pool here must not be null!"); return null; } }

处理方式 当类ReuseBoltClientConnectionManager.getConnection()时,如果未获取到Connection时,清除ConnectionPool的缓存,确保不会出现ConnectionPool的缓存中没有对应的Connection

OrezzerO commented 3 years ago

你好,你这边的 SOFARPC 版本是多少, 路由方式是通过注册中心还是直连?

antlers-lv commented 3 years ago

你好,你这边的 SOFARPC 版本是多少, 路由方式是通过注册中心还是直连?

你好,我这边SOFARPC的版本是3.4.6

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

konieshadow commented 2 years ago

同样的问题。客户端和服务端在同一主机上部署,服务端重启后,客户端无法重新连接。仅当客户端使用Docker部署且使用host主机的内网ip进行直连时才会出现。

复现条件 sofa-rpc版本:5.8.3和5.7.6 路由方式:ip:port 直连,类似 172.16.120.9:9997 客户端部署方式:docker部署 服务端部署方式:docker或tomcat直接部署

seeflood commented 2 years ago

@konieshadow 我帮你重新打开issue了,你的报错信息和楼主一样么?最好能提供下所有日志哈 是一开始就调不通 还是服务端重启了之后才调不通? 另外调不通的时候,可以先试下能在机器上ping到目标机器、能正常telnet么

konieshadow commented 2 years ago

我照着官方的testReconnect写了一个单元测试的case,基本可以复现问题。 源代码:github.com/konieshadow/rpc-test Docker镜像:konieshadow/rpc-test:latest

服务端监听在0.0.0.0,客户端首先使用外网IP直连。以下是具体的执行步骤: 一、启动容器,使用ECS的外网IP作为客户端连接的HOST。

docker run --rm --name rpc-test -it -e SERVER_HOST=47.241.x.x -p 22221:22221 konieshadow/rpc-test bash

二、在上一步执行后进入的容器控制台中执行:

mvn test

最终可以看到测试完成,日志输出:

Listen host is 0.0.0.0
Server host is 47.241.x.x
call first time
stop server
call second time
restart server
call third time
finish

此为期望的正确结果。

退出容器后,使用内网IP进行测试。以下是具体的执行步骤。 一、启动容器,使用ECS的内网IP作为客户端连接的HOST。

docker run --rm --name rpc-test -it -e SERVER_HOST=172.18.x.x -p 22221:22221 konieshadow/rpc-test bash

二、在上一步执行后进入的容器控制台中执行:

mvn test

最终可以看到测试完成,日志输出:

Listen host is 0.0.0.0
Server host is 172.18.x.x
call first time
stop server
call second time
restart server
call third time
com.alipay.sofa.rpc.core.exception.SofaRouteException: RPC-020020009: The service addresses [bolt://172.18.x.x:22221,] of service [com.example.rpctest.HelloService:1.0] is not available,or specify url not exist in providers

看起来像是使用内网IP直连(实际上经过了Docker网络端口映射)后,服务端重启后客户端并未自动连接。

为了验证是否是因为Docker端口映射的问题,改为直接使用host网络。以下是具体的执行步骤。 一、启动容器,使用ECS的外网IP作为客户端连接的HOST。

docker run --rm --name rpc-test -it -e SERVER_HOST=172.18.x.x --network host konieshadow/rpc-test bash

二、在上一步执行后进入的容器控制台中执行:

mvn test

最终可以看到测试完成,日志输出:

Listen host is 0.0.0.0
Server host is 172.18.x.x
call first time
stop server
call second time
restart server
call third time
finish

表明在不经过Docker网络端口映射的情况下,不会出现重连失败的问题。

我无力去跟随sofarpc的源码进行调试,只能通过不断测试来验证问题。如果可以的话,希望开发者重现一下上述过程。 多谢!

konieshadow commented 2 years ago

@konieshadow 我帮你重新打开issue了,你的报错信息和楼主一样么?最好能提供下所有日志哈 是一开始就调不通 还是服务端重启了之后才调不通? 另外调不通的时候,可以先试下能在机器上ping到目标机器、能正常telnet么

回答一下您的问题。 1、客户端报错日志:

com.alipay.sofa.rpc.core.exception.SofaRouteException: RPC-020020009: The service addresses [172.16.x.x:9997,] of service [com.xxx.xxx.xxx.XxxService:1.0] is not available,or specify url not exist in providers  
    at com.alipay.sofa.rpc.client.AbstractCluster.unavailableProviderException(AbstractCluster.java:512)
    at com.alipay.sofa.rpc.client.AbstractCluster.select(AbstractCluster.java:465)
    at com.alipay.sofa.rpc.client.FailoverCluster.doInvoke(FailoverCluster.java:67)
    at com.alipay.sofa.rpc.client.AbstractCluster.invoke(AbstractCluster.java:298)
    at com.alipay.sofa.rpc.client.ClientProxyInvoker.invoke(ClientProxyInvoker.java:83)
...

2、客户端首次连接服务端时(两个docker容器按照任意顺序先后启动)是可以正常调用的,当服务端重启后,客户端一直报上面的错误。此时服务端应该是正常启动了的,因为当客户端再次重启时会自动连接服务端且恢复正常。

3、内网和外网的端口都开放了,测试telnet rpc的端口是通的。

谢谢!

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

seeflood commented 2 years ago

@EvenLjj 看看哈

heyhpython commented 1 year ago

@antlers-lv 这个问题麻烦问下解决了吗?我看issue被关闭了 但是没有贴出fix的mr

antlers-lv commented 2 months ago

@heyhpython 我自己修改了ReuseBoltClientConnectionManager的getConnection方法哈,代码贴下面了。 修改位置为注释“主动关闭连接”那一行

public Connection getConnection(RpcClient rpcClient, ClientTransportConfig transportConfig, Url url) {
        if (rpcClient == null || transportConfig == null || url == null) {
            return null;
        }
        Connection connection = urlConnectionMap.get(transportConfig);
        if (connection != null && !connection.isFine()) {
            closeConnection(rpcClient, transportConfig, url);
            connection = null;
        }
        if (connection == null) {
            try {
                connection = rpcClient.getConnection(url, url.getConnectTimeout());
            } catch (Exception e) {
                LOGGER.warn("get connection failed in url," + url);
            }
            if (connection == null) {
                // 主动关闭连接
                rpcClient.closeConnection(url);
                return null;
            }
            // 保存唯一长连接
            Connection oldConnection = urlConnectionMap.putIfAbsent(transportConfig, connection);
            if (oldConnection != null) {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Multiple threads init ClientTransport with same key:" + url);
                }
                //only if new connection is not equals old connection,we can close it
                if (connection != oldConnection) {
                    rpcClient.closeStandaloneConnection(connection); //如果同时有人插入,则使用第一个
                    connection = oldConnection;
                }
            } else {

                // 增加计数器
                AtomicInteger counter = connectionRefCounter.get(connection);
                if (counter == null) {
                    counter = new AtomicInteger(0);
                    AtomicInteger oldCounter = connectionRefCounter.putIfAbsent(connection, counter);
                    if (oldCounter != null) {
                        counter = oldCounter;
                    }
                }
                int currentCount = counter.incrementAndGet();
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Bolt client transport {} of {}, current ref count is: {}", url.toString(),
                        NetUtils.channelToString(connection.getLocalAddress(), connection.getRemoteAddress()),
                        currentCount);
                }
            }
        }
        return connection;
    }