上一篇文章中,讲到 Dubbo 初始化一个 代理对象时,会执 ReferenceConfiginit 方法,而后执行其

ref = createProxy(map);

而在这一步中,会进行以下几步:

  1. 判断是否是 Jvm 类型应用
  2. 组装url
  3. 代理生成 invoker
  4. 最后 执行 (T) PROXY_FACTORY.getProxy(invoker); 返回type 类型对象。

Dubbo 中 Invoker

InvokerDubbo 的核心模型,代表一个可执行体。在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。Invoker 是由 Protocol 实现类构建而来。Protocol 实现类有很多,它本身也是一个 SPI 类型。

上一篇文章中,分析了参数组装,以及 Wrapper 类 和 SPI 组装关系。

准备工作

这篇即重点分析 invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
结合上两篇文章,由于 Protocol 没有 默认的 Adaptive 类,所以将使用默认的 Protocol$Adaptive 类此处,有需要可以看这篇文章:
Dubbo 中默认的 Adaptive类生成过程及例子 由于 url是 registry 类型,所以最终得到的是 RegistryProtocl,而里面和Wrapper 类组装而成的结构为:
ProtocolFilterWrapper->ProtocolListenerWrapper->QosProtocolWrapper->RegistryProtocol

起始最终 init 中 PROXY_FACTORY 类型为 :ProtocolFilterWrapper,有前面的分析,包装类其实是链式的组装结构,即最终会依次调用:
ProtocolFilterWrapperProtocolListenerWrapperQosProtocolWrapperrefer 方法,而由代码可知,当url 的协议为 registry 时候,则默认会跳过而执行下一个 Protocolrefer。这样一来,依次执行玩前三个Wrapper 之后,就会执行 RegistryProtocolrefer
本文将重点分析RegistryProtocol 的 refer,其中会包括Dubbo 集群容错的一些相关内容介绍。

RegistryProtocol 的 refer

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // 拼凑 url
        url = URLBuilder.from(url)
                .setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
                .removeParameter(REGISTRY_KEY)
                .build();
        // 获取注册中心
        Registry registry = registryFactory.getRegistry(url);
        // 如果类型就是 `RegistryService` 类型
        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(REFER_KEY));
        String group = qs.get(GROUP_KEY);
        if (group != null && group.length() > 0) {
            if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
                return doRefer(getMergeableCluster(), registry, type, url);
            }
        }
        // 执行 doRefer
        return doRefer(cluster, registry, type, url);
    }

上文代码中,主要做了以下几步:

  1. 拼凑url
  2. 判断是否为RegistryService,是得化说明不是具体接口类型,所以直接返回
  3. 如果有group 参数的,则使用 MergeableCluster 构建 invoker,否则使用默认的 FailoverCluster 构建。

Registry registry = registryFactory.getRegistry(url); 这是 AbstractRegistryFactory 中方法,是根据url获取 Registry 过程,Registry 也没有默认的 Adaptive 类,所以将使用默认的 Adaptive 类,而url在 Registry$Adatpive 被换为了 zookeeper 协议, 而后会执行 Registry registry = REGISTRIES.get(key); 从缓存中获取 Regisry,否则会通过url新建一个。

@Override
    public Registry getRegistry(URL url) {
        // 重新设置path
        url = URLBuilder.from(url)
                .setPath(RegistryService.class.getName())
                .addParameter(INTERFACE_KEY, RegistryService.class.getName())
                .removeParameters(EXPORT_KEY, REFER_KEY)
                .build();
        // 由url获取key
        String key = url.toServiceStringWithoutResolving();
        // Lock the registry access process to ensure a single instance of the registry
        LOCK.lock();
        try {
			// 尝试从缓存中获取
            Registry registry = REGISTRIES.get(key);
            if (registry != null) {
                return registry;
            }
            // 通过 SPI 或者 IOC 创建create registry by spi/ioc
            registry = createRegistry(url);
            if (registry == null) {
                throw new IllegalStateException("Can not create registry " + url);
            }
            REGISTRIES.put(key, registry);
            return registry;
        } finally {
            // Release the lock
            LOCK.unlock();
        }
    }

