Dubbo服务的引用

服务引用的总体逻辑

Dubbo服务引用的总体逻辑,可以用一张图来表示
Dubbo服务消费机制

从整体上看,Dubbo框架做服务消费也分为两大部分
1. 通过持有远程服务实例生成Invoker
2. 把Invoker通过动态代理转换成实现用户接口的动态代理引用
这里的Invoker承载了网络连接、服务调用和重试等功能,在客户端,它可能是一个远程的实现,也可能是一个集群实现

服务引用的入口请参考文章:Dubbo是如何搭上Spring的车的?

从源码出发

服务引用的初始化

从服务引用的入口进来,就是其初始化逻辑了。直接看ReferenceConfig的init方法

private void init() {
    
    
    // $-- 只允许init一次
    if (initialized) {
    
    
        return;
    }
    initialized = true;
    if (interfaceName == null || interfaceName.length() == 0) {
    
    
        throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
    }
    // $-- ConsumerConfig对象注入
    // get consumer's global configuration
    checkDefault();
    appendProperties(this);
    // $-- generic属性的处理
    if (getGeneric() == null && getConsumer() != null) {
    
    
        setGeneric(getConsumer().getGeneric());
    }
    if (ProtocolUtils.isGeneric(getGeneric())) {
    
    
        // $-- 如果是泛化调用
        interfaceClass = GenericService.class;
    } else {
    
    
        try {
    
    
            // $-- 非泛化调用,通过反射实例化接口
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                    .getContextClassLoader());
        } catch (ClassNotFoundException e) {
    
    
            throw new IllegalStateException(e.getMessage(), e);
        }
        // $-- 校验接口与方法
        checkInterfaceAndMethods(interfaceClass, methods);
    }
    // $-- 文件服务调用配置的处理(是否配置有直连提供者)
    String resolve = System.getProperty(interfaceName);
    String resolveFile = null;
    if (resolve == null || resolve.length() == 0) {
    
    
        resolveFile = System.getProperty("dubbo.resolve.file");
        if (resolveFile == null || resolveFile.length() == 0) {
    
    
            File userResolveFile = new File(new File(System.getProperty("user.home")), "dubbo-resolve.properties");
            if (userResolveFile.exists()) {
    
    
                resolveFile = userResolveFile.getAbsolutePath();
            }
        }
        if (resolveFile != null && resolveFile.length() > 0) {
    
    
            Properties properties = new Properties();
            FileInputStream fis = null;
            try {
    
    
                fis = new FileInputStream(new File(resolveFile));
                properties.load(fis);
            } catch (IOException e) {
    
    
                throw new IllegalStateException("Unload " + resolveFile + ", cause: " + e.getMessage(), e);
            } finally {
    
    
                try {
    
    
                    if (null != fis) fis.close();
                } catch (IOException e) {
    
    
                    logger.warn(e.getMessage(), e);
                }
            }
            resolve = properties.getProperty(interfaceName);
        }
    }
    if (resolve != null && resolve.length() > 0) {
    
    
        url = resolve;
        if (logger.isWarnEnabled()) {
    
    
            if (resolveFile != null) {
    
    
                logger.warn("Using default dubbo resolve file " + resolveFile + " replace " + interfaceName + "" + resolve + " to p2p invoke remote service.");
            } else {
    
    
                logger.warn("Using -D" + interfaceName + "=" + resolve + " to p2p invoke remote service.");
            }
        }
    }
    if (consumer != null) {
    
    
        if (application == null) {
    
    
            application = consumer.getApplication();
        }
        if (module == null) {
    
    
            module = consumer.getModule();
        }
        if (registries == null) {
    
    
            registries = consumer.getRegistries();
        }
        if (monitor == null) {
    
    
            monitor = consumer.getMonitor();
        }
    }
    if (module != null) {
    
    
        if (registries == null) {
    
    
            registries = module.getRegistries();
        }
        if (monitor == null) {
    
    
            monitor = module.getMonitor();
        }
    }
    if (application != null) {
    
    
        if (registries == null) {
    
    
            registries = application.getRegistries();
        }
        if (monitor == null) {
    
    
            monitor = application.getMonitor();
        }
    }
    // $-- 校验application
    checkApplication();
    // $-- 校验stub与mock
    checkStubAndMock(interfaceClass);
    // $-- 下面进行构建配置map,用于后续构建url
    Map<String, String> map = new HashMap<String, String>();
    Map<Object, Object> attributes = new HashMap<Object, Object>();
    map.put(Constants.SIDE_KEY, Constants.CONSUMER_SIDE);
    map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
    map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
    if (ConfigUtils.getPid() > 0) {
    
    
        map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
    }
    if (!isGeneric()) {
    
    
        // $-- 非泛化调用
        String revision = Version.getVersion(interfaceClass, version);
        if (revision != null && revision.length() > 0) {
    
    
            map.put("revision", revision);
        }

        String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
        if (methods.length == 0) {
    
    
            logger.warn("NO method found in service interface " + interfaceClass.getName());
            map.put("methods", Constants.ANY_VALUE);
        } else {
    
    
            map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
        }
    }
    map.put(Constants.INTERFACE_KEY, interfaceName);
    appendParameters(map, application);
    appendParameters(map, module);
    appendParameters(map, consumer, Constants.DEFAULT_KEY);
    appendParameters(map, this);
    String prefix = StringUtils.getServiceKey(map);
    if (methods != null && !methods.isEmpty()) {
    
    
        for (MethodConfig method : methods) {
    
    
            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");
                }
            }
            appendAttributes(attributes, method, prefix + "." + method.getName());
            checkAndConvertImplicitConfig(method, map, attributes);
        }
    }

    // $-- ip地址获取
    String hostToRegistry = ConfigUtils.getSystemProperty(Constants.DUBBO_IP_TO_REGISTRY);
    if (hostToRegistry == null || hostToRegistry.length() == 0) {
    
    
        hostToRegistry = NetUtils.getLocalHost();
    } else if (isInvalidLocalHost(hostToRegistry)) {
    
    
        throw new IllegalArgumentException("Specified invalid registry ip from property:" + Constants.DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
    }
    map.put(Constants.REGISTER_IP_KEY, hostToRegistry);

    //attributes are stored by system context.
    StaticContext.getSystemContext().putAll(attributes);
    // $-- 创建ref代理
    ref = createProxy(map);
    ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
    ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}

