Dubbo学习记录(十) --- 服务导出之处理注册中心配置 与 浅谈构造器模式使用

服务导出

Dubbo服务导出大体流程

  1. Dubbo的每个Service注解都对应一个ServiceBean对象,每个ServiceBean对象都实现了Spring的ApplicationListener接口,当Spring项目启动结束后,会触发一个上下文刷新事件ContextRefreshEvent事件, 触发的方法是onApplicationEvent方法, ServiceBean的onApplicationEvent方法中,会调用ServiceBean#export(), 再去调用父类ServiceConfig#export()方法进行服务导出
  2. 会确定每个ServiceBean的配置参数,像timeout, protocol等, 这些参数的配置可以在以下的地方进行配置;
    2.1 JVM环境运行参数, 通过-D的方式来指定参数
    2.2 配置中心可以配置 项目的全局配置和应用配置, 相当于一个分布式共享的Dubbo.properties文件;
    2.3 @Service注解可以指定参数;
    2.4 项目中, 指定的dubbo.properties配置文件;
    这么多个地方可以指定配置参数, 在服务导出时,就需要确定每个Service使用的是哪些配置参数,即确定优先级最高的参数;
  3. 加载确定注册中心信息;
  4. 根据配置的Protocol协议启动容器, Dubbo协议对应的Netty, http对应的是tomcat/jetty, 并在接收请求过程中,根据我们的配置接收请求, 如超时事件, 最大连接数等;
  5. 容器启动完后,每个ServiceBean的服务配置会注册到注册中心,同时会注册监听服务配置,监听配置的变更;
    主要就是这几个过程, 更简单点 确定服务配置参数 => 启动容器 => 注册到注册中心;
    public synchronized void export() {
    
    
    	//确定服务参数
        checkAndUpdateSubConfigs();

        // 检查服务是否需要导出
        if (!shouldExport()) {
    
    
            return;
        }

        // 检查是否需要延迟发布
        if (shouldDelay()) {
    
    
            DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
        } else {
    
    
            // 导出服务
            doExport();
        }
    }

服务导出思路

服务导出要做的几件事情:

  1. 确定服务的参数
  2. 确定服务支持的协议
  3. 构造服务最终的URL
  4. 根据服务支持的不同协议,启动不同的Server,用来接收和处理请求
  5. 服务URL注册到注册中心去
  6. 因为Dubbo支持动态配置服务参数,所以服务导出时还需要绑定一个监听器Listener来监听服务的参数是否有修改,如果发现有修改,则需要重新进行导出

确定服务的参数

上篇博客单独写了一篇,不重复描述了。
简单概述一下流程:

  1. 补全参数;
  2. 刷新配置参数;
    2.1 获取配置中心的配置参数,例如配置中心地址;并刷新配置中心的参数
    2.2 获取配置中心的全局配置和应用配置,放入Environment的appExternalConfiuration和externalConfiguration两种Map属性中;
    2.3 刷新所有Dubbo配置类的参数; ==>ApplicationConfig, MonitorConfig,ModuleConfig,ProtocolConfig, RegistryConfig, ProviderConfig, ConsumerConfig
    (注意:在刷新参数过程中,指定服务参数的优先级别, JVM环境配置 > 配置中心App应用配置 > 配置中心全局配置 > 注解参数配置 > dubbo.properties文件配置)
  3. 创建设置provider配置类,由ConfigManager管理
  4. 创建设置Protocol配置类, 由Configmanager管理
  5. 创建设置Application, 由ConfigManager管理;
  6. @Service注解代表的ServiceConfig参数的刷新,这时候设置的配置参数值全都是最高优先级的。
  7. 一些Mock, Stub等的配置;

处理注册中心配置

项目可能会配置多个注册中心, Dubbo会将这些注册中心配置转换为一个个的RegistryConfig。 在确定服务配置参数阶段,每个RegistryConfig获取了最高优先级别的参数;
目的: 处理RegistryConfig, 将每个注册中心配置解析为一个个的URL, 最后返回一个URL集合;

private void doExportUrls() {
    
    
        // 得到url,注册服务也是一个服务,所以也会有对应的url,通过调用该url完成服务注册
        List<URL> registryURLs = loadRegistries(true);   //
		
		//...后面的也是重点代码,但与注册中心处理无关;
    }

loadRegistries(true)

