Apache Dubbo 系列之不保证你能看懂的服务引用过程

448 阅读7分钟

🌟 欢迎关注个人技术公众号:野区杰西

前言

上篇文章已经讲完了 Dubbo 在服务提供者的服务暴露过程,这一篇主要是将服务的引用过程。

服务引用的时机点主要有两个

  1. 依赖 Spring 容器的 afterPropertiesSet 方法进行调用
  2. ReferenceBean 对应的服务被注入到其他类中时引用

而在引用的过程中,主要做几个事情:

  1. 配置检查与收集工作,收集的信息包括了服务调用的方式(引用本地 (JVM) 服务| 直连方式引用远程服务 | 通过注册中心引用远程服务)
  2. 决定方式后生成一个或多个 Invoker(由于集群的原因),此时 Invoker 已经有了调用的能力
  3. 通过 ProxyFactoryInvoker 生成代理类,并让代理类去调用 Invoker 逻辑。避免了 Dubbo 框架代码对业务代码的侵入

好,大致上流程是非常清晰的! Let s Go!

主体流程讲解

我们从 ReferenceBeanafterPropertiesSet 开始。为了不贴上大量的代码,我只讲解重要的地方。

afterPropertiesSet 方法主要是将 applicationmoduleRegistry 以及 Monitor 的配置文件信息封装配置类。

//从 Spring 获取
if (getApplication() == null
        && (getConsumer() == null || getConsumer().getApplication() == null)) {
    Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
    if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
        ApplicationConfig applicationConfig = null;
        for (ApplicationConfig config : applicationConfigMap.values()) {
            if (config.isDefault() == null || config.isDefault().booleanValue()) {
                if (applicationConfig != null) {
                    throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
                }
                applicationConfig = config;
            }
        }
        // 设置 application 的配置信息
        if (applicationConfig != null) {
            setApplication(applicationConfig);
        }
    }
}

//... Module、Registry、Monitor 同理

设置完后,然后判断是否懒加载,最后调用 getObject 方法。

public Object getObject() throws Exception {
    return get();
}

public synchronized T get() {
	// 判断是否初始的过程中被销毁
    if (destroyed) {
        throw new IllegalStateException("Already destroyed!");
    }
    if (ref == null) {
        //调用 init
        init();
    }
    return ref;
}

init 方法主要是对配置进行检查和处理,以保证配置的正确性。代码有点长,可以细看注释

private void init() {
    //判断是否当前有线程正在初始化,如果有就停止
    if (initialized) {
        return;
    }
    initialized = true;
    //判断接口名称
    if (interfaceName == null || interfaceName.length() == 0) {
        throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
    }
    // 检测 consumer 变量是否为空,为空则创建
    checkDefault();
    appendProperties(this);
    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();
        }
    }
    //
    checkApplication();
    //检查根服务
    checkStub(interfaceClass);
    //检查
    checkMock(interfaceClass);
    //
    Map<String, String> map = new HashMap<String, String>();
    Map<Object, Object> attributes = new HashMap<Object, Object>();
    //设置 SIDE_KEY、Dubbo 版本、时间戳
    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)), ","));
        }
    }
    //设置接口名称,application、module等配置信息
    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);
        }
    }
    //从环境获取 Registry 的信息
    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);

    //在系统环境上下文保存上面设置的信息
    StaticContext.getSystemContext().putAll(attributes);
    //开始创建代理
    ref = createProxy(map);
    //封装成 ConsumerModel,全局保存
    ConsumerModel consumerModel = new ConsumerModel(getUniqueServiceName(), this, ref, interfaceClass.getMethods());
    ApplicationModel.initConsumerModel(getUniqueServiceName(), consumerModel);
}

createProxy 方法在翻译上就是创建代理,实际上还有很多附加的功能意义。我们来看看代码