init方法的代码比较长,归结起来,主要做了以下几件事情:

  1. init次数控制,只允许init一次
  2. 服务参数、属性的读取,以及泛化调用、ip地址等的处理
  3. 本地文件url配置的处理
  4. 创建代理

init方法的大部分笔墨都在处理配置,其重点的远程服务引用功能,还是要看创建代理这一步,即createProxy方法

private T createProxy(Map<String, String> map) {
    
    
    // $-- temp://localhost?application=demo-consumer&check=false&dubbo=2.0.2&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=3376&qos.port=33333&register.ip=192.168.0.105&side=consumer&timestamp=1588948356581
    URL tmpUrl = new URL("temp", "localhost", 0, map);
    // $-- 判断是否是同一个JVM内部引用,默认非injvm引用
    final boolean isJvmRefer;
    if (isInjvm() == null) {
    
    
        // $-- 如果指定了url,则非injvm
        if (url != null && url.length() > 0) {
    
     // if a url is specified, don't do local reference
            isJvmRefer = false;
        } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
    
    
            // by default, reference local service if there is
            isJvmRefer = true;
        } else {
    
    
            isJvmRefer = false;
        }
    } else {
    
    
        isJvmRefer = isInjvm().booleanValue();
    }

    if (isJvmRefer) {
    
    
        // $-- 使用injvm协议从内存中获取实例
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
        invoker = refprotocol.refer(interfaceClass, url);
        if (logger.isInfoEnabled()) {
    
    
            logger.info("Using injvm service " + interfaceClass.getName());
        }
    } else {
    
    
        // $-- 获取到了url,此url可能是点对点直连的url,也有可能是注册中心的url
        if (url != null && url.length() > 0) {
    
    
            // $-- 支持使用分号隔开指定的多个直连机器
            String[] us = Constants.SEMICOLON_SPLIT_PATTERN.split(url);
            if (us != null && us.length > 0) {
    
    
                for (String u : us) {
    
    
                    URL url = URL.valueOf(u);
                    if (url.getPath() == null || url.getPath().length() == 0) {
    
    
                        url = url.setPath(interfaceName);
                    }
                    if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
    
    
                        // $-- 是注册中心url(允许直连地址写成注册中心)
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
    
    
                        // $-- 是点对点直连的url(直连某一台服务提供者)
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        } else {
    
    
            // $-- 未指定url,则从registry中获取注册中心url
            List<URL> us = loadRegistries(false);
            if (us != null && !us.isEmpty()) {
    
    
                for (URL u : us) {
    
    
                    URL monitorUrl = loadMonitor(u);
                    if (monitorUrl != null) {
    
    
                        map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                    }
                    // $-- 注册中心地址后添加refer存储服务消费元数据信息
                    urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                }
            }
            if (urls.isEmpty()) {
    
    
                throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
            }
        }

        if (urls.size() == 1) {
    
    
            // $-- 单url,直接获取invoker
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        } else {
    
    
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            for (URL url : urls) {
    
    
                // $-- 逐个获取invoker,并添加到invokers列表
                invokers.add(refprotocol.refer(interfaceClass, url));
                if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
    
    
                    registryURL = url; // use last registry url
                }
            }
            // $-- 通过Cluster将多个Invoker转换成一个Invoker
            if (registryURL != null) {
    
     // registry url is available
                // use AvailableCluster only when register's cluster is available
                URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                // $-- StaticDirectory只是将invokers外面包了一层皮
                invoker = cluster.join(new StaticDirectory(u, invokers));
            } else {
    
     // not a registry url
                invoker = cluster.join(new StaticDirectory(invokers));
            }
        }
    }

    Boolean c = check;
    if (c == null && consumer != null) {
    
    
        c = consumer.isCheck();
    }
    if (c == null) {
    
    
        c = true; // default true
    }
    if (c && !invoker.isAvailable()) {
    
    
        throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
    }
    if (logger.isInfoEnabled()) {
    
    
        logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
    }
    // create service proxy
    return (T) proxyFactory.getProxy(invoker);
}

