URL

Dubbo用URL 作为配置总线,贯穿整个体系。

  • Dubbo 采用URL 的方法来作为约定的参数类型,被称为公共契约;通过URL 来交流、交互使得代码更加规范化、形成一种统一的格式;
  • 同时,使用URL作为公共约束充分利用了我们对已有概念的印象,通俗易懂并且容易拓展。
参数
protocol: //username:password@host:port/path?key=value&key=value
  • protocol:Dubbo中的各种协议,比如:dubbo、thrift、http
  • username/password:用户名/密码
  • host/port:主机/端口
  • path:接口的名称
  • parameters:参数键值对


总结

  1. 根据 URL 参数判断是本地暴露还是远程暴露 {
    • 本地暴露:通过 exportLocal(), 构建本地 URL 并将 protocol 协议为 inJvm。
    • 远程暴露:通过 RegistryProtocol.export() 注册中配置中心表中
    }
  2. 通过 ProxyFactory.getInvoker() 生成 Invoker 并包装
  3. 通过 protocol.export() 生成 exporter,并将 serviceKey、exporter 存放在 exporterMap 中

源码总结
  1. serviceConfig.export() 加载配置信息,是否可以暴露服务,是否需要延迟注册服务;
  2. 调用doExport() 继续获取配置信息,检查配置
  3. 调用doExportUrls() 将服务的多个协议 暴露注册到多个注册中心
  4. 调用doExportUrlsFor1Protocol 将配置信息存放到map中,构建URL,从URL中获取scope 为空需要调用exportLocall 执行本地暴露、接着执行远程暴露
  5. 本地暴露和远程暴露,都会执行ProxyFactory.getInvoker()、 protocol.export()
  6. 根据ProxyFactory.getInvoker()判断是本地暴露还是远程暴露(有RegistryURL),通过URL的不同protocol协议返回不同的Invoker 对象,例如:injvm、dubbo等协议。这里就解释了为什么需要封装Invoker,因为Invoker 可能多种方式实现例如:本地暴露、远程暴露、集群暴露等
  7. 再通过包装Invoker 传递给protocol.export() ,根据Invoker里面的URL 参数得知具体的protocol 协议,通过Dubbo 的SPI 机制加载具体的实现类,最后返回exporter例如:InjvmProtocol.export()、 DubboProtocol.export()
  8. 并存放ExportMap,key:服务接口的全限定名, value:exporter (包装的Invoker)缓存。这里避免了注册中心宕机带来的印象,会从本地缓存中加载。


服务暴露流程

从代码流程的角度:

  • 检测配置,如果有些配置空的话会默认创建,并且组装成URL ;
  • 暴露服务,暴露到本地服务和远程的服务
  • 注册服务至配置中心

alt


从对象构建转换的角度:

  • 将服务实现类通过Proxy组件转成Invoker
  • 将Invoker通过具体的协议Protocol转换成Exporter,然后将Exporter通过Registry注册到注册中心。

alt


Dubbo 为什么默认使用 javassist

  • 代理:Javassist、JDK代理、ASM、Cglib
  • Javassist代理,简单、快捷、字节码生成方便
  • ASM 比 Javassist 更快,但是没有快一个数量级,而 Javassist 只需用字符串拼接就可以生产字节码,而 ASM 需要手工生成,成本较高,比较麻烦


源码解析过程

serviceConfig.export
  • 加载配置信息,是否可以暴露服务,是否需要延迟注册服务
  • 继续调用doExport方法
  • 配置信息列表
