dubbo的disconected问题

本文探讨了Dubbo provider服务中出现的连接断开日志,涉及消费者与提供者间的连接问题。通过分析发现,可能的原因是缓存文件冲突和消费者服务误操作。解决策略包括重启consumer和调整dubbo缓存配置。

现在在dubbo的provider中,出现了这样的日志:

[INFO ] 2017-11-15 10:50:07,790--DubboServerHandler-10.255.242.97:20990-thread-517--[com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol]  [DUBBO] disconected from /10.255.242.96:11582,url:dubbo://10.255.242.97:20990/com.tms.express.service.ServiceA?anyhost=true&application=tms-express-service&channel.readonly.sent=true&codec=dubbo&default.accepts=4000&default.buffer=8192&default.connections=20&default.exporter.listener=apiMonitorProviderExporterListener&default.loadbalance=random&default.payload=88388608&default.queues=0&default.retries=0&default.service.filter=apiMonitorProviderFilter,,&default.threadpool=fixed&default.threads=600&default.weight=100&dubbo=2.8.3.2&generic=false&heartbeat=60000&interface=com.tms.express.service.ServiceA&logger=slf4j&methods=handlePassbackDataSf,orderWeightSchedule&owner=nobody&pid=6694&revision=1.0-SNAPSHOT&side=provider×tamp=1510563828892,dubbo version: 2.8.3.2, current host: 10.255.242.97

先说结论:

  1. 重启consumer服务。传说中的重启试试
  2. 如果一个服务器上部署了多个dubbo服务,最好修改dubbo缓存文件的位置,否则可能会因为争抢文件锁而导致缓存失败。缓存文件默认是/{user.home}/.dubbo/.dubbo/dubbo-registry-{zookeeper地址}.cache,里面记录了zookeeper中的provider和consumer地址,启动脚本里面加入-Ddubbo.registry.file参数,或者在dubbo配置文件中,都可以修改文件存放位置,具体方法网上一大堆。

下面是分析过程:

  1. 日志里中说的是10.255.242.96:11582(consumer服务器)到10.255.242.97:20990(本机,provider服务器)的一个连接断开了,而且指定了service的名字是ServiceA。
  2. provider的日志每2秒报一次,甚至每2秒报很多次,每次的端口都不一样,但是能看到端口号是不断叠加的。
  3. 根据端口号11582并不能确定consumer服务器上具体是哪个dubbo服务和provider断开了连接,因为在consumer服务器上查看这个端口的时候,发现这个端口并没有被使用。
  4. 在consumer服务器上查看到provider服务器的连接,除了正常的dubbo连接之外,还有一个奇怪的端口连接了provider服务器,并且处在TIME_WAIT状态,没有pid。多次netstat之后发现每次处在TIME_WAIT状态的端口号都不一样。
  5. 根据service的名字可以知道具体是哪个dubbo服务,这个服务作为consumer连接了provider,在报这个日志的时候,两个服务之间的通信和调用都是正常的,不存在调不通的问题,系统功能并不受影响。

根据service名字找到的consumer,在日志文件中可以看到这种日志:

[INFO ] 2017-11-15 11:34:54,056--DubboClientReconnectTimer-thread-1--[com.alibaba.dubbo.remoting.transport.netty.NettyClient]  [DUBBO] Close new netty channel [id:0x56b32429, /10.255.242.96:45723 => /10.255.242.97:20990], because theclient closed., dubbo version: 2.8.3.2, current host: 10.255.242.96
[INFO ] 2017-11-15 11:34:54,056--DubboSharedHandler-thread-337--[com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol]  [DUBBO] disconected from /10.255.242.97:20990,url:dubbo://10.255.242.97:20990/com.tms.express.service.ServiceA?anyhost=true&application=tms-kafka-producer&check=false&codec=dubbo&default.accepts=4000&default.buffer=8192&default.connections=20&default.exporter.listener=apiMonitorProviderExporterListener&default.loadbalance=random&default.payload=88388608&default.reference.filter=apiMonitorConsumerFilter,,&default.retries=0&default.service.filter=apiMonitorProviderFilter,,&default.weight=100&dubbo=2.8.3.2&generic=false&heartbeat=60000&interface=com.tms.express.service.ServiceA&logger=slf4j&methods=handlePassbackDataSf,OrderWeightSchedule&owner=nobody&pid=43193&protocol=dubbo&retries=0&revision=1.0-SNAPSHOT&side=consumer&timeout=60000×tamp=1502358440938,dubbo version: 2.8.3.2, current host: 10.255.242.96