createProxy方法统筹了多种方式的服务引用,主要逻辑可以总结如下:

  • 判断是否为injvm协议引用

    • 如果是injvm协议,即本机内部调用,直接从内存中获取实例
    • 如果为非injvm协议,则可能为点对点直连,也可能为注册中心url,分别按照对应的方式获取地址,然后进行引用
  • 随后会判断是否开启了启动时校验(check参数),如果开启了,会校验服务是否可用

  • 最后会创建服务代理

其中injvm的引用逻辑比较简单,这里就不浪费笔墨了。下面我们直接来看一下远程服务的引用。

远程服务的引用

远程服务引用的获取,分为两种情况。

  1. 如果只有一个url地址,则直接通过refer方法获取Invoker即可
  2. 如果有多个url地址,则除了要逐个获取invoker,还要通过Cluster将多个Invoker合并转换成一个Invoker

无论最终如何处理,都要首先通过refer获得Invoker对象,即代码

refprotocol.refer(interfaceClass, url);

我们使用Dubbo,一般都是使用注册中心的,因此这里我们主要看存在注册中心场景下的引用。

在存在注册中心的场景下,通过Dubbo SPI机制,最终会调用RegistryProtocolrefer方法(调用过程中的Filter和Wrapper不是必要逻辑,为了节省篇幅,这里省去不谈)。

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    
    
    // $--- 设置具体注册中心协议,如将registry://... 转换为了zookeeper://...形式
    url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
    // $-- 获取具体注册中心实例,如 ZookeeperRegistry(这里的registryFactory是通过Adaptive扩展生成的)
    Registry registry = registryFactory.getRegistry(url);
    if (RegistryService.class.equals(type)) {
    
    
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // $-- 根据配置处理多分组聚合(支持对注册中心的服务进行分组调用)
    // group="a,b" or group="*"
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
    String group = qs.get(Constants.GROUP_KEY);
    if (group != null && group.length() > 0) {
    
    
        if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
                || "*".equals(group)) {
    
    
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    // $-- 处理订阅数据并通过Cluster合并多个Invoker
    return doRefer(cluster, registry, type, url);
}

refer方法的处理,主要是先获取了具体的注册中心实例,然后判断是否配置了group分组,根据此配置,进行后续的doRefer

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    
    
    // $-- 将url包装为directory,directory是服务消费集群容错的核心
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    directory.setRegistry(registry);
    directory.setProtocol(protocol);
    // all attributes of REFER_KEY
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    // $-- 组装消费的协议地址,形如 consumer://192.168.1.1/...
    URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, parameters.remove(Constants.REGISTER_IP_KEY), 0, type.getName(), parameters);
    if (!Constants.ANY_VALUE.equals(url.getServiceInterface())
            && url.getParameter(Constants.REGISTER_KEY, true)) {
    
    
        // $-- 注册消费信息到注册中心
        registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
                Constants.CHECK_KEY, String.valueOf(false)));
    }
    // $-- 订阅服务提供者(providers)、路由(routers)和动态配置(configurators)
    directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
            Constants.PROVIDERS_CATEGORY
                    + "," + Constants.CONFIGURATORS_CATEGORY
                    + "," + Constants.ROUTERS_CATEGORY));

    // $-- 通过Cluster合并invokers
    Invoker invoker = cluster.join(directory);
    // $-- 将consumer invoker记录到内存map中
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}

