Dubbo集群容错之Directory

496 阅读3分钟

1. 前言

前面的文章分析了Dubbo集群容错层之Cluster和ClusterInvoker,我们已经知道,Directory会提供可调用的服务列表,Cluster会将这一组服务聚合成单个具备集群容错能力的ClusterInvoker,上层服务还是对ClusterInvoker发起invoke调用,透明的实现了集群容错。

本文重点分析下Directory接口,Dubbo目前提供了两种实现,分别是基于注册中心的RegistryDirectory,和静态服务StaticDirectory。可以这么说,Dubbo实现服务的自动注册与发现,RegistryDirectory功不可没。

2. Directory

Directory是服务目录接口,它主要的功能就是在Consumer发起RPC调用时,根据Invocation过滤出可调用的服务给ClusterInvoker做负载均衡。

public interface Directory<T> extends Node {

    /**
     * 服务接口
     * 一个接口对应一个Directory实例
     */
    Class<T> getInterface();

    /**
     * 获取可用的Invoker
     * 根据Router过滤掉部分服务
     */
    List<Invoker<T>> list(Invocation invocation) throws RpcException;

    // 获取所有Invoker
    List<Invoker<T>> getAllInvokers();

    // 消费者URL
    URL getConsumerUrl();

}

2.1 RegistryDirectory

RegistryDirectory是基于注册中心的服务目录,它会去注册中心订阅服务。

public void subscribe(URL url) {
    setConsumerUrl(url);
    // 注册监听
    CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
    serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
    // 订阅服务
    registry.subscribe(url, this);
}

服务变更时会触发notify()方法,会获取到服务提供者的URL。因为服务往往都是集群部署的,所以获取到的一般都是一组URL,暂且称它为ProviderUrls。只要ProviderUrls有变更,就会将它们自动转换成对应的Invoker,例如Provider提供的是dubbo协议的服务,那么就会自动转换成DubboInvoker。

有了一组Invoker,接下来,当Consumer发起RPC调用时,就会依赖RegistryDirectory提供服务列表了,此时会调用它的doList()方法,默认情况下会返回所有的服务,但是Dubbo是支持服务路由的,你可以编写自己的路由规则,例如针对有状态的请求尽可能的调用同一台服务器。

Router是Dubbo提供的服务路由接口,Dubbo支持条件路由、文件路由和脚本路由,开发者可以基于Router实现自己的路由规则。Router被设计成链式结构,RegistryDirectory会构建RouterChain,利用SPI的自动激活特性,将激活的Router加入到RouterChain。

private RouterChain(URL url) {
    List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
        .getActivateExtension(url, "router");
    List<Router> routers = extensionFactories.stream()
        .map(factory -> factory.getRouter(url))
        .collect(Collectors.toList());
    initWithRouters(routers);
}

Invokers会经过Router的层层过滤,最终返回的Invokers才会被用来做负载均衡。

public List<Invoker<T>> doList(Invocation invocation) {
    List<Invoker<T>> invokers = null;
    try {
        invokers = routerChain.route(getConsumerUrl(), invocation);
    } catch (Throwable t) {}
    return invokers == null ? Collections.emptyList() : invokers;
}

RegistryDirectory还有一个比较重要的方法是toInvokers(),它会将ProviderUrls自动转换成对应的Invoker,依赖的其实还是Protocol,以dubbo协议为例,那就是创建DubboInvoker,此时就会和服务端建立网络连接。

invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);

2.2 StaticDirectory

StaticDirectory是一个静态的服务目录,实现很简单,不会和注册中心打交道,它内部的Invokers是在创建的时候直接指定的,什么时候会用到StaticDirectory呢? 当消费者想绕过注册中心,直接指定url,和Provider点对点直连时,就会用到StaticDirectory。

3. 总结

Directory接口用于Consumer发起RPC调用时提供服务列表,Dubbo内置了两种实现,分别是基于注册中心的RegistryDirectory,和静态服务StaticDirectory,后者在点对点直连时会用到,使用更多的是前者。 RegistryDirectory+注册中心实现了Dubbo服务自动注册与发现,RegistryDirectory会去注册中心订阅服务,然后将ProviderUrls转换成对应的Invoker,然后构建RouterChain,后续根据RPC调用的Invocation参数进行服务过滤,提供一组可用的Invokers交给LoadBalance做负载均衡。