private T createProxy(Map<String, String> map) {
    URL tmpUrl = new URL("temp", "localhost", 0, map);
    final boolean isJvmRefer;
    //判断配置文件是否配置了 injvm 
    // 如果是 null
    if (isInjvm() == null) {
        // 如果 url 被指定 说明不是本地引用
        if (url != null && url.length() > 0) { 
            isJvmRefer = false;
        } else if (InjvmProtocol.getInjvmProtocol().isInjvmRefer(tmpUrl)) {
            //默认是本地引用
            isJvmRefer = true;
        } else {
            isJvmRefer = false;
        }
    } else {
        isJvmRefer = isInjvm().booleanValue();
    }

    if (isJvmRefer) {
        //配置本地引用的 URL
        URL url = new URL(Constants.LOCAL_PROTOCOL, NetUtils.LOCALHOST, 0, interfaceClass.getName()).addParameters(map);
        //生成 invoker
        invoker = refprotocol.refer(interfaceClass, url);
        if (logger.isInfoEnabled()) {
            logger.info("Using injvm service " + interfaceClass.getName());
        }
    } else {
        //这时候判断是否 url 不为空,如果不为空有可能是点对点的连接 or 注册中心的地址 
        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())) {
                        urls.add(url.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        } else { // 从注册中心的配置组装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()));
                    }
                    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) {
            invoker = refprotocol.refer(interfaceClass, urls.get(0));
        } else {
            //有多个 URL 需要生成 多个 Invoker 
            //而且如果 url 中是 registry 协议的,保留最后一个
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            for (URL url : urls) {
                // 通过 refprotocol 调用 refer 构建 Invoker,refprotocol 会在运行时
                // 根据 url 协议头加载指定的 Protocol 实例,并调用实例的 refer 方法
                invokers.add(refprotocol.refer(interfaceClass, url));
                if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                    registryURL = url; 
                }
            }
            // registryURL 不为空的话
            if (registryURL != null) { 
                // 如果注册中心链接不为空,则将使用 AvailableCluster
                URL u = registryURL.addParameterIfAbsent(Constants.CLUSTER_KEY, AvailableCluster.NAME);
                // 创建 StaticDirectory 实例,并由 Cluster 对多个 Invoker 进行合并
                invoker = cluster.join(new StaticDirectory(u, invokers));
            } else { 
                invoker = cluster.join(new StaticDirectory(invokers));
            }
        }
    }

    Boolean c = check;
    if (c == null && consumer != null) {
        c = consumer.isCheck();
    }
    if (c == null) {
        c = true; // default true
    }
    // invoker 可用性检查
    if (c && !invoker.isAvailable()) {
        // make it possible for consumer to retry later if provider is temporarily unavailable
        initialized = false;
        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());
    }
    // 将 Invoker 创建代理类
    return (T) proxyFactory.getProxy(invoker);
}

创建 invoker

InvokerDubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体。所以我在这里最常见的协议 RegistryProtocolDubboProtocol 来看看如何创建 Invoker

我们先看 DubboProtocol 类的 refer 方法

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
    //优化序列化
    optimizeSerialization(url);
    // 创建 rpc invoker
    DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
    invokers.add(invoker);
    return invoker;
}

接下来我们看 getClients 方法。getClients 的作用在于

private ExchangeClient[] getClients(URL url) {
    // 是否是共享连接
    boolean service_share_connect = false;
    int connections = url.getParameter(Constants.CONNECTIONS_KEY, 0);
    // 如果未配置 connections 则共享连接
    if (connections == 0) {
        service_share_connect = true;
        connections = 1;
    }

    ExchangeClient[] clients = new ExchangeClient[connections];
    for (int i = 0; i < clients.length; i++) {
        if (service_share_connect) {
            //获取共享客户端
            clients[i] = getSharedClient(url);
        } else {
            //初始化新的客户端
            clients[i] = initClient(url);
        }
    }
    return clients;
}

上面代码主要是判断是否是共享客户端。如果 connections 为 0 说明是共享。我们先看 getSharedClient 的代码。