public synchronized void export() {
        if (provider != null) {
            if (export == null) {
              
              // 是否支持暴露服务
                export = provider.getExport();
            }
            if (delay == null) {
              
              // 是否需要延迟注册服务
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {
            return;
        }

        if (delay != null && delay > 0) {
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
            doExport();
        }
    }

doExport
  • 继续获取配置信息,检查配置
  • 继续调用doExportUrls方法
protected synchronized void doExport() {
        if (unexported) {
            throw new IllegalStateException("Already unexported!");
        }
        if (exported) {
            return;
        }
        exported = true;
        
  
        //  加载配置信息
        ···················
          
        checkApplication();
        checkRegistry();
        checkProtocol();
        appendProperties(this);
        checkStubAndMock(interfaceClass);
        if (path == null || path.length() == 0) {
            path = interfaceName;
        }
        doExportUrls();
        ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
        ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
    }

doExportUrls
  • 将服务的多个协议 暴露注册到多个注册中心
  • loadRegistries:根据配置组装成注册中心相关的URL
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2&pid=7960&qos.port=22222&registry=zookeeper&timestamp=1598624821286
  • 继续调用doExportUrlsFor1Protocol方法
private void doExportUrls() {
        
        // 获取当前服务的注册中心,由于是个List可以有多个服务中心
        List<URL> registryURLs = loadRegistries(true);
  
        // 遍历多个协议,每个协议都需要向这些注册中心注册
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

doExportUrlsFor1Protocol
  • 将配置信息存放到map中,构建URL
  • 如果从URL 中获取到scope 为空,需要调用exportLocal 方法进行本地暴露
  • 如果有注册中心会进行远程暴露,有监控中心会添加之后向其汇报
  • 通过ProxyFactory.getInvoker() 将具体的实体类转化成Invoker
  • 通过protocol.export() 将Invoker包装转成Exporter
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
        if (name == null || name.length() == 0) {
            name = "dubbo";
        }

        Map<String, String> map = new HashMap<String, String>();
        map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
        map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
        map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        appendParameters(map, application);
        appendParameters(map, module);
        appendParameters(map, provider, Constants.DEFAULT_KEY);
        appendParameters(map, protocolConfig);
        appendParameters(map, this);
        if (methods != null && !methods.isEmpty()) {
            ···········
        }

  
        //  将配置信息存放到map中,构建URL 
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

  
        ··············
  
 
        String scope = url.getParameter(Constants.SCOPE_KEY);
        // don't export when none is configured
        if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            // export to local if the config is not remote (export to remote only when config is remote)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
              
              
                // scopre 为null,会进行本地暴露
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (registryURLs != null && !registryURLs.isEmpty()) {
                  
                  
                    // 有注册中心会进行远程暴露
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                          
                            // 有监控中心,添加后向其汇报
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
                      
                      
                      
                      
                        //  将具体的实现类转换成Invoker
                        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
                      
                      
                        //  将其包装后转化成exporter
                        Exporter<?> exporter = protocol.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = protocol.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            }
        }
        this.urls.add(url);
    }

exportLocal
  • 构建本地的URL,其中的protocol协议变成 injvm
  • 具体实现类通过ProxyFactory.getInvoker() 转换成Invoker,具体默认使用Javassist生成代理Invoker
  • 本地调用的格式,相比远程暴露多加了RegistryURL,直接调用Invoker即可,通过不同的参数类型返回不同的Invoker
proxyFactory.getInvoker(ref, (Class) interfaceClass, local);
  • Invoker 包装通过Protocol.export() 转化成exporter,会根据Invoker的参数选择不同的实现类,本地暴露会执行InjvmProtocol.export()
@SuppressWarnings({"unchecked", "rawtypes"})
    private void exportLocal(URL url) {
        
        // 构建本地的URL 
        if (!Constants.LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
            URL local = URL.valueOf(url.toFullString())
                    .setProtocol(Constants.LOCAL_PROTOCOL)
                    .setHost(LOCALHOST)
                    .setPort(0);
            ServiceClassHolder.getInstance().pushServiceClass(getServiceClass(ref));
          
            // 将具体实现类 -> invoker -> exporter
            Exporter<?> exporter = protocol.export(
              
                    // 调用本地方法的形式, 相比远程暴露少了registryURL
                    proxyFactory.getInvoker(ref, (Class) interfaceClass, local));
            exporters.add(exporter);
            logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry");
        }
    }
为什么需要本地暴露?
  • 因为可能存在同一个JVM 内部引用自身服务的情况,因此暴露本地服务在内部调用的时候可以直接消费同一个JVM的服务, 避免了网络间的通信

远程暴露
  • 会先根据ProxyFactory.getInvoker() 里面的参数RegistryURL 拼接URL
  • 通过URL的参数,可以得知先通过registry 协议找到RegistryProtocol.export() ,会根据Invoker里面的URL 具体protocol 协议执行不同的具体类,例如export=dubbo:// 会执行DubboProtocol.export()
proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
RegistryProtocol.export()
  1. 将包装的Invoker 转化成 exporter
  2. 获取注册中心的相关配置,如果需要注册则向配置中心注册
  3. 维护一个Map记录服务提供者,key:服务接口全限定名,value:包装过的Invoker
    @Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        // export invoker 根据dubbo:// 的暴露,并且还会打开端口等操作
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);
      
        // 获取注册中心的 URL 比如 zookeeper://127.0.0.1:2181/
        URL registryUrl = getRegistryUrl(originInvoker);

        // registry provider 根据URL 加载 Registry的实现类,比如:zookeeperRegistry
        final Registry registry = getRegistry(originInvoker);
      
        //  获取注册服务提供者的URL
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);

        //to judge to delay publish whether or not
        boolean register = registedProviderUrl.getParameter("register", true);
      
        //  将提供者信息注册到服务提供者与消费者注册表中
        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {
            
            // 向注册中心注册服务
            register(registryUrl, registedProviderUrl);
       ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

      
        // 获取订阅 URL
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
        
        //  创建监听器,监听override
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }
DubboProtocol.export()
  1. 根据Invoker获取 URL,将 key 与 Invoker 关联构造出 exporter 将其存储到 exporterMap 中,然后初始化配置
  2. 调用 openServer() 更新或者创建 server
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        //  获取 URL
        URL url = invoker.getUrl();

        // export service 存放到map中
        String key = serviceKey(url);
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);

        //export an stub service for dispatching event
        ····················
          
        //  打开server
        openServer(url);
        optimizeSerialization(url);
        return exporter;
    }



// openServer
private void openServer(URL url) {
        // find server.
        String key = url.getAddress();
        //client can export a service which's only for server to invoke
        boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
        if (isServer) {
            
            //  通过ip地址获取server
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
              
              
                //  没有则创建createServer
                serverMap.put(key, createServer(url));
            } else {
                // server supports reset, use together with override
                server.reset(url);
            }
        }
    }

proxyFactory.getInvoker
  • 主要作用是封装Invoker对象,因为export()会根据Invoker里面的URL 的protocol参数执行不同的protocol.export()
  • 通过proxyFactory.getInvoker() 里面的具体实现类创建例如:JDK、Javassist、Stub;
  • 实际上由Javassist 创建Invoker ,因为Javassist 动态代理比 JDK 代理快
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper cannot handle this scenario correctly: the classname contains '$'
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName,
                                      Class<?>[] parameterTypes,
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }
为什么要封装成Invoker?
  • 屏蔽调用细节,统一暴露出一个可执行体,这样调用者简单的使用它,向它发起Invoker 调用,它可能是一个本地的实现、远程的实现 或者 集群的实现

protocol.export
  • protocol.export() 标注了@Adaptive 注解,会生成代理类
  • 通过代理类会根据Invoker 里面的URL 参数得知具体的协议,然后通过Dubbo SPI机制选择对应的实现类进行export;
    
    //  Protocol接口
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;


    // InjvmExporter实现类
    @Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
    }