Dubbo源码深度解析(三)

接上篇博客《Dubbo源码深度解析(二)》继续讲。上篇博客主要讲的是Dubbo的SPI机制,以及Dubbo启动流程的初始化中的部分方法,本篇博客将继续讲剩下的方法,以及服务是如何发布出去的,先看看初始化方法中剩下的几个方法,如下:

③ DubboBootstrap#loadRemoteConfigs()方法:

这两个方法,就是从配置中心获取的配置中,找出前缀为:dubbo.registries. 和 dubbo.protocols. ,如供你没有配置,那请忽略,如果你配置了,当然可以获取到,并保存起来,保存逻辑为:

可以知道,最终所有属性都是配置在ConfigManager的configsCache属性中。断点看看结果:

但是断点中看到的结果,为什么key为registry和protocol的不止一个呢?因为我在配置中心的dubbo.properties做了修改,修改如下:

这是为了测试前面说的前缀为 dubbo.registries. 和 dubbo.protocols. 的情况,说白了就是配置多个注册中心和多个协议,这是Dubbo支持的。当然,仅仅在配置中心这样配置还不够,因为这里涉及到了hessian协议,需要在service-provider模块中引入相关的依赖,如下:

XML 复制代码
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-hessian</artifactId>
            <version>2.7.10</version>
        </dependency>

dubbo协议的依赖我在之前已经引入了,因此无需添加它的依赖。同时,由于在配置中心中灵配置了另一个nacos配置中心的地址,因此需要再开一个nacos服务,端口为:8849,Idea配置如下:

重新启动服务提供方,发现在两个nacos服务上,在服务管理>服务列表,都可以查到,服务提供方的服务,先看看端口是8848的nacos服务,如下:

查看详情,结果为:

再看看端口是8849的nacos服务,如下:

查看详情,结果为:

我发现,当前版本的Dubbo,如果使用hessian协议的话,服务无法使用netty4?看了下HessianProtocol,发现绑定服务的是这个属性,代码如下:

只能使用Jetty、Tomcat或者Servlet,看看接口全限定名的文件,也是如此,如下:

那我们现在测一下,由于添加了hessian协议,因此在service-comsumer模块中也要加相关依赖,如下:

XML 复制代码
 <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-rpc-hessian</artifactId>
            <version>2.7.10</version>
        </dependency>

然后指定要使用的协议,再启动服务消费方,调用一个对外暴露的接口:http://localhost:9000/hello/getName/Tim,结果如下:

如果使用dubbo协议呢?跟上面类似,如下:

虽然是报500,原因是我断点导致的超时,不打断点正常调用,是没问题的。至于为什么会调用到这里来,后面详细讲,因为这才是我要讲的重点。

④ DubboBootstrap#checkGlobalConfigs()方法,校验的逻辑,非核心流程,跳过。

⑤ DubboBootstrap#startMetadataCenter()方法:

说白了就是获取元数据的配置信息,通过调用MetadataReportFactory#getMetadataReport()方法,获取MetadataReport对象,并放入MetadataReportInstance的静态属性metadataReports,缓存起来,供后续使用。

元数据中心是dubbo2.7版本之后新增的功能,主要是为了减轻注册中心的压力,将部分存储在注册中心的内容放到元数据中心。元数据中心的数据只是给自己使用的,改动不需要告知对端,比如服务端修改了元数据,不需要通知消费端。这样注册中心存储的数据减少,同时大大降低了因为配置修改导致注册中心频繁通知监听者,从而大大减轻注册中心的压力。因此也可以在元数据中配置注册中心中心。当然要看到其效果,要先注释掉注册中心相关的配置,代码如下:

除此之外,还要加上依赖,如下:

XML 复制代码
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-metadata-report-nacos</artifactId>
            <version>2.7.10</version>
        </dependency>

然后启动服务提供方服务,看看结果:

⑥ DubboBootstrap#startMetadataCenter()方法:

⑦ DubboBootstrap#initEventListener()方法:

到这里为止,DubboBootstrap#initialize()方法算是讲完了,回到DubboBootstrap#start()方法,代码如下:

再看看DubboBootstrap#exportServices()方法,代码如下:

从ConfigManager中取出所有ServiceConfigBase对象,也就是ServiceBean,看看类的继承层级:

ServiceBean是什么时候被放入ConfigManager中的呢?也就是我前面说的,在这里被放入的,代码如下:

回到DubboBootstrap#exportServices()方法,遍历获取到的所有ServiceConfig,调用ServiceConfig#export()方法,代码如下:

看看ServiceConfig#checkAndUpdateSubConfigs()方法,代码如下(代码较长,只截取核心):

到这里为止,已经给ServiceConfig对象设置好了协议的配置和注册中心的配置,这部分配置也是我在DubboConfiguration中配置好了。当然也包括接口,也设置好了,主要是通过interfaceName属性,基于类加载器加载并设置的,而interfaceName属性也是之前在ServiceClassPostProcessor中设置的,具体代码为:

再看看ServiceBean的 ref属性是如何赋值的,这就需要回到ServiceClassPostProcessor#buildServiceBeanDefinition()方法,代码如下:

重点就是给ref设置的属性为RuntimeBeanReference对象,并且名字为"helloServiceImpl",RuntimeBeanReference是Spring提供的,最终给ref属性注入的对象那个肯定不是RuntimeBeanReference,而是在这里做了处理,代码如下(Spring属性注入的部分源码):

有这个调用链路可知,最终获取的是RuntimeBeanReference的beanName,通过beanName从Spring容器中拿到HelloServiceImpl对象,通过反射,赋值给ServiceBean的ref属性。打断点看看,结果如下:

重点来了,也就是ServiceConfig#doExport()方法,代码如下:

回到ServiceConfig#doExportUrls()方法,遍历所有的协议,代码如下:

如果我配置了,是这样的效果:

针对group、version,是可以起到隔离服务的作用的,比如version,如果服务提供方有多个结点的话,可以在服务消费方指定version,服务消费反感发起请求,就会@DubboReference上配置的version找对应的节点,如果找到的节点有多个的话,则会通过负载均衡找到一个节点。再看看ServiceRepository#registerService()方法,代码如下:

再看看ServiceConfig#doExportUrlsFor1Protocol()方法(核心),代码如下(以打断点为主,涉及到的参数太多了,不可能一个个去看):