工作:

  1. 遍历注册中心配置类;
  2. 注册中心配置类若没有设置地址address, 则使用匿名地址0.0.0.0
  3. 注册中心配置类的地址若为 “N/A”, 则跳出循环, 执行下一个配置类;
  4. 注册中心配置类的地址不为"N/A",则
    4.1 获取ApplicationConfig的参数, 放入map中; (通过遍历Method方法)
    4.2 获取RegistryConfig的参数, 放入map中; (通过遍历Method方法)
    注册中心配置类里面 包装了用户的姓名username, 密码password 都会放入map;
    4.3 添加path的值,固定为RegistryService.class.getName();
    4.4 获取Dubbo版本信息, 放入map中;
    4.5 构造URL,通过地址 + 参数(UrlUtils.parseURLs(address, map));
    4.6 传入protocol参数值, 以及URL的协议名称,构建最终URL;
    4.7 从URL中获取provider的register的值, true注册,false反之; 消费者,判断subscribe的值,true订阅,false不订阅注册中心的煮熟;
    protected List<URL> loadRegistries(boolean provider) {
    
    
        // check && override if necessary
        List<URL> registryList = new ArrayList<URL>();
        if (CollectionUtils.isNotEmpty(registries)) {
    
    
            for (RegistryConfig config : registries) {
    
    
                String address = config.getAddress();
                // 如果注册中心没有配地址,则地址为0.0.0.0
                if (StringUtils.isEmpty(address)) {
    
    
                    address = ANYHOST_VALUE;
                }
                // 如果注册中心的地址不是"N/A"
                if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
    
    
                    Map<String, String> map = new HashMap<String, String>();
                    // 把application中的参数放入map中,注意,map中的key是没有prefix的
                    appendParameters(map, application);
                    // 把config中的参数放入map中,注意,map中的key是没有prefix的
                    // config是RegistryConfig,表示注册中心
                    appendParameters(map, config);
                    // 此处path值固定为RegistryService.class.getName(),因为现在是在加载注册中心
                    map.put(PATH_KEY, RegistryService.class.getName());
                    // 把dubbo的版本信息和pid放入map中
                    appendRuntimeParameters(map);

                    // 如果map中如果没有protocol,那么默认为dubbo
                    if (!map.containsKey(PROTOCOL_KEY)) {
    
    
                        map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
                    }

                    // 构造注册中心url,地址+参数
                    List<URL> urls = UrlUtils.parseURLs(address, map);

                    for (URL url : urls) {
    
    
                        url = URLBuilder.from(url)
                                .addParameter(REGISTRY_KEY, url.getProtocol())
                                .setProtocol(REGISTRY_PROTOCOL)
                                .build();

                        // 这里是服务提供者和服务消费者区别的逻辑
                        // 如果是服务提供者,获取register的值,如果为false,表示该服务不注册到注册中心
                        // 如果是服务消费者,获取subscribe的值,如果为false,表示该引入的服务不订阅注册中心中的数据
                        if ((provider && url.getParameter(REGISTER_KEY, true))
                                || (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
    
    
                            registryList.add(url);
                        }
                    }
                }
            }
        }
        return registryList;
    }

UrlUtils.parseURLs(address, map)

目的: 根据address 和 参数 初步创建一个URL; 如果address 格式 如: “192.168.2.110;192.168.2.111”,则会解析为两个URL;

	//REGISTRY_SPLIT_PATTERN = ";"
    public static List<URL> parseURLs(String address, Map<String, String> defaults) {
    
    
        if (address == null || address.length() == 0) {
    
    
            return null;
        }
        String[] addresses = REGISTRY_SPLIT_PATTERN.split(address);
        if (addresses == null || addresses.length == 0) {
    
    
            return null; //here won't be empty
        }
        List<URL> registries = new ArrayList<URL>();
        for (String addr : addresses) {
    
    
            registries.add(parseURL(addr, defaults));
        }
        return registries;
    }

遍历ProtocolConfig协议

  1. 获取注册中心的URL集合;
  2. 遍历ProtocolConfig协议;
  3. 获取PathKey, 格式: group / contextPath / path :version; contextPath 为应用名, path为全路径名;
  4. 创建ProvicerModel实例, 包装了路径pathkey, 服务实现类, 接口类信息;ApplicationModel表示应用中服务接口以及接口实现类 提供了 一种服务;
  5. 以pathKey为 key, providerModel实例为value,放入缓存PROVIDER_SERVICES的Map中
  6. 每种协议导出一个单独的服务,注册到各个注册中心,一个服务对应一个或者多个协议;
	private List<ProtocolConfiguration> protocols;
	
    private void doExportUrls() {
    
    
        List<URL> registryURLs = loadRegistries(true);  
        for (ProtocolConfig protocolConfig : protocols) {
    
    
            //获取pathKey
            String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
			//创建ProviderModel实例
            ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
            //放入缓存
            ApplicationModel.initProviderModel(pathKey, providerModel);
			//一个服务,对应多种协议,每种一些导出一次,注册到注册中心;
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }

浅谈构造器模式与使用

大体概念:建造者模式 是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,
用户不需要知道内部的具体构建细节。

	url = URLBuilder.from(url).addParameter(REGISTRY_KEY, url.getProtocol()).setProtocol(REGISTRY_PROTOCOL).build();
	//获取参数, 创建构造器对象;
    public static URLBuilder from(URL url) {
    
    
        String protocol = url.getProtocol();
        String username = url.getUsername();
        String password = url.getPassword();
        String host = url.getHost();
        int port = url.getPort();
        String path = url.getPath();
        Map<String, String> parameters = new HashMap<>(url.getParameters());
        return new URLBuilder(
                protocol,
                username,
                password,
                host,
                port,
                path,
                parameters);
    }
    //添加参数,返回的还是自身实例
     public URLBuilder addParameter(String key, String value) {
    
    
        if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) {
    
    
            return this;
        }
        // if value doesn't change, return immediately
        if (value.equals(parameters.get(key))) {
    
     // value != null
            return this;
        }

        parameters.put(key, value);
        return this;
    }
    //设置协议参数, 返回的还是自身实例
    public URLBuilder setProtocol(String protocol) {
    
    
        this.protocol = protocol;
        return this;
    }
    //使用构造器实例,创建URL对象。
    public URL build() {
    
    
        port = port < 0 ? 0 : port;
        // trim the leading "/"
        int firstNonSlash = 0;
		//...
        return new URL(protocol, username, password, host, port, path, parameters);
    }

  • 像业务中,new Object(), 然后一个个的set, 代码量非常之多,而且很多地方都会用到创建复杂对象,
  • 数据库表多了一个字段,entity对象也多了一个字段,那就麻烦了,到处都得改,非常不方便, 因此,这个URLBuidler构造器值得借鉴。
  • 例如URL 需要增加一个字段,那么只需要在Builder构造器里面修改即可,不需要去修改我们的业务代码。减少代码改动量,
    符合开闭原则,单一职责原则;
    处理注册中心配置 : 根据RegistryConfig转换成一个一个的URL对象,最终返回一个URL集合;

猜你喜欢

转载自blog.csdn.net/yaoyaochengxian/article/details/123512598
今日推荐