这种日志说明,consumer连接了provider,但是又把连接给关了,consumer关连接的时候,provider报了文章最上面的日志。

很不幸的是有时候consumer里找不到同样service的disconected日志,但是provider里的disconected日志依然在刷,只有这一个consumer用到了ServiceA,最后发现情况是这样的:

consumer日志中写的Service名字,和provider日志中写的service名字,不一定一样

我的dubbo服务部署图

provider服务器上有一个dubbo服务,提供了两个service,分别是ServiceA和ServiceB,consumer服务器上有两个dubbo服务,分别是ServiceA和ServiceB的consumer

provider在报ServiceA的disconnect日志,而consumer服务1并没有类似日志,反倒是consumer服务2有这样的日志:

[INFO ] 2017-11-15 11:34:54,056--DubboClientReconnectTimer-thread-1--[com.alibaba.dubbo.remoting.transport.netty.NettyClient]  [DUBBO] Close new netty channel [id:0x56b32429, /10.255.242.96:45723 => /10.255.242.97:20990], because theclient closed., dubbo version: 2.8.3.2, current host: 10.255.242.96
[INFO ] 2017-11-15 11:34:54,056--DubboSharedHandler-thread-337-- [com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol]  [DUBBO] disconected from /10.255.242.97:20990,url:dubbo://10.255.242.97:20990/com.tms.express.service.ServiceB?anyhost=true&application=tms-kafka-producer&check=false&codec=dubbo&default.accepts=4000&default.buffer=8192&default.connections=20&default.exporter.listener=apiMonitorProviderExporterListener&default.loadbalance=random&default.payload=88388608&default.reference.filter=apiMonitorConsumerFilter,,&default.retries=0&default.service.filter=apiMonitorProviderFilter,,&default.weight=100&dubbo=2.8.3.2&generic=false&heartbeat=60000&interface=com.tms.express.service.ServiceB&logger=slf4j&methods=handlePassbackDataSf,OrderWeightSchedule&owner=nobody&pid=12193&protocol=dubbo&retries=0&revision=1.0-SNAPSHOT&side=consumer&timeout=60000×tamp=1502358440938,dubbo version: 2.8.3.2, current host: 10.255.242.96

注意这段日志里写的service名字是ServiceB,和provider里提示的ServiceA并不一样,但是产生的原因是一样的。

重启consumer服务2之后,provider和consumer服务2都没有这种日志了。

我怀疑这种情况是dubbo搞错了

下面是相关的dubbo源码:

一,provider的日志来源:

provider中disconected日志来源于com.alibaba.dubbo.rpc.protocol.dubbo包的DubboProtocol类,这个类里面有个属性ExchangeHandler,里面的部分方法是在属性定义的时候写的:

public class DubboProtocol extendsAbstractProtocol {
   ......省略部分代码
   private ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {
       public Object reply(ExchangeChannel channel, Object message) throwsRemotingException {
           if (message instanceof Invocation) {
                Invocation inv = (Invocation)message;
                Invoker<?> invoker =getInvoker(channel, inv);
                //如果是callback 需要处理高版本调用低版本的问题
                if(Boolean.TRUE.toString().equals(inv.getAttachments().get(IS_CALLBACK_SERVICE_INVOKE))){
                    String methodsStr =invoker.getUrl().getParameters().get("methods");
                    boolean hasMethod = false;
                    if (methodsStr == null ||methodsStr.indexOf(",") == -1) {
                        hasMethod =inv.getMethodName().equals(methodsStr);
                    } else {
                        String[] methods =methodsStr.split(",");
                        for (String method :methods) {
                            if(inv.getMethodName().equals(method)) {
                                hasMethod =true;
                                break;
                            }
                        }
                    }
                    if (!hasMethod) {
                        logger.warn(newIllegalStateException("The methodName " + inv.getMethodName() +" not found in callback service interface ,invoke will be ignored. pleaseupdate the api interface. url is:" + invoker.getUrl()) + ",invocation is :" + inv);
                        return null;
                   }
                }
               RpcContext.getContext().setRemoteAddress(channel.getRemoteAddress());
                return invoker.invoke(inv);
           }
           throw new RemotingException(channel, "Unsupported request: " +message == null ? null : (message.getClass().getName() + ": " +message) + ", channel: consumer: " + channel.getRemoteAddress() +" --> provider: " + channel.getLocalAddress());
       }
 
       @Override
       public void received(Channel channel, Object message) throwsRemotingException {
           if (message instanceof Invocation) {
                reply((ExchangeChannel)channel, message);
           } else {
                super.received(channel,message);
           }
       }
 
       @Override
       public void connected(Channel channel) throws RemotingException {
           invoke(channel, Constants.ON_CONNECT_KEY);
       }
 
       @Override
       public void disconnected(Channel channel) throws RemotingException {
           if (logger.isInfoEnabled()) {
                logger.info("disconectedfrom " + channel.getRemoteAddress() + ",url:" +channel.getUrl());
           }
           invoke(channel, Constants.ON_DISCONNECT_KEY);
       }
 
       private void invoke(Channel channel, String methodKey) {
           Invocation invocation = createInvocation(channel, channel.getUrl(),methodKey);
           if (invocation != null) {
                try {
                    received(channel,invocation);
               } catch (Throwable t) {
                    logger.warn("Failed toinvoke event method " + invocation.getMethodName() + "(), cause:" + t.getMessage(), t);
                }
           }
       }
 
       private Invocation createInvocation(Channel channel, URL url, StringmethodKey) {
           String method = url.getParameter(methodKey);
           if (method == null || method.length() == 0) {
                return null;
           }
           RpcInvocation invocation = new RpcInvocation(method, newClass<?>[0], new Object[0]);
           invocation.setAttachment(Constants.PATH_KEY, url.getPath());
           invocation.setAttachment(Constants.GROUP_KEY,url.getParameter(Constants.GROUP_KEY));
           invocation.setAttachment(Constants.INTERFACE_KEY,url.getParameter(Constants.INTERFACE_KEY));
           invocation.setAttachment(Constants.VERSION_KEY,url.getParameter(Constants.VERSION_KEY));
           if (url.getParameter(Constants.STUB_EVENT_KEY, false)) {
               invocation.setAttachment(Constants.STUB_EVENT_KEY,Boolean.TRUE.toString());
           }
           return invocation;
       }
    }
 
   ...省略部分代码
 
   private void openServer(URL url) {
       // find server.
        String key = url.getAddress();
       //client 也可以暴露一个只有server可以调用的服务。
       boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
       if (isServer) {
           ExchangeServer server = serverMap.get(key);
           if (server == null) {
                serverMap.put(key,createServer(url));
           } else {
                //server支持reset,配合override功能使用
                server.reset(url);
           }
       }
    }
   private ExchangeServer createServer(URL url) {
        //默认开启server关闭时发送readonly事件
       url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY,Boolean.TRUE.toString());
       //默认开启heartbeat
       url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY,String.valueOf(Constants.DEFAULT_HEARTBEAT));
       String str = url.getParameter(Constants.SERVER_KEY,Constants.DEFAULT_REMOTING_SERVER);
 
       if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
           throw new RpcException("Unsupported server type: " + str +", url: " + url);
 
       url = url.addParameter(Constants.CODEC_KEY,Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
       ExchangeServer server;
       try {
           server = Exchangers.bind(url, requestHandler);
       } catch (RemotingException e) {
           throw new RpcException("Fail to start server(url: " + url +") " + e.getMessage(), e);
       }
       str = url.getParameter(Constants.CLIENT_KEY);
       if (str != null && str.length() > 0) {
           Set<String> supportedTypes =ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw newRpcException("Unsupported client type: " + str);
           }
       }
       return server;
    }
}

requestHandler绑定到了provider的url上(DubboProtocol的openServer方法),用于响应dubbo的连接、断开、调用等请求,如果consumer到这个provider的连接断开了,就输出日志(requestHandler的disconnected方法)。

二,consumer的日志来源:

consumer的日志来自com.alibaba.dubbo.remoting.transport.netty包下的NettyClient类,doConnect方法:

   protected void doConnect() throws Throwable {
       long start = System.currentTimeMillis();
       ChannelFuture future = bootstrap.connect(getConnectAddress());
       try {
           boolean ret = future.awaitUninterruptibly(getConnectTimeout(),TimeUnit.MILLISECONDS);
 
           if (ret && future.isSuccess()) {
                Channel newChannel =future.getChannel();
               newChannel.setInterestOps(Channel.OP_READ_WRITE);
                try {
                    // 关闭旧的连接
                    Channel oldChannel =NettyClient.this.channel; // copy reference
                    if (oldChannel != null) {
                        try {
                            if(logger.isInfoEnabled()) {
                               logger.info("Close old netty channel " + oldChannel + "on create new netty channel " + newChannel);
                           }
                            oldChannel.close();
                        } finally {
                           NettyChannel.removeChannelIfDisconnected(oldChannel);
                        }
                    }
                } finally {
                    if(NettyClient.this.isClosed()) {
                        try {
                            if(logger.isInfoEnabled()) {
                               logger.info("Close new netty channel " + newChannel + ",because the client closed.");
                            }
                            newChannel.close();
                        } finally {
                           NettyClient.this.channel = null;
                           NettyChannel.removeChannelIfDisconnected(newChannel);
                        }
                    } else {
                       NettyClient.this.channel = newChannel;
                    }
                }
           } else if (future.getCause() != null) {
                throw newRemotingException(this, "client(url: " + getUrl() + ") failed toconnect to server "
                        + getRemoteAddress() +", error message is:" + future.getCause().getMessage(), future.getCause());
           } else {
                throw newRemotingException(this, "client(url: " + getUrl() + ") failed toconnect to server "
                        + getRemoteAddress() +" client-side timeout "
                        + getConnectTimeout() +"ms (elapsed: " + (System.currentTimeMillis() - start) + "ms)from netty client "
                        +NetUtils.getLocalHost() + " using dubbo version " +Version.getVersion());
           }
       } finally {
           if (!isConnected()) {
                future.cancel();
           }
       }
}

这是consumer发起连接的时候调用的代码,先获取一个新连接,如果原来有个连接,就先把旧连接关掉,替换成新的连接,算是重连。如果新的连接也不能用,就把新的连接也关掉,也就是本文中的情况。

本文中出现的disconect连接我觉得本不该出现,因为dubbo使用长连接,查看了provider和consumer之间的连接数也充足(配置的20个connection),所以除了这些之外的连接都是多余的,而且超过了20个之后再发起连接就只能失败,额外出现的这些试图发起的连接应该是在provider地址列表更新的时候,consumer的缓存文件缓存失败导致的错误。

