Dubbo服务发布与注册

  • Post author:
  • Post category:其他


Dubbo服务导出过程开始于Spring容器的刷新事件,Dubbo在接收到事件后,会立即执行服务导出逻辑。

整个导出逻辑大体分为三部分:

  • 前置工作:主要是配置检查和组装URL
  • 导出服务:包含导出本地服务和远程服务
  • 向注册中心注册服务,用于服务的发现

具体实现:


main()方法启动后:根据20880端口(默认端口号),发布一个远程调用的地址:dubbo//:192.168.1.1:20880/interface?...(发布到注册中心)


上述的这个过程主要做了一下的事情:

  • 解析配置文件–>

    dubbo

    是基于

    spring

    标签进行扩展的
  • 暴露服务–>本地服务(以

    injvm

    开头的URL)和远程服务
  • 启动

    netty

    –>提供

    netty

    服务,暴露端口
  • 连接

    zk


  • zk

    注册地址
  • 监听

    zk



1、前置工作



1.1 解析配置并检查


spring

里面提供了很多扩展


dubbo-config-spring

模块里面提供了配置文件的扩展


spring

提供了两个可扩展的:

NamespaceHandlerSupport 和 BeanDefinitionParser


根据

SPI

扩展机制进入

DubboNamespaceHandler

解析配置文件,进入到

serviceBean



ServiceConfig

中,开始进行导出服务的前置工作;通过

onApplicationEvent

监听事件,检查服务是否发布,调用

export()

导出服务,以下是对

export()

的分析:

  • 检查

    <dubbo service: interface>

    的属性是否合法
  • 检查

    providerConfig 和 ApplicationConfig

    等核心配置类对象是否为空,为空则出其他配置类获取对应的实例
  • 检查并处理泛化服务和普通服务
  • 检查本地存根配置


  • ApplicationConfig 和 RegisterConfig

    等配置类进行检查,为空则尝试创建,无法创建则抛出异常



多协议多注册中心的导出服务

private void doExportUrls(){
		//加载注册中连接
		List<URL> registryURLs = loadRegistries(true);
		// 遍历 protocols,并在每个协议下导出服务
	    for (ProtocolConfig protocolConfig : protocols) {
	        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
	    }
 }



1.3 组装URL

走到这一步url大概是这个样子: registry://192.168.22.130:2181/org.apache.dubbo.registry.RegistryService/……

这一步是根据配置信息组装url, 而且url驱动流程的执行,会再各个模块之间一直往下传,后续会不断修改URL头或协议地址,并根据url地址中的信息做相应的处理;

在doExportUrlsFor1Protocol方法中做了url的组装处理,通过反射的方式获取到版本、时间戳、方法名以及各种配置对象的字段信息,然后放入map, 贴一下伪代码帮助理解:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    // 获取 ArgumentConfig 列表
    for (遍历 ArgumentConfig 列表) {
        if (type 不为 null,也不为空串) {    // 分支1
            1. 通过反射获取 interfaceClass 的方法列表
            for (遍历方法列表) {
                1. 比对方法名,查找目标方法
                2. 通过反射获取目标方法的参数类型数组 argtypes
                if (index != -1) {    // 分支2
                    1. 从 argtypes 数组中获取下标 index 处的元素 argType
                    2. 检测 argType 的名称与 ArgumentConfig 中的 type 属性是否一致
                    3. 添加 ArgumentConfig 字段信息到 map 中,或抛出异常
                } else {    // 分支3
                    1. 遍历参数类型数组 argtypes,查找 argument.type 类型的参数
                    2. 添加 ArgumentConfig 字段信息到 map 中
                }
            }
        } else if (index != -1) {    // 分支4
            1. 添加 ArgumentConfig 字段信息到 map 中
        }
    }
}



2. 导出Dubbo服务

准备工作做完了,接下来进行服务导出。服务导出分为导出到本地JVM和导出到远程。

代码根据 url 中的 scope 参数决定服务导出方式,分别如下:

scope = none,不导出服务

scope != remote,导出到本地

scope != local,导出到远程

服务发布的本质就是把export的每个服务转为一个对应的Invoker可执行体,然后把转换后的Invoker都放到一个exporterMap(key,invoker)集合中:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
    
    // 省略无关代码
    
    if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
            .hasExtension(url.getProtocol())) {
        // 加载 ConfiguratorFactory,并生成 Configurator 实例,然后通过实例配置 url
        url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
    }

    String scope = url.getParameter(Constants.SCOPE_KEY);
    // 如果 scope = none,则什么都不做
    if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
        // scope != remote,导出到本地
        if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
            exportLocal(url);
        }

        // scope != local,导出到远程
        if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
            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 = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                    }

                    String proxy = url.getParameter(Constants.PROXY_KEY);
                    if (StringUtils.isNotEmpty(proxy)) {
                        registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
                    }

                    // 为服务提供类(ref)生成 Invoker
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
                    // DelegateProviderMetaDataInvoker 用于持有 Invoker 和 ServiceConfig
                    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);
}

不管是导出到本地,还是远程。进行服务导出之前,均需要先创建 Invoker,这是一个很重要的步骤。因此下面先来分析 Invoker 的创建过程。



2.1 invoker创建过程:

在 Dubbo 中,Invoker 是一个非常重要的模型。在服务提供端,以及服务引用端均会出现 Invoker。Dubbo 官方文档中对 Invoker 进行了说明,这里引用一下。

Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现。

Invoker由ProxyFactory创建而来,Dubbo默认的ProxyFactory实现类是javassistProFactory。来看一下javassistProFactory类创建Invoker的过程:

public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
  // 为目标类创建 Wrapper
    final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
    // 创建匿名 Invoker 类对象,并实现 doInvoke 方法。
    // 
    return new AbstractProxyInvoker<T>(proxy, type, url) {
        @Override
        protected Object doInvoke(T proxy, String methodName,
                                  Class<?>[] parameterTypes,
                                  Object[] arguments) throws Throwable {
      // 调用 Wrapper 的 invokeMethod 方法,invokeMethod 最终会调用目标方法
            return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
        }
    };
}

重写后的doInvoke逻辑比较简单,仅仅是将调用请求转发给了Wrapper类的invokeMethod,Wrapper用于“包裹”目标类,Wrapper是一个抽象类,仅可通过getWrapper方法传入的Class对象进行解析,拿到类的信息,生成invokeMethod方法和其他方法代码,代码生成完毕之后,通过javassist生成Class对象,最后再通过反射创建Wrapper实例。

 public static Wrapper getWrapper(Class<?> c) { 
    while (ClassGenerator.isDynamicClass(c))
        c = c.getSuperclass();

    if (c == Object.class)
        return OBJECT_WRAPPER;

    // 从缓存中获取 Wrapper 实例
    Wrapper ret = WRAPPER_MAP.get(c);
    if (ret == null) {
        // 缓存未命中,创建 Wrapper
        ret = makeWrapper(c);
        // 写入缓存
        WRAPPER_MAP.put(c, ret);
    }
    return ret;
}

未完待续。。。



版权声明:本文为wanghuawei19930812原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。