java 复制代码
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        String name = protocolConfig.getName();
        if (StringUtils.isEmpty(name)) {
            name = DUBBO;
        }
        // 将配置的参数设置到map中,没有配置的,设置默认值
        Map<String, String> map = new HashMap<String, String>();
        map.put(SIDE_KEY, PROVIDER_SIDE);

        ServiceConfig.appendRuntimeParameters(map);
        AbstractConfig.appendParameters(map, getMetrics());
        AbstractConfig.appendParameters(map, getApplication());
        AbstractConfig.appendParameters(map, getModule());
        // remove 'default.' prefix for configs from ProviderConfig
        // appendParameters(map, provider, Constants.DEFAULT_KEY);
        AbstractConfig.appendParameters(map, provider);
        AbstractConfig.appendParameters(map, protocolConfig);
        AbstractConfig.appendParameters(map, this);
        MetadataReportConfig metadataReportConfig = getMetadataReportConfig();
        if (metadataReportConfig != null && metadataReportConfig.isValid()) {
            map.putIfAbsent(METADATA_KEY, REMOTE_METADATA_STORAGE_TYPE);
        }
        if (CollectionUtils.isNotEmpty(getMethods())) {
            for (MethodConfig method : getMethods()) {
                AbstractConfig.appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (CollectionUtils.isNotEmpty(arguments)) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                            }
                                        } else {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    AbstractConfig.appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("Argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            AbstractConfig.appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("Argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }

        if (ProtocolUtils.isGeneric(generic)) {
            map.put(GENERIC_KEY, generic);
            map.put(METHODS_KEY, ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put(REVISION_KEY, revision);
            }

            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if (methods.length == 0) {
                logger.warn("No method found in service interface " + interfaceClass.getName());
                map.put(METHODS_KEY, ANY_VALUE);
            } else {
                map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }

        /**
         * Here the token value configured by the provider is used to assign the value to ServiceConfig#token
         */
        if (ConfigUtils.isEmpty(token) && provider != null) {
            token = provider.getToken();
        }

        if (!ConfigUtils.isEmpty(token)) {
            if (ConfigUtils.isDefault(token)) {
                map.put(TOKEN_KEY, UUID.randomUUID().toString());
            } else {
                map.put(TOKEN_KEY, token);
            }
        }
        //init serviceMetadata attachments
        serviceMetadata.getAttachments().putAll(map);

        // export service
        String host = findConfigedHosts(protocolConfig, registryURLs, map);  // 获取当前服务的ip地址
        Integer port = findConfigedPorts(protocolConfig, name, map);
        // dubbo://192.168.217.1:20880/com.szl.service.api.HelloService?anyhost=true&application=service-provider&bind.ip=192.168.217.1
        // &bind.port=20880&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=com.szl.service.api.HelloService&methods=sayHello
        // &pid=5252&qos.enable=false&release=&server=netty4&side=provider&timestamp=1653840932266
        URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), map);

        // You can customize Configurator to append extra parameters
        if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                .hasExtension(url.getProtocol())) {
            url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
                    .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
        }

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

            // export to local if the config is not remote (export to remote only when config is remote)
            if (!SCOPE_REMOTE.equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            // export to remote if the config is not local (export to local only when config is local)
            if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
                if (CollectionUtils.isNotEmpty(registryURLs)) {
                    for (URL registryURL : registryURLs) {
                        //if protocol is only injvm ,not register
                        if (LOCAL_PROTOCOL.equalsIgnoreCase(url.getProtocol())) {
                            continue;
                        }
                        url = url.addParameterIfAbsent(DYNAMIC_KEY, registryURL.getParameter(DYNAMIC_KEY));
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            if (url.getParameter(REGISTER_KEY, true)) {
                                logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                            } else {
                                logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                            }
                        }

                        // For providers, this is used to enable custom proxy to generate invoker
                        String proxy = url.getParameter(PROXY_KEY);
                        if (StringUtils.isNotEmpty(proxy)) {
                            registryURL = registryURL.addParameter(PROXY_KEY, proxy);
                        }
                        // 生成 invoker对象
                        Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                        DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                        Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                        exporters.add(exporter);
                    }
                } else {
                    if (logger.isInfoEnabled()) {
                        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                    }
                    // 生成代理对象
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);

                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    exporters.add(exporter);
                }

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

我先给@DubboService注解添加以下信息,如下:

因此可以知道,如果不设置scope,则会暴露对外两个地址,一个是本地(测试用,无需调用远程服务),使用的是"injvm"协议;一个是远程,默认使用的是"dubbo"协议。继续往下看:

上图中漏了一点:在调用PROXY_FACTORY.getInvoker()方法中,又执行了:registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())方法,该方法就是将url进行编码,并将其加入到registryURL的export属性中,如下:

限于篇幅,先讲到这里,剩下的内容将在下一篇博客中继续讲解,如有错误,恳请指正,谢谢!

相关推荐
Yeats_Liao3 分钟前
Go语言 实现 TCP 端口扫描器
网络·tcp/ip·golang
IT_陈寒16 分钟前
Redis性能翻倍秘籍:10个99%开发者不知道的冷门配置优化技巧
前端·人工智能·后端
LB211219 分钟前
Redis 黑马skyout
java·数据库·redis
洛小豆23 分钟前
Swagger3学习与实践指南
spring boot·后端·spring cloud
豐儀麟阁贵25 分钟前
Java知识点储备
java·开发语言
hrrrrb31 分钟前
【Spring Security】Spring Security 密码编辑器
java·hive·spring
Victor35632 分钟前
Redis(58)如何配置和查看Redis的慢查询日志?
后端
Victor35634 分钟前
Redis(59)Redis的主从复制是如何实现的?
后端
豐儀麟阁贵34 分钟前
2.3变量与常量
java·开发语言
摇滚侠2 小时前
Spring Boot 3零基础教程,自动配置机制,笔记07
spring boot·笔记·后端