### ### Dubbo 面试常见问题及答案 #### 1. 什么是 DubboDubbo 是一个高性能、轻量级的开源分布式服务框架,主要用于构建大规模分布式系统中的服务治理。它提供了服务注册与发现、负载均衡、容错处理等核心功能,支持多种协议和序列化方式,适用于微服务架构下的服务通信需求[^1]。 #### 2. 为什么要使用 DubboDubbo 提供了完整的 RPC 通信能力,简化了服务之间的调用流程。其核心优势包括服务注册与发现机制、高效的负载均衡策略、强大的容错机制以及灵活的扩展性,适用于复杂业务场景下的服务治理需求[^1]。 #### 3. Dubbo 提供了哪 3 个关键功能? Dubbo 的三个关键功能是: - **服务注册与发现**:服务提供者将自身注册到注册中心,消费者通过注册中心获取服务提供者的地址信息。 - **负载均衡**:支持多种负载均衡策略,如随机、轮询、最少活跃调用等。 - **容错机制**:如失败重试、快速失败、集群容错等[^1]。 #### 4. Dubbo 服务的关键节点有哪些? Dubbo 的关键节点包括: - **服务提供者(Provider)**:提供服务的实现。 - **服务消费者(Consumer)**:调用服务的一方。 - **注册中心(Registry)**:负责服务的注册与发现。 - **配置中心(Config Center)**:管理服务的配置信息。 - **监控中心(Monitor)**:用于监控服务调用情况。 #### 5. 说一下 Dubbo 服务注册流程? 服务提供方在启动时将自己的服务接口、实现类、服务注册地址等信息注册到注册中心。服务消费方在启动时向注册中心订阅需要的服务。注册中心返回可用的服务提供者地址给服务消费方。服务消费方通过负载均衡算法选择其中一个服务提供者,并向其发起远程调用请求。服务提供者接收到请求后,执行相应的服务实现,并将执行结果返回给服务消费方。服务消费方接收到执行结果后进行处理[^2]。 #### 6. Dubbo 架构的特点? Dubbo 架构具有以下特点: - **模块化设计**:各功能模块解耦,易于扩展。 - **支持多种协议**:如 dubbo://、rmi://、http:// 等。 - **可插拔的注册中心**:支持 ZooKeeper、Nacos、Eureka 等。 - **异步调用**:支持非阻塞调用,提升性能。 - **服务治理能力**:包括负载均衡、容错、服务降级等。 #### 7. Dubbo 的负载均衡策略?默认是? Dubbo 支持以下负载均衡策略: - **Random LoadBalance**(随机):默认策略,按权重随机选择服务提供者。 - **RoundRobin LoadBalance**(轮询):按顺序依次调用。 - **LeastActive LoadBalance**(最少活跃调用):优先调用处理请求最少的服务提供者。 - **ConsistentHash LoadBalance**(一致性哈希):相同请求参数的调用总是发往同一个服务提供者[^1]。 #### 8. Dubbo 服务调用默认是阻塞的?还有其他的? 默认是阻塞的,可以异步调用,没有返回值的可以这么做。Dubbo 是基于 NIO 的非阻塞实现并行调用,客户端不需要启动多线程即可完成并行调用多个远程服务,相对多线程开销较小,异步调用会返回一个 Future 对象[^3]。 #### 9. 服务提供者能实现失效踢出是什么原理? 服务提供者在注册中心注册后,注册中心会定期检测服务提供者的健康状态。如果某个服务提供者在一定时间内没有心跳上报,注册中心会将其从服务列表中剔除,确保消费者不会调用到不可用的服务节点[^3]。 #### 10. Dubbo 有哪些协议?推荐? Dubbo 支持的协议包括: - **dubbo://**:默认协议,基于 TCP 长连接,适用于高并发场景。 - **rmi://**:基于 Java RMI 的协议。 - **http://**:适用于 RESTful 风格的调用。 - **hessian://**:基于 Hessian 协议的二进制序列化方式。 推荐使用 **dubbo://** 协议,适用于大多数服务调用场景[^1]。 #### 11. Dubbo 默认序列化框架?其他的你还知道? Dubbo 默认使用 **Hessian2** 作为序列化框架,其他支持的序列化方式包括: - **JSON** - **Java 原生序列化** - **Fastjson** - **Protobuf** - **Thrift** #### 12. 如何用代码方式绕过注册中心点对点直连? 可以通过在服务消费者的配置中直接指定服务提供者的地址,跳过注册中心的查找流程。示例代码如下: ```java ReferenceConfig<IService> referenceConfig = new ReferenceConfig<>(); referenceConfig.setApplication(new ApplicationConfig("consumer-app")); referenceConfig.setRegistry(new RegistryConfig("empty://")); // 空注册中心 referenceConfig.setProtocol("dubbo"); referenceConfig.setUrl("dubbo://192.168.1.100:20880"); // 直接指定服务地址 IService service = referenceConfig.get(); ```[^1] #### 13. Dubbo 的服务追踪解决方案? Dubbo 提供了与 **Apache Zipkin**、**SkyWalking**、**Pinpoint** 等分布式追踪系统的集成能力,通过在服务调用链中注入 Trace ID 和 Span ID,实现对服务调用路径的可视化追踪[^1]。 #### 14. Dubbo 不维护了吗?DubboDubbox 有什么区别? Dubbo 由 Apache 维护,并且持续更新。Dubbox 是当当网基于 Dubbo 的扩展版本,增加了对 RESTful 协议的支持、Kryo 序列化等特性。Dubbo 官方版本在功能上更为稳定,社区活跃度更高。 #### 15. Dubbo 默认通信框架? Dubbo 默认使用 **Netty** 作为底层通信框架,支持高并发、长连接的 TCP 通信,具有良好的性能和扩展性。 #### 16. Dubbo 协议默认端口号?http 协议默认端口?hessian?rmi? - **dubbo 协议默认端口**:20880 - **http 协议默认端口**:80 - **hessian 协议默认端口**:80 - **rmi 协议默认端口**:1099 #### 17. 自动剔除服务什么原理? 自动剔除服务的原理是基于服务提供者的心跳机制。服务提供者定期向注册中心发送心跳,如果注册中心在设定的时间内未收到心跳,则认为该服务提供者已下线,并将其从服务列表中移除[^3]。 #### 18. 从 `2.0.5` 版本开始,Dubbo 支持通过 x 命令来进行服务治理? 从 `2.0.5` 版本开始,Dubbo 支持通过 **Dubbo Admin** 或 **命令行工具** 进行服务治理,例如动态调整负载均衡策略、服务权重、服务禁用等操作。 #### 19. 如何用命令查看服务列表? 可以通过 Dubbo 提供的命令行工具执行以下命令查看服务列表: ```bash dubbo> ls ``` 该命令会列出当前注册中心中所有已注册的服务接口。 #### 20. Dubbo 框架设计是怎样的? Dubbo 框架采用模块化设计,主要包括以下核心模块: - **Protocol**:定义服务调用的协议。 - **Invoker**:封装服务调用的执行逻辑。 - **Proxy**:生成服务代理对象。 - **Registry**:负责服务注册与发现。 - **Cluster**:实现服务容错与负载均衡。 - **Transporter**:负责底层通信。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值