深入理解Dubbo原理系列(三)- Dubbo服务暴露和消费原理

一. Dubbo服务暴露原理解析

不管在服务暴露还是在服务消费的场景下,Dubbo框架都会根据优先级来对配置信息做一个聚合处理, 目前默认的覆盖策略主要遵循以下几点规则:

  1. -D传递给JVM参数的优先级最高,比如-Dubbo.protocol.port=20880
  2. 代码或XML配置优先级次高,比如Spring中XML文件指定<dubbo:protocol port=“20880”>
  3. 置文件优先级最低,比如 dubbo.properties 文件指定 dubbo.protocol.port=20880

此外,只有在XML没有配置的时候,dubbo.properties 配置项才会生效。Dubbo的配置也会受到provider的影响(运行期属性值影响),并遵循以下两点规则:

  1. 如果只有provider端指定配置,则会自动透传到客户端,比如timeout
  2. 如果客户端也配置了相应属性,则服务端配置会被覆盖,比如timeout

紧接着,来看下RPC框架的一个暴露原理:
在这里插入图片描述
整体来看,DUbbo的服务暴露部分分为两个部分:

  1. 将持有的服务实例通过代理转换成Invoker。
  2. 将Invoker通过具体的协议(比如Dubbo框架中的Dubbo协议)转换成Exporter。

我之前的文章提到过,Dubbo中的所有模型都会向Invoker靠拢,向它发起invoke调用,他的实现可能是本地、远程、集群三种,因此,本文服务暴露准备从远程和本地两个角度来讲。

1.1 远程服务的暴露机制

首先,框架进行服务暴露的入口在ServiceConfig这个类中,无论XML还是注解,都会转换成ServiceBean,它继承了ServiceConfig类。在服务暴露前会按照上文的配置覆盖策略进行配置:

  1. 遍历服务的所有方法,若没有值则尝试从-D选项中读取。
  2. 若还没有值,则自动从配置文件dubbo.properties中读取。

1.若配置的服务同时注册了多个注册中心,那么调用ServiceConfig.doExportUrls()方法进行依次暴露:

private void doExportUrls() {
        // 1.获得服务仓库。ServiceRepository是存储了所有服务端发布的服务、客户端需要访问的服务,
        // 通过ServiceRepository可以获取所有Dubbo实例发布的服务和引用的服务。
        // 其中有三种较为重要的属性:services、consumers、providers,类型为ConcurrentMap<String, ServiceDescriptor>
        ServiceRepository repository = ApplicationModel.getServiceRepository();
        // 2.注册当前所需发布的服务到服务仓库中
        // 通过服务接口的Class对象,获取ServiceDescriptor,包含的数据有:接口名,服务接口每个方法的名字、入参类型、返回值类型等详细信息
        ServiceDescriptor serviceDescriptor = repository.registerService(getInterfaceClass());
        // 3.注册当前提供者到服务仓库中
        // 该方法会该方法会创建ProviderModel对象,并将其注册到ServiceRepository中。
        // 之后,dubbo可以访问该providers属性,获取所有服务端发布的服务信息。
        repository.registerProvider(
                getUniqueServiceName(),
                ref,
                serviceDescriptor,
                this,
                serviceMetadata
        );
        // 4.获取服务注册与发现的服务URL列表,返回类型是List说明Dubbo支持多协议。
        List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
        // 5.遍历每个协议,进行服务发布
        for (ProtocolConfig protocolConfig : protocols) {
            // 6.构建pathKey,格式:服务名称/group/version
            String pathKey = URL.buildKey(getContextPath(protocolConfig)
                    .map(p -> p + "/" + path)
                    .orElse(path), group, version);
            // 7.注册解析好的服务到服务仓库中,路径为上述新的一个映射地址pathKey
            repository.registerService(pathKey, interfaceClass);
            // 8.设置当前服务的元数据的服务key
            serviceMetadata.setServiceKey(pathKey);
            // 9.发布当前协议的服务到服务的注册与发现中心
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

2.紧接着来看看具体的发布协议的方法doExportUrlsFor1Protocol(protocolConfig, registryURLs)

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
	// 1.获取协议的名称,若没有配置,默认为dubbo协议
    String name = protocolConfig.getName();
    if (StringUtils.isEmpty(name)) {
        name = DUBBO;
    }
	// 2.创建一个map用来存参数,即服务的配置信息
    Map<String, String> map = new HashMap<String, String>();
    map.put(SIDE_KEY, PROVIDER_SIDE);
	// 3.添加运行时的参数于map中,比如服务的进程id,dubbo版本信息等
    ServiceConfig.appendRuntimeParameters(map);
    AbstractConfig.appendParameters(map, getMetrics());
    // 。。。代码省略,都是参数的添加
    MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
    // 4.如果配置了元数据存储服务地址的话将元数据存储在远程,不存储在本地。
    if (metadataReportConfig != null && metadataReportConfig.isValid()) {
        map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
    }
    // 。。代码省略,添加一些泛化配置到map中
	

    // 5.如果需要使用token来验证当前服务,就将其添加到map中
    if (ConfigUtils.isEmpty(token) && provider != null) {
        token = provider.getToken();
    }
	// 。。代码省略,对token的一些处理
    // 6.将装有所有配置项的map里的数据全部添加到元数据中
    serviceMetadata.getAttachments().putAll(map);

    // 7.获取当前服务的IP、监听的端口等信息,并将这些信息和参数map整合成一个请求URL
    String host = findConfigedHosts(protocolConfig, registryURLs, map);
    Integer port = findConfigedPorts(protocolConfig, name, map);
    URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

    // 8.配置扩展,支持自定义工厂来对配置项进行拦截修改
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }
	// 9.获取当前发布服务所配置的scope属性值,表示当前服务发布的返回,是远程还是本地
    String scope = url.getParameter(SCOPE_KEY);
    // 如果scope 配置为"none" 将不会发布当前服务
    if (!SCOPE_NONE.equalsIgnoreCase(scope)) {

        // 10.如果没有配置为"none" 或者"remote" ,那就发布到本地,若不配置也会发布到本地。
        // 10.此时发布到本的话会将协议改为"injvm", ip改成"127.0.0.1", 端口改为"0",然后再发布
        if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
        	// 10.进行本地服务的暴露
            exportLocal(url);
        }
        // 11.若发布的类型是remote,则将服务信息发布到远程服务中心
        if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
            if (CollectionUtils.isNotEmpty(registryURLs)) {
            	// 12.循环所有处理过的注册中心地址进行发布
                for (URL registryURL : registryURLs) {
                    //如果需要发布的服务协议是injvm就不发布。
                    if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                        continue;
                    }
                    url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                    // 加载dubbo monitor的URL实例
                    URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                    if (monitorUrl != null) {
                    	// 13.如果配置了监控地址,那么服务调用信息会进行上报
                        url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                    }
                    // ...省略

                    // 14.获取当前dubbo服务URL中的proxy参数
                    String proxy = url.getParameter(PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                    }
					// 15.使用ProxyFactory通过动态代理来转换成Invoker
					// 将服务实例ref转换成Invoker,registryURL存储的是注册中心地址,使用export作为key追加服务元数据信息
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
					// 16.服务暴露后向注册中心注册服务信息
                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {
                // ...省略代码
                Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
				// 17.若没有注册中心,则直接暴露服务
                Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                exporters.add(exporter);
            }

            MetadataUtils.publishServiceDefinition(url);
        }
    }
    this.urls.add(url);
}

3.可以发现,最后都是通过Exporter来进行服务的暴露的,前面基本上是对一些参数的配置以及是做出判断(进行本地暴露还是远程暴露),在将服务实例ref转换成Invoker之后,如果有注册中心时则进行更细粒度的控制,比如先进行服务暴露再注册服务元数据。那么接下来看下其具体实现PROTOCOL.export(wrapperInvoker)

