Dubbo 服务端注册流程

源码分析使用Dubbo2.6.7版本

程序入口

我们现在dubbo的使用大部分开发者会基于spring,让容器来管理我们的对象这样就很方便;

我们使用dubbo的时候会配置相关标签,标签解析入口:

com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

这里我们只需要看service标签的解析逻辑就可以了,当然基础必要的标签我们默认已经配置好了包括:

application,registry,protocol
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));

dubbo使用了   DubboBeanDefinitionParser 来统一做解析所有的标签的前置处理,该类主要是将dubbo特有的标签结构解析成标准的 RootBeanDefinition,简单可以理解成向各种BeanDefinition中addPropertyValue;

其标签处理流程公共的封装,可以大致简单看看,重点还是看各个特有标签的处理流程;

我们看看ServiceBean 的继承关系:

public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
        ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
        ApplicationEventPublisherAware

关键的一点它实现了InitializingBean和 ApplicationListener

,表示在spring容器将该对象实例化并初始化后就会执行 afterPropertiesSet 方法将标签设置的属性设置到bean里,spring refresh完毕后会调用 onApplicationEvent 方法将服务发布到注册中心去:

该方法主要是首先做了一系列的配置校验,最终调用方法

 export();
        

该方法即是发布服务的核心,最终调用父类的

com.alibaba.dubbo.config.ServiceConfig#doExport

然后是一系列的检查,然后是核心方法:

com.alibaba.dubbo.config.ServiceConfig#doExportUrls
    private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

这个方法的意思是,以注册配置为驱动 即配置项:

<dubbo:registry address="${zookeeper.list}" protocol="zookeeper"/>

,再以协议为驱动 即配置项:

    <dubbo:protocol name="dubbo" port="20880"/>
    <dubbo:protocol name="http" port="10086"/>

将所有服务实现注册到注册中心去;例如配置了2类注册中心、3个协议、5个服务类的话,那么会显式地发布 2 x 3 x 5 = 30次(不包括隐式注册的本地服务)

------------------------------------------------------------------------------------------------------------------------

配置文件示例:

---------------------------------------------------------------------------------------------------------------------

debug protocols:

但是一般情况下我们配置一个协议应该就满足业务需求了,如果我们在service标签上配置了其他protocol,会优先使用service标签上的protocols

------------------------------------------------------------------------------------------------------------------------

上图种registryURLs toString结构类似:

registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=1784&qos.port=22222&registry=zookeeper&timestamp=1572960033726

这里表示一个原始的registry标签解析生成的url,在dubbo中url代表了该服务发布所需的所有信息;

这里url是以registry开头的,在通过临时生成的Protocol$Adaptive类处理时会先执行RegistryProtocol的export方法

 

------------------------------------------------------------------------------------------------------------------------

RegistryProtocol

方法来到com.alibaba.dubbo.registry.integration.RegistryProtocol#export

 

标记1:缓存服务连接地址和包装好的ExporterChangeableWrapper

标记2:如果需要注册服务到注册中心(以zk为例),这里会在zk上创建对应的/dubbo/xxxservice/provider 节点

标记3:最终调用的方法:

com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe

这里订阅主要是服务端订阅类似:/dubbo/com.alibaba.dubbo.demo.DemoService/configurators目录下的节点,主要是响应配置信息的改变(比如在dubbo admin里操作了禁用某些服务,或者修改了配置等)

@Override
    protected void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
                String root = toRootPath();
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                if (listeners == null) {
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);
                if (zkListener == null) {
                    listeners.putIfAbsent(listener, new ChildListener() {
                        @Override
                        public void childChanged(String parentPath, List<String> currentChilds) {
                            for (String child : currentChilds) {
                                child = URL.decode(child);
                                if (!anyServices.contains(child)) {
                                    anyServices.add(child);
                                    subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
                                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                                }
                            }
                        }
                    });
                    zkListener = listeners.get(listener);
                }
//创建节点
                zkClient.create(root, false);
                List<String> services = zkClient.addChildListener(root, zkListener);
                if (services != null && !services.isEmpty()) {
                    for (String service : services) {
                        service = URL.decode(service);
                        anyServices.add(service);
                        subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
                                Constants.CHECK_KEY, String.valueOf(false)), listener);
                    }
                }
            } else {
                List<URL> urls = new ArrayList<URL>();
                for (String path : toCategoriesPath(url)) {
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                    if (listeners == null) {
                        zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                        listeners = zkListeners.get(url);
                    }
                    ChildListener zkListener = listeners.get(listener);
                    if (zkListener == null) {
                        listeners.putIfAbsent(listener, new ChildListener() {
                            @Override
                            public void childChanged(String parentPath, List<String> currentChilds) {
                                ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                            }
                        });
                        zkListener = listeners.get(listener);
                    }
//创建节点
                    zkClient.create(path, false);
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    if (children != null) {
                        urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }
                notify(url, listener, urls);
            }
        } catch (Throwable e) {
            throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

这样服务端在zookeeper上的节点就创建完毕了;

SPI

很多人都说:学Dubbo最大的亮点是它的SPI。那么什么是spi呢?spi是一种服务发现机制,我的理解就是dubbo里把很多东西都做了模块化,并且把这些独立的模块取上相应的名字(名字与真实类的关联关系放在配置文件里);

比如:在协议里有个  DubboProtocol,并给该组件取名“dubbo”并保存在配置文件中,在我们使用的时候我们会通过某种(运行时生成一个自适应类,该动态类以名字为参数找到真正的实现类)方式拿到该类;

代码中很多地方可以看到以下代码:

    private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

这便是上面说的自适应类,即在该类加载时动态生成的自适应类

Protocol

Protocol的实例是 Protocol$Adaptive类型的,通过debug我们获取了它的源码(com.alibaba.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass 源码生成是在这个类里生成的,在这里打个断点就可以看到了),该类作用是消费端生成代理和服务端发布服务:

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

/**
 * 协议自适应类
 */
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }

    /**
     * 服务引用
     */
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        // 根据extName找到具体适应类,然后调用方法
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }

    /**
     * 服务暴露
     */
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        // 根据extName找到具体适应类,然后调用方法
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