以上代码做了如下几件事:

  1. 将url中path设置为 org.apache.dubbo.registry.RegistryService
  2. 尝试获取key的缓存,key的格式为 zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService,这也就说明,不同的接口,可以有不同的注册中心。
  3. 如果无法找到缓存,则执行 子类具体实现的 createRegistry 新建

ZookeeperRegistyFactory 工厂模式的应用,则是通过 createRegistry 新创键一个 ZookeeperRegistry 并返回:

public Registry createRegistry(URL url) {
        return new ZookeeperRegistry(url, zookeeperTransporter);
    }

可以看下面 ZookeeperRegistry 章节分析。
4. 判断是否 有配置 group,如果有则使用 MergeableCluster,否则将使用默认的Cluster$AdaptiveFailoverCluster 去调用 子类具体实现的 doRefer 方法
可以看下面 doRefer 小节具体分析

ZookeeperRegistry

那么 这个 ZookeeperRegistry 做了什么事呢?

  1. ZookeeperRegistry 构造方法:
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    // 执行父类构造方法
        super(url);
        // 判断是否为 0.0.0.0 的host
        if (url.isAnyHost()) {
            throw new IllegalStateException("registry address == null");
        }
        String group = url.getParameter(GROUP_KEY, DEFAULT_ROOT);
        if (!group.startsWith(PATH_SEPARATOR)) {
            group = PATH_SEPARATOR + group;
        }
        this.root = group;
        zkClient = zookeeperTransporter.connect(url);
        zkClient.addStateListener(state -> {
            if (state == StateListener.RECONNECTED) {
                try {
                    recover();
                } catch (Exception e) {
                    logger.error(e.getMessage(), e);
                }
            }
        });
    }
  1. FailbackRegistry 构造方法:
public FailbackRegistry(URL url) {
        super(url);
        this.retryPeriod = url.getParameter(REGISTRY_RETRY_PERIOD_KEY, DEFAULT_REGISTRY_RETRY_PERIOD);

        // since the retry task will not be very much. 128 ticks is enough.
        retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboRegistryRetryTimer", true), retryPeriod, TimeUnit.MILLISECONDS, 128);
    }
  1. AbstractRegistry 构造方法:
public AbstractRegistry(URL url) {
    // 设置url
        setUrl(url);
        // 尝试从缓存文件中获取配置
        // Start file save timer
        syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
        String filename = url.getParameter(FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress() + ".cache");
        File file = null;
        if (ConfigUtils.isNotEmpty(filename)) {
            file = new File(filename);
            if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
                if (!file.getParentFile().mkdirs()) {
                    throw new IllegalArgumentException("Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
                }
            }
        }
        this.file = file;
        // When starting the subscription center,
        // we need to read the local cache file for future Registry fault tolerance processing.
        // 尝试从缓存文件中获取配置
        loadProperties();
        // 告知 机制
        notify(url.getBackupUrls());
    }

以上列举了 ZookeeperRegistry 的构造方法,以及继承结构中父类 FailbackRegistryAbstractRegistry 的构造方法。
从后往前讲:

  1. 在 AbstractRegistry中,首先是检查了是否有缓存文件配置,有则读取出来,文件位置在 ~/.dubbo 下,名字格式为:
    url.getParameter(FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getParameter(APPLICATION_KEY) + "-" + url.getAddress() + ".cache"); 例如以下: /home/anla7856/.dubbo/dubbo-registry-dubbo-consumer-127.0.0.1:2181.cache。 而后则第一次通知了 其他消息监听的去配置下 url 的 backupUrl,即Zookeeper集群的其他配置地址。
  2. FailbackRegistryZookeeperRegistry 的父类,里面主要封装和记录了一些失败的通知或者订阅信息,在里面开启了一个 定时器 HashedWheelTimer,用于失败重试,而重试周期如果没有指明,则为5000ms。
  3. 正题就是 ZookeeperRegistry 中的构造方法,除了调用父类的构造方法,它还负责连接Zookeeper,即内部使用了 一个Dubbo 封装的 ZookeeperClientZookeeperTransporter 用于操作 Zookeeper。由于可能有多种ZookeeperTransporter 类型,所以事实上它也是 SPI 类型。
    最后,监听 Zookeeper 的状态变更消息,从而执行 recover 方法。recover 方法则主要是当需要重连时候,重新尝试连接以及刷新已获取的配置。