// 最终调用的是RegistryProtocol.export()方法
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
	// 1.获取注册中心地址
    URL registryUrl = getRegistryUrl(originInvoker);
    // 2.获取当前接口提供者的地址
    URL providerUrl = getProviderUrl(originInvoker);
    // 3.获取订阅的URL
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
    // 4.创建一个覆盖订阅的监听器,当服务发布完成后,会将上述获取到的订阅的URL进行覆盖。
    // 意义就是当服务重新发布后需要修改注册中心的据,即进行数据的更新
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
	// 5.添加service配置覆盖监听器 以及提供者配置信息覆盖监听器
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
    // 6.发布服务并返回一个发布器的包装类实例,包含Invoker的销毁方法
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // 7.根据注册中心来获取对应的注册器,如Zookeeper、Nacos。  以及获取需要注册到注册中心的提供者的服务地址
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);

    // 8.如果需要注册那就将服务提供者的地址注册到注册中心,
    boolean register = providerUrl.getParameter(REGISTER_KEY, true);
    if (register) {
    	// doLocalExport主要是服务暴露并绑定一个Netty连接,到这里紧接着:连接注册中心进行服务提供者注册
    	// 而相关的register()代码可以看我的另外一篇博客,Dubbo原理二-Zookeeper的,里面有将Zookeeper注册的具体实现
    	// 而这里走的就是dubbo-Zookeeper的一个注册流程(注册中心以Zookeeper为例)
        register(registryUrl, registeredProviderUrl);
    }

    // 9.将已经注册的服务添加到provider model提供者模型中
    registerStatedUrl(registryUrl, registeredProviderUrl, register);
    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);

    // 10.通知服务注册中心进行当前发布的服务的数据进行覆盖,用于处理动态配置
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
	// 11.通知当前的Registry协议的监听器
    notifyExport(exporter);
    // 12.服务销毁收尾工作,比如关闭端口、反注册服务信息等。
    return new DestroyableExporter<>(exporter);
}

4.再来看下上一个步骤中的第六小步:发布服务并返回一个发布器的包装类实例doLocalExport(originInvoker, providerUrl)方法:

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) {
    String key = getCacheKey(originInvoker);
    // 最终会来到DubboProtocol的export方法
    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> {
        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);
        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    });
}

public class DubboProtocol extends AbstractProtocol {
	@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        URL url = invoker.getUrl();
        // 1.根据服务的分组、版本、接口和端口来构造一个key
        String key = serviceKey(url);
        // 2.把exporter存储到单例map中进行缓存
        DubboExporter<T> exporter = new DubboExporter<T>(invoker, key, exporterMap);
        exporterMap.put(key, exporter);
		// ....
		// 务初次暴露会创建监听服务器
        openServer(url);
        optimizeSerialization(url);

        return exporter;
    }

	private void openServer(URL url) {
        //。。
        serverMap.put(key, createServer(url));
        //。。
    }

	 private ProtocolServer createServer(URL url) {
	 	// 1.配置一些服务特性
        url = URLBuilder.from(url)
                // send readonly event when server closes, it's enabled by default
                .addParameterIfAbsent(CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString())
                // enable heartbeat by default
                .addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT))
                .addParameter(CODEC_KEY, DubboCodec.NAME)
                .build();
        // 2.从提供者的地址中获取服务的类型,没有配置的话,默认是netty服务类型
        String str = url.getParameter(SERVER_KEY, DEFAULT_REMOTING_SERVER);
		//...
        try {
        	// 3.创建创建NettyServer并且初始化Handler,主要是绑定服务提供者的线程,搭建一个TCP连接(和注册中心)
            server = Exchangers.bind(url, requestHandler);
        //...
        str = url.getParameter(CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set<String> supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }

        return new DubboProtocolServer(server);
    }
}


1.2 本地服务的暴露机制

本地服务的暴露相对而言比较简单(上文也有提及本地服务的暴露,可以看第二大步的注释10),从ServiceConfig下的exportLocal方法出发:

private void exportLocal(URL url) {
	// 显式的指定injvm协议进行暴露,协议改为"injvm", ip改成"127.0.0.1", 端口改为"0"
    URL local = URLBuilder.from(url)
            .setProtocol(LOCAL_PROTOCOL)
            .setHost(LOCALHOST_VALUE)
            .setPort(0)
            .build();
    // 调用 InjvmProtocol的export方法,主要是把服务保存在内存中
    Exporter<?> exporter = PROTOCOL.export(
            PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, local));
    exporters.add(exporter);
    logger.info("Export dubbo service " + interfaceClass.getName() + " to local registry url : " + local);
}

本地暴露的export主要用的是InjvmProtocol的export方法,实现非常简单:

public class InjvmProtocol extends AbstractProtocol implements Protocol {
	@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
    	// 直接返回 InjvmExporter实例对象
        return new InjvmExporter<T>(invoker, invoker.getUrl().getServiceKey(), exporterMap);
    }
    // 构造函数内部会把当前Invoker加入exporterMap,即本地缓存
    InjvmExporter(Invoker<T> invoker, String key, Map<String, Exporter<?>> exporterMap) {
        super(invoker);
        this.key = key;
        this.exporterMap = exporterMap;
        exporterMap.put(key, this);
    }
}

到这里,肯定是需要进行一个大总结的,否则内容太多看起来也不直观

1.3 Dubbo服务暴露原理总结

对于远程服务暴露,流程如下:

  1. 服务暴露前,根据上下文的配置覆盖策略进行配置。
  2. 服务暴露入口于ServciceConfig.doExportUrls()方法,主要做这么几件事:

2.1 获得服务仓库(存储了所有服务端发布的服务、客户端需要访问的服务)。
2.2 注册当前所需发布的服务(即接口)和服务提供者到服务仓库中。
2.3 获取服务注册与发现的服务URL列表
2.4 遍历每个协议,进行服务发布,根据服务的名称+group+版本号进行服务URL映射并将其作为元数据的服务key。
2.5 发布当前协议的服务到服务的注册与发现中心,调用doExportUrlsFor1Protocol()方法。

  1. doExportUrlsFor1Protocol()方法中,主要做这么几件事:

3.1 获取协议名称,并且创建一个用于存储配置参数的集合map。
3.2 将配置信息、运行时配置、泛化配置、验证信息等加入到map中。
3.3 将当前服务的IP、监听的端口和参数map整合成一个请求URL。
3.4 进行配置的扩展。
3.5 获取当前发布服务所配置的scope属性值,判断当前服务发布返回的是远程还是本地。
----3.5.1 若 scope的值是"none" 或者不是"remote" ,那就发布到本地,请看下面的本地服务暴露流程。
----3.5.2 否则进行远程发布,看3.6↓
3.6 循环所有处理过的注册中心地址进行发布,获取当前dubbo服务URL中的proxy参数
3.7 将服务实例ref通过动态代理的方式转换成Invoker,(包含Exporter)。
3.8 服务暴露后通过Exporter向注册中心注册服务信息,调用PROTOCOL.export(wrapperInvoker)方法

  1. 对于export()方法,远程暴露先调用的是RegistryProtocol.export()方法,主要做五件事(具体看第3、4步中的代码注释):

4.1 委托具体协议(Dubbo)进行服务暴露,创建NettyServer监听端口和保存服务实例。
4.2 创建注册中心对象,与注册中心创建TCP连接(Netty实现)
4.3 注册服务元数据到注册中心。
4.4 订阅configurators节点,监听服务动态属性变更事件通过覆盖订阅的监听器来实现,进行数据覆盖更新)。
4.5 服务销毁收尾工作,比如关闭端口、反注册服务信息等(通过发布器的包装类实例来实现,包含Invoker的销毁方法)。

其实上面还有很多步骤都省略了(都在注释里),对总结在进行精简可得流程如下:

  1. 进行参数配置,然后根据每个服务进行服务暴露。
  2. 将每个服务ref通过动态代理的方式转换成一个Invoker,再通过Invoker来获得Exporter对象。
  3. Exporter进行服务的具体暴露,会通过Netty(默认实现)来和注册中心搭建一个TCP连接,负责监听数据的更新并且进行数据覆盖。
  4. 暴露完成后,再进行元数据的注册(就是往Zookeeper上创建一个节点)。
  5. 进行服务销毁的收尾工作。

对于本地服务暴露,流程如下:

  1. 显式的指定injvm协议进行暴露,协议改为"injvm", IP改成"127.0.0.1", 端口改为"0"
  2. 通过返回InjvmExporter实例对象,将当前Invoker加入exporterMap,进行本地的缓存。

二. Dubbo服务消费原理解析

首先,同样的来看下RPC框架的一个服务消费原理图:
在这里插入图片描述
整体来看,Dubbo做服务消费也分成两个部分:

  1. 通过持有的远程服务实例来生成Invoker,即远程代理对象。
  2. 把Invoker通过动态代理转换成实现用户接口的动态代理引用。

