Dubbo RegistryDirectory服务目录解析

85 阅读2分钟

服务目录的用途在于查找Invoker以及服务提供者变更的时候进行刷新Invoker。 这点我们可以从它的父类接口的方法看出来。

public interface Directory<T> extends Node {

     //获取一个服务的Invoker集合
    List<Invoker<T>> list(Invocation invocation) throws RpcException;

}

这里同样的还有一个通用套路,就是一个抽象类AbstractDirectory来抽象出公共方法,然后提供抽象方法给子类实现。

我们从服务变更时刷新开始分析服务目录的作用。这里服务变更时调用的是notify方法,该方法来自NotifyListener的notify方法,实现该接口可以在服务提供者信息变更时通知到这个方法。

public synchronized void notify(List<URL> urls) {
    //收集出每个服务对应的URL
    Map<String, List<URL>> categoryUrls = urls.stream()
            .filter(Objects::nonNull)
            .filter(this::isValidCategory)
            .filter(this::isNotCompatibleFor26x)
            .collect(Collectors.groupingBy(url -> {
                if (UrlUtils.isConfigurator(url)) {
                    return CONFIGURATORS_CATEGORY;
                } else if (UrlUtils.isRoute(url)) {
                    return ROUTERS_CATEGORY;
                } else if (UrlUtils.isProvider(url)) {
                    return PROVIDERS_CATEGORY;
                }
                return "";
            }));


    List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
    //刷新服务提供者
    refreshOverrideAndInvoker(providerURLs);
}

上面的方法调用会来到下面这个重点方法

private void refreshInvoker(List<URL> invokerUrls) {
    Assert.notNull(invokerUrls, "invokerUrls should not be null");
    //这里正常情况下默认走的else逻辑,因为服务的协议默认为dubbo
    if (invokerUrls.size() == 1
            && invokerUrls.get(0) != null
            && EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
        this.forbidden = true; // Forbid to access
        this.invokers = Collections.emptyList();
        routerChain.setInvokers(this.invokers);
        destroyAllInvokers(); // Close all invokers
    } else {
        //设置静止访问为否
        this.forbidden = false; // Allow to access
        Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
        if (invokerUrls == Collections.<URL>emptyList()) {
            invokerUrls = new ArrayList<>();
        }
        if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
            invokerUrls.addAll(this.cachedInvokerUrls);
        } else {
            //缓存url
            this.cachedInvokerUrls = new HashSet<>();
            this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
        }
        if (invokerUrls.isEmpty()) {
            return;
        }
        //将url转换成invoker
        Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map

        if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
            logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls
                    .toString()));
            return;
        }

        List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
        // pre-route and build cache, notice that route cache should build on original Invoker list.
        // toMergeMethodInvokerMap() will wrap some invokers having different groups, those wrapped invokers not should be routed.
        //将最新的invokers保存到routerChain中 
        //routerChain是invoker的容器。
        routerChain.setInvokers(newInvokers);
        this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers;
        this.urlInvokerMap = newUrlInvokerMap;

        try {
            //销毁没用的invoker:就是旧的invoker和新的invoker进行对比,旧的没用的直接移除掉
            destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
        } catch (Exception e) {
            logger.warn("destroyUnusedInvokers error. ", e);
        }
    }
}

接下来就是目录的查找功能,list方法的实现在抽象类AbstractDirectory进行实现的,然后定义了一个doList让子类进行实现。

public List<Invoker<T>> doList(Invocation invocation) {
    //如果被禁用,抛出异常
    if (forbidden) {
        // 1. No service provider 2. Service providers are disabled
        throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " +
                getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +
                NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() +
                ", please check status of providers(disabled, not registered or in blacklist).");
    }
    //有多个组,那么合并后返回
    if (multiGroup) {
        return this.invokers == null ? Collections.emptyList() : this.invokers;
    }

    List<Invoker<T>> invokers = null;
    try {
        // Get invokers from cache, only runtime routers will be executed.
        //从路由中获取出来invoker
        invokers = routerChain.route(getConsumerUrl(), invocation);
    } catch (Throwable t) {
        logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
    }
    return invokers == null ? Collections.emptyList() : invokers;
}

整体分析来看,就是在服务消费者服务启动的时候,会先注册服务到注册中心,并且订阅需要消费的服务,然后调用notify更新一次invoker。最后在代理调用的时候在用list来返回对应的服务列表。