当获取完 Registry 后,将其放入 REGISTRIES 中缓存起来,并最终返回该 registry

doRefer

上面的 获取 registry 方法 是 仅仅是准备工作,即初始化了 相关的 RegistryCluster。而在RegistryProtocol各自的doRefer 方法 则会进行具体一些设定,例如Directory 配置,订阅配置,注册监听器等。

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    	// 创建一个 RegistryDirectory
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        // 设置 Registry 和 protocol
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
        // 从新构造订阅的URL
        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
        if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
            directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
            registry.register(directory.getRegisteredConsumerUrl());
        }
        directory.buildRouterChain(subscribeUrl);
        directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
                PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));

        Invoker invoker = cluster.join(directory);
        ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
        return invoker;
    }

上面贴出了 RegistryProtocoldoRefer 方法,有以下几个逻辑点:

  1. 创建一个RegistryDirectory,即服务目录,服务目录中存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息,比如 ip、端口、服务协议等。通过这些信息,服务消费者就可通过 Netty 等客户端进行远程调用。而后设置 RegistryProtocolRegistriesConsumerUrl
  2. 而后,调用 registry.register(directory.getRegisteredConsumerUrl()); 去注册 这个 registeredConsumerUrl,默认使用 Zookeeper 作为注册中心进行服务注册和管理。由ZookeeperRegistry->FailbackRegistry->AbstractRegistry 依次初始化并调用。最终,执行ZookeeperRegistrydoRegistry语句在zk上面增加一个字节点:
@Override
    public void doRegister(URL url) {
        try {
            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true));
        } catch (Throwable e) {
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }
  1. 执行 directory.buildRouterChain(subscribeUrl); 初始化 路由链 的初始化,具体路由链将由后面文章介绍。
  2. 执行 directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY, PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY)); 去订阅监听。
    具体订阅过程看下一篇详细分析。
  3. Invoker invoker = cluster.join(directory); 使用默认的 Cluster$Adatpive ,创建一个Invoker 并返回,并且缓存入 ProviderConsumerRegTable 中。

看下 Cluster 的SPI 文件:

mock=org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=org.apache.dubbo.rpc.cluster.support.FailoverCluster
failfast=org.apache.dubbo.rpc.cluster.support.FailfastCluster
failsafe=org.apache.dubbo.rpc.cluster.support.FailsafeCluster
failback=org.apache.dubbo.rpc.cluster.support.FailbackCluster
forking=org.apache.dubbo.rpc.cluster.support.ForkingCluster
available=org.apache.dubbo.rpc.cluster.support.AvailableCluster
mergeable=org.apache.dubbo.rpc.cluster.support.MergeableCluster
broadcast=org.apache.dubbo.rpc.cluster.support.BroadcastCluster
registryaware=org.apache.dubbo.rpc.cluster.support.RegistryAwareCluster

里面有个 Wrapper 类,所以Cluster 的返回必然是 Wrapper -> Cluster 模式,具体可以看这篇文章分析:
SPI模式中 Wrapper和 SPI 类组装逻辑

再看看 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));
    }

}

里面join 方法仅仅返回 一个 MockClusterInvoker

所以从代码中可以推断出 ,最终在 ReferenceConfig 返回必将是一个 MockClusterInvoker

由打断点而获取到的invoker 内容如下所示:

dubbo继承RpcExceptionFilter dubbo invoker详解_zookeeper


而最终再通过 return (T) PROXY_FACTORY.getProxy(invoker); 返回一个代理类。

所以另一方面,当我们写代码时候,直接端点进入,是无法直接进入 type 的 实现类,而是进入MockClusterInvoker

那具体订阅过程是如何的呢?代理工厂如何生成代理类呢?

这些问题,且看下回分解,感谢阅读。