服务消费的入口在ReferenceBean类下的getObject方法中,不管是XML形式还是注解形式,最终都会转换成一个ReferenceBean类,它继承了ReferenceConfig类。并且在服务消费前同样按照文章最开始的覆盖策略进行配置。(该过程形似服务暴露)

1.先来看下ReferenceBean下的getObject()方法:

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
        ApplicationContextAware, InitializingBean, DisposableBean {
	@Override
    public Object getObject() {
        return get();
    }

	public synchronized T get() {
       if (destroyed) {
           throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
       }
       if (ref == null) {
       		// 如果需要注入的dubbo引用为空,那就初始化后再返回ref
           init();
       }
       return ref;
   }
    // 紧接着来看下init()方法:
    public synchronized void init() {
        // 代码省略。。即检查配置和进行配置的加载
        // 1.使用存放配置参数的map去创建一个代理类,然后赋值给ref属性。
        ref = createProxy(map);
		// 代码省略。。
		// 2.修改初始化变量为true:代表当前的服务引用以及初始化完成
        initialized = true;
        checkInvokerAvailable();
        dispatch(new ReferenceConfigInitializedEvent(this, invoker));
    }
}

2.可得最终通过createProxy(map)方法来获得一个代理类:

private T createProxy(Map<String, String> map) {
    // 1.若需要发布injvm协议则发布
    if (shouldJvmRefer(map)) {
        URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
        invoker = REF_PROTOCOL.refer(interfaceClass, url);
        if (logger.isInfoEnabled()) {
            logger.info("Using injvm service " + interfaceClass.getName());
        }
    } else {
        urls.clear();
        // 2.如果是直连,那就进行url处理。
        if (url != null && url.length() > 0) { 
        	// 。。URL的地址处理
        } else { 
        	// 3.如果不需要发布injvm协议,那就载入注册中心的地址
            if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
                // 。。添加集合MAP、URL中的参数
            }
        }
		// 4.如果是单注册中心那就使用注册中心的地址来进行引用,会使用到一个自适应扩展点Protocol
		// 此时由于url是注册中心的地址,因此这里的Invoker是RegistryProtocol实例。
        if (urls.size() == 1) {
            invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
        } else {
        	// 5.如果是多注册中心那就进行循环引用,逐个获取服务并添加到invokers列表中
        	// 通过Cluster将多个Invoker转换成一个Invoker       
        }
    }
	// 6.将服务的元数据进行保存,默认会写入到本地。
    URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
    MetadataUtils.publishServiceDefinition(consumerURL);

    // 7.使用服务引用生成的invoker来构建一个代理类(默认是使用javassist技术来构建)
    return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
}

总结就是:

  1. 前期就是根据配置的参数来决定是直连消费、单/多注册中心消费,再对URL进行对应的处理。
  2. 调用Protocolrefer()方法获得引用Invoker。
  3. 再通过Invoker生成一个代理类。

3.紧接着来看下Protocolrefer()方法:type是接口类型,URL是载入后的注册中心地址。 Protocol是一个自适应扩展点,此时的自适应扩展其实就是被包装的RegistryProtocol实现
在这里插入图片描述
因此来看下RegistryProtocol类的refer方法,主要是触发数据的拉取、订阅和服务Invoker转换等操作

@Override
@SuppressWarnings("unchecked")
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    url = getRegistryUrl(url);
    // 1.根据具体注册中心协议(如Zookeeper)来创建一个具体注册中心的实例
    Registry registry = registryFactory.getRegistry(url);
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // 2.根据配置来处理多分组结果聚合
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
    String group = qs.get(GROUP_KEY);
    if (group != null && group.length() > 0) {
        if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
            return doRefer(Cluster.getCluster(MergeableCluster.NAME), registry, type, url, qs);
        }
    }
	// 获取集群容错策略
    Cluster cluster = Cluster.getCluster(qs.get(CLUSTER_KEY));
    // 3.处理订阅数据,并通过Cluster合并多个Invoker
    return doRefer(cluster, registry, type, url, qs);
}

4.从doRefer(cluster, registry, type, url, qs)方法出发:

public class RegistryProtocol implements Protocol {
	protected <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url, Map<String, String> parameters) {
	    // 获得当前消费者的URL
	    URL consumerUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
	    ClusterInvoker<T> migrationInvoker = getMigrationInvoker(this, cluster, registry, type, url, consumerUrl);
	    // 构造拦截器链
	    return interceptInvoker(migrationInvoker, url, consumerUrl);
	}
	
	protected <T> Invoker<T> interceptInvoker(ClusterInvoker<T> invoker, URL url, URL consumerUrl) {
		// 获得所有注册协议的相关监听器
        List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {
            return invoker;
        }
		// 根据监听器的类型来进行监听器和引用的绑定
        for (RegistryProtocolListener listener : listeners) {
            listener.onRefer(this, invoker, consumerUrl);
        }
        return invoker;
    }
}

listener.onRefer()方法最终会执行到RegistryProtocoldoCreateInvoker()方法:

protected <T> ClusterInvoker<T> doCreateInvoker(DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
	// 设置目录的注册器和协议
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    // 构建一个消费地址例
    Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
    URL urlToRegistry = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
    // 当前的消费者是否需要注册,判断标准是消费端的register配置,默认是需要注册
    if (directory.isShouldRegister()) {
        directory.setRegisteredConsumerUrl(urlToRegistry);
        // 将消费者地址注册到注册中心,如果是zookeeper的话就会进行消费者地址写入
        registry.register(directory.getRegisteredConsumerUrl());
    }
    // 设置服务路由链,根据配置进行设置
    directory.buildRouterChain(urlToRegistry);
    // 订阅当前服务的提供者
    directory.subscribe(toSubscribeUrl(urlToRegistry));
	// 将当前的服务目录加入到集群中并返回一个Invoker
    return (ClusterInvoker<T>) cluster.join(directory);
}

refer()方法如何获得一个Invoker?总结就是:

  1. 通过RegistryProtocol.refer()方法实现数据的拉取、订阅和服务Invoker转换等操作。
  2. 其中根据具体的注册中心协议来创建一个注册中心实例,并且根据容错策略来合并多个Invoker。(可能有多个服务)
  3. 构建当前的消费者URL,并以此获取所有相关的监听器,并根据监听器去进行服务引用的注入
  4. 对于每一个不同的目录,设置基本的服务路由链,并进行当前服务的订阅,并将目录合并成一个集群Cluster中。
  5. 最终返回一个Invoker。

目录:RegistryDirectory,当前接口的服务提供者的目录,里面会维护Invoker列表,每一个invoker就是一个服务提供者节点封装,在订阅的时候根据服务提供者地址上的注册器,转换成每一个Invoker实例存directory中。

那么最后,将得到的invoker通过动态代理的方式来构建一个代理类(默认是使用javassist技术来构建),最终的方法调用则是通过代理类来执行invoke方法。

Dubbo服务消费原理总结

总结:

  1. 服务消费的入口在ReferenceBean类下的getObject方法中,主要负责dubbo引用ref进行初始化,即通过配置参数去创建一个代理类,然后赋值给ref属性。
  2. 调用方法Protocol.refer(),根据接口的类型、载入的注册中心地址进行ref的注入。Protocol是一个自适应扩展点,此时的自适应扩展其实就是被包装的RegistryProtocol实现,即调用RegistryProtocol.refer()方法。
  3. 其中会根据协议类型来创建对应的注册中心实例,并根据容错策略来合并Invoker。
  4. 对于合并的每一个Invoker,通过监听器去进行具体的实现,如设置基本的服务路由链,进行当前服务的订阅等操作。最终合并到Cluster集群中然后返回。
  5. 将服务的元数据进行保存,默认会写入到本地。
  6. 使用服务引用生成的invoker来构建一个代理类。(默认是使用javassist技术来构建)。

精简总结可得:

  1. Dubbo要想调用一个服务,需要对对应服务进行引用ref的初始化。
  2. 初始化则根据接口类型、URL地址的不同来进行属性注入(不同的Invoker)。
  3. 每个Invoker又会进行对应的服务注册和订阅,并将对应服务的元数据进行保存(默认写入本地)。
  4. Dubbo会根据容错策略将多个Invoker(若服务同时配置多个注册中心)合并成一个并返回。
  5. 通过动态代理的方式,通过Invoker返回引用ref,完成初始化。

写的感觉依旧不是很好,若文章写的有问题,还望指出!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值