private ExchangeClient getSharedClient(URL url) {
    String key = url.getAddress();
    // 获取带有“引用计数”功能的 ExchangeClient
    ReferenceCountExchangeClient client = referenceClientMap.get(key);
    if (client != null) {
        if (!client.isClosed()) {
            // 增加引用计数
            client.incrementAndGetCount();
            return client;
        } else {
            referenceClientMap.remove(key);
        }
    }

    locks.putIfAbsent(key, new Object());
    synchronized (locks.get(key)) {
        if (referenceClientMap.containsKey(key)) {
            return referenceClientMap.get(key);
        }

        // 创建 ExchangeClient 客户端
        ExchangeClient exchangeClient = initClient(url);
        // 将 ExchangeClient 实例传给 ReferenceCountExchangeClient,这里使用了装饰模式
        client = new ReferenceCountExchangeClient(exchangeClient, ghostClientMap);
        referenceClientMap.put(key, client);
        ghostClientMap.remove(key);
        locks.remove(key);
        return client;
    }
}

上面主要是通过缓存来看看 ExchangeClient 实例,并将实例传给 ReferenceCountExchangeClient 构造方法创建一个带有引用计数功能的 ExchangeClient 实例。

无论是共享还是非共享,都会调用 initClient 方法。initClient 方法比较重要。因为这里涉及了客户端的远程调用。

private ExchangeClient initClient(URL url) {

    // 客户端设置
    String str = url.getParameter(Constants.CLIENT_KEY, url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_CLIENT));

    url = url.addParameter(Constants.CODEC_KEY, DubboCodec.NAME);
    // 心跳设置,默认是允许的
    url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));

    // BIO is not allowed since it has severe performance issue.
    if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
        throw new RpcException("Unsupported client type: " + str + "," +
                " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
    }

    ExchangeClient client;
    try {
        // 查看是否延迟加载
        if (url.getParameter(Constants.LAZY_CONNECT_KEY, false)) {
            client = new LazyConnectExchangeClient(url, requestHandler);
        } else {
            client = Exchangers.connect(url, requestHandler);
        }
    } catch (RemotingException e) {
        throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
    }
    return client;
}

上面的 Exchangers.connnect 中的 Exchangers 不是直接负责连接的,它是一个包装器,负责屏蔽底层的连接客户端。我们快速看一下 Exchangers

public static Exchanger getExchanger(URL url) {
    //从 url 获取对应的值,利用 Adaptive 机制获取对应的 Exchangers
    String type = url.getParameter(Constants.EXCHANGER_KEY, Constants.DEFAULT_EXCHANGER);
    return getExchanger(type);
}

Exchangers 默认只有 HeaderExchangerHeaderExchanger 主要是封装了一个 Client 以及实现心跳机制的类。我们继续看看其 connect 方法。

public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
    return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
}

TransportersDubbo 也非常重要,它相当于是最底层与其他服务进行通信的组件。按照官档来说主要有 nettymina。 我们来看看其 connect 方法。

public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
    if (url == null) {
        throw new IllegalArgumentException("url == null");
    }
    ChannelHandler handler;
    if (handlers == null || handlers.length == 0) {
        handler = new ChannelHandlerAdapter();
    } else if (handlers.length == 1) {
        handler = handlers[0];
    } else {
        handler = new ChannelHandlerDispatcher(handlers);
    }
    //通过 spi 机制获取到对应的实现
    return getTransporter().connect(url, handler);
}

如果学过 netty 的同学的话,可能很快可以理解 handler 相当于往 Transporter 加一个流程处理,这样子实现可以非常灵活的添加自定义处理。注意,这里的 ChannelHandlerDubbo 自己实现了一遍,所以可以说是对于其他通信组件同样适用。

结语

Dubbo 服务引用流程还是非常清晰的。

下节继续讲 Dubbo 的调用过程。

下期见!