ProxyFactory

ProxyFactory主要作用是获得消费端的代理对象和生成服务端真实实现类的包装类(Invoker);

ProxyFactory引用的是一个ProxyFactory$Adaptive类实例,通过debug我们获得源码如下(该类主要作用是生成目标类的代理类):

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

/**
 * 代理自适应类
 */
public class ProxyFactory$Adaptive implements com.alibaba.dubbo.rpc.ProxyFactory {
    /**
     * 服务引用获取代理对象
     */
    public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }

    /**
     * 服务暴露获取暴露接口对象
     */
    public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common.URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() + ") use keys([proxy])");
        // 根据extName找到具体适应类,然后调用方法
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
}

DubboProtocol

下面以 DubboProtocol为例分析以下服务发布的流程

正常情况下调用流程(简要说明):

com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer

com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#createServer

com.alibaba.dubbo.remoting.exchange.Exchangers#bind(com.alibaba.dubbo.common.URL, com.alibaba.dubbo.remoting.exchange.ExchangeHandler)

这个方法里会

1、获得Exchanger接口的自适应类即Exchanger$Adaptive,这个自适应类在调用方法的时候会默认(如果没有特殊工指定)去找

com.alibaba.dubbo.remoting.exchange.support.header.HeaderExchanger,因为在Exchanger类里标注了@SPI(HeaderExchanger.NAME)即 HeaderExchanger;

2、调用HeaderExchanger的bind方法

com.alibaba.dubbo.remoting.Transporters#bind(com.alibaba.dubbo.common.URL, com.alibaba.dubbo.remoting.ChannelHandler...)

这个方法里

1、获得Transporter接口的自适应类 Transporter$Adaptive

2、调用bind方法

调用bind方法的时候,Transporter$Adaptive会默认调用 com.alibaba.dubbo.remoting.transport.netty4.NettyTransporter做具体实现

NettyServer构造方法 -> 父类AbstractServer构造方法 -> NettyServer的 doOpen()方法,
至此就可以看到底层netty标准服务端生成和各种handler注入的代码了

StubProxyFactoryWrapper

 ProxyFactory在框架中实例是 StubProxyFactoryWrapper,而 StubProxyFactoryWrapper是对 JavassistProxyFactory和JdkProxyFactory起装饰作用;真实的默认实现是 JavassistProxyFactory

 

 服务端中转对象

待服务发布完成后,框架会将真实服务实现包装成一个Invoker对象,然后再封装缓存起来

1.首先将服务的实现封装成一个Invoker,Invoker中封装了服务的实现类。
2.将Invoker封装成Exporter,并缓存起来,缓存里使用Invoker的url作为key。
3.服务端Server启动,监听端口。(请求来到时,根据请求信息生成key,到缓存查找Exporter,就找到了Invoker,就可以完成调用。)
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dubbo 中,服务和调用都可以设置超时时间,但是它们的含义和设置方式有所不同。 服务超时时间是指服务提供方处理请求的最大时间,如果在这个时间内服务提供方没有返回结果,则 Dubbo 会抛出超时异常。服务超时时间可以通过 `timeout` 属性来设置,例如: ``` <dubbo:service interface="com.example.DemoService" ref="demoServiceImpl" timeout="3000"/> ``` 在上面的示例中,服务提供方处理请求的最大时间为 3 秒钟。 调用超时时间是指服务消费方等待服务提供方返回结果的最大时间,如果在这个时间内服务提供方没有返回结果,则 Dubbo 会抛出超时异常。调用超时时间可以通过 `timeout` 属性来设置,例如: ``` <dubbo:reference id="demoService" interface="com.example.DemoService" timeout="3000"/> ``` 在上面的示例中,服务消费方等待服务提供方返回结果的最大时间为 3 秒钟。 需要注意的是,服务和调用的超时时间是相对的,即服务的超时时间应该大于调用的超时时间,否则服务提供方可能在请求还未处理完毕时就返回结果了,导致调用方得到错误的结果。 综上所述,服务超时时间和调用超时时间的含义和设置方式有所不同,需要根据具体的场景进行设置。同时,服务的超时时间应该大于调用的超时时间,以保证服务提供方能够正常处理请求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值