doRefer方法中,先创建了一个Directory对象(Directory是Dubbo服务消费中集群容错的核心),随后填充组装Directory,创建出Invoker

这里为什么不直接创建Invoker,而要用到Directory这个中间对象呢?
原因是为了服务调用时的集群容错机制。这个我们在后续的文章中再详细介绍。

Invoker是Dubbo服务消费中承上启下的一个关键点。这里进行的创建组装,涉及到集群相关知识,可以先Mark一下,后续再聊。

Invoker创建时,有几个重要的逻辑,如下:

  • 注册消费者信息到注册中心中
  • 订阅服务提供者、路由和配置的信息
  • 通过Cluster,将Directory包装成Invoker

关于注册消费者到注册中心,订阅注册中心上的服务提供者、路由和配置等信息,这些源码级别的分析,我们已经在服务暴露文章中介绍过了,这里就不再赘述了。

现在,我们主要关注一下Clusterjoin方法,看看其主要功能。
这里的Cluster也是Dubbo SPI生成的自适应扩展类,默认实现为 FailoverCluster,并且被MockClusterWrapper包装了一下

public class MockClusterWrapper implements Cluster {
    
    

    private Cluster cluster;

    public MockClusterWrapper(Cluster cluster) {
    
    
        this.cluster = cluster;
    }

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    
    
        return new MockClusterInvoker<T>(directory,
                this.cluster.join(directory));
    }

}

MockClusterWrapper的join方法,是直接将内部的FailoverCluster进行join之后,包装成MockClusterInvoker后返回

public class FailoverCluster implements Cluster {
    
    

    public final static String NAME = "failover";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
    
    
        return new FailoverClusterInvoker<T>(directory);
    }

}

FailoverCluster方法的join也很简单,直接将directory利用FailoverClusterInvoker进行包装返回。
FailoverClusterInvoker的构造函数中也无复杂逻辑,只是变量的赋值罢了。

至此,一个Invoker的实例就生成了。接下来,就只剩下代理的创建了。

生成远程服务代理

生成远程服务代理,对应代码:

(T) proxyFactory.getProxy(invoker);

这里同样使用了Dubbo SPI机制。其中,proxyFactory被StubProxyFactoryWrapper包装了一层,内部实际上调用的是AbstractProxyFactory的getProxy方法。而AbstractProxyFactory的getProxy方法实现,依赖于SPI配置,可以为JavassistJDK Proxy的方式。

代理生成实现的逻辑并不复杂,这里就不赘述了。

总结

总结一下,Dubbo服务的引用,主要是将一些URL相关的信息,组装成一个Directory对象,然后通过Cluster连接成一个Invoker,最后生成此Invoker的代理给应用层调用。

猜你喜欢

转载自blog.csdn.net/somehow1002/article/details/108430075