了解了 Dubbo 的服务暴露、引用、触发的全过程后,在这我们总结下这些过程中涉及到的注册中心配置信息。通过了解这些配置信息,你可以多一个调试和理解 Dubbo 核心流程的视角。

ServcieConfig#export 来源

注册中心初始化和信息注册的关键代码如下:

  1. // => org.apache.dubbo.registry.integration.RegistryProtocol#export
  2. public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
  3. // ......
  4. // url to registry
  5. final Registry registry = getRegistry(registryUrl);
  6. final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
  7. // ......
  8. }

获取 Registry 的流程: 「Dubbo」3.0 注册中心配置项概览 - 图1其中 ServiceDiscoveryRegistry 不同于传统意义上的 registry(如 ZookeeperRegistry、NacosRegistry),它是 “面向服务”的。它不会直接和外部的注册中心直接交互,当调用 register(URL)subscribe(URL, NotifyListener) 会存储服务暴露和引用的 urlsWritableMetadataService 里。
之后可以通过 WritableMetadataService.getExportedURLs() 等方法获取暴露的 urls,相对的,可以通过 WritableMetadataService.getSubscribedURLs() 获取订阅的 urls
每个 ServiceDiscoveryRegistry 都拥有自己的 ServiceDiscovery 实例,其中 ServiceDiscovery 负责与外部注册中心的网络通讯,例如 ZookeeperServiceDiscovery 就是基于 Apache Curator X Discovery 封装的。

通过观察 ServiceDiscovery 接口,我们尝试分析 Dubbo 对注册中心的基础依赖要求:

  1. @SPI("zookeeper")
  2. public interface ServiceDiscovery extends Prioritized {
  3. // 生命周期:支持初始化、销毁
  4. void initialize(URL registryURL) throws Exception;
  5. void destroy() throws Exception;
  6. boolean isDestroy();
  7. // 服务注册:CUD,少了个 R
  8. void register(ServiceInstance serviceInstance) throws RuntimeException;
  9. void update(ServiceInstance serviceInstance) throws RuntimeException;
  10. void unregister(ServiceInstance serviceInstance) throws RuntimeException;
  11. // 服务发现:这就是那个 R 了
  12. // 获取服务列表
  13. Set<String> getServices();
  14. // 获取相关实例
  15. getInstances(...);
  16. // 事件支持:
  17. // 触发事件
  18. dispatchServiceInstancesChangedEvent(...);
  19. // 添加事件监听
  20. addServiceInstancesChangedListener(...);
  21. // 移除事件监听
  22. removeServiceInstancesChangedListener(...);
  23. // 监听服务实例变化
  24. ServiceInstancesChangedListener createListener(Set<String> serviceNames);
  25. }

ReferenceConfig#get 来源

以读和监听为主,在接口级服务发现时候会往编号 2 的 consumers 目录注册接口服务订阅者信息。

注册信息快查表

此处,我们以 zookeeper 为注册中心,通过 PrettyZoo 连接远端注册中心服务器,来看每一步都注册了什么。

// => org.apache.dubbo.registry.integration.RegistryProtocol#export
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    // ......

    // decide if we need to delay publish
    boolean register = providerUrl.getParameter(REGISTER_KEY, true);
    if (register) {
        // 此处都是往 WritableMetadataService 写入,没有涉及到远端的注册中心
        register(registry, registeredProviderUrl);
    }

    // ......
}
阶段 出场编号 目录 1 段 目录 2 段 目录 3 段 节点类型 作用
暴露 1 /dubbo/config /DUBBO_SERVICEDISCOVERY_MIGRATION dubbo-demo-triple-api-provider.migration 永久节点 迁移规则监听
2 /dubbo/{InterfaceName}

例如:
/dubbo/org.apache.dubbo.demo.GreeterService
/{CATEGORY}
例如:/providers 或 /consumers
协议 URL

tri%3A%2F%2F192.168.31.95%3A50051%2Forg.apache.dubbo.demo.GreeterService%3Fanyhost%3Dtrue%26application%3Ddubbo-demo-triple-api-provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dorg.apache.dubbo.demo.GreeterService%26metadata-type%3Dremote%26methods%3DsayHello%26pid%3D1965%26release%3D%26service-name-mapping%3Dtrue%26side%3Dprovider%26timestamp%3D1628859567247
IP 地址

似乎获取值有问题!
临时节点 注册服务提供者相关信息
注册服务消费者相关信息

对于服务消费者来说,还会发起对 目录 2 段的监听,实现服务发现和动态路由
3 /dubbo/{InterfaceName}

例如:
/dubbo/org.apache.dubbo.demo.GreeterService
/configurators
永久节点 订阅服务
4 /dubbo/metadata /{InterfaceName}/{CATEGORY}

例如:
/org.apache.dubbo.demo.GreeterService/provider
应用名

例如:
/dubbo-demo-triple-api-provider
FullServiceDefinition 定义,使用 GSON 作序列化

序列化对象是:服务接口定义
永久节点 元数据
注册提供者或消费者完整服务定义
5 /dubbo/mapping /org.apache.dubbo.demo.GreeterService 应用名,例如:

dubbo-demo-triple-api-provider
永久节点 服务归属应用映射

用于用户在使用应用级服务发现,但未指定应用名时的兼容
6 /dubbo/metadata /org.apache.dubbo.metadata.MetadataService/{VERSION}

例如:
/org.apache.dubbo.metadata.MetadataService/1.0.0
/dubbo-demo-triple-api-provider/provider/dubbo-demo-triple-api-provider FullServiceDefinition 定义,使用 GSON 作序列化

序列化对象是:MetadataService 接口
永久节点 注册 MetadataService 实例信息
7 /dubbo/metadata /{APPLICATION_NAME}

例如:
/dubbo-demo-triple-api-provider
/{REVISION}

例如:
/9eb93cf97837ad9b71d1a20eca044606
MetadataInfo 序列化对象,
使用 GSON 作序列化
永久节点 注册 MetadataInfo 信息
8 /services /{APPLICATION_NAME}


例如:
/dubbo-demo-triple-api-provider
/{ServiceInstance}



例如
/127.0.0.1:50051
存储服务实例信息,用于应用级服务发现 临时节点 用于监听应用变更通知

应用级服务发现重度依赖该配置信息

编号 1 来源于 MigrationRuleListener
org.apache.dubbo.registry.integration.RegistryProtocol#notifyExport 触发:

// => org.apache.dubbo.registry.client.migration.MigrationRuleListener#MigrationRuleListener
public MigrationRuleListener() {
    this.configuration = ApplicationModel.getEnvironment().getDynamicConfiguration().orElse(null);

    // ......
    // 尝试去远端获取 key 为 /dubbo/config/DUBBO_SERVICEDISCOVERY_MIGRATION/dubbo-demo-triple-api-provider.migration 的配置,该命令有类似 Linux mkdirp 的效果,如果上级目录不存在会尝试创建,但由于 key 值存在,就
    String rawRule = configuration.getConfig(RULE_KEY, DUBBO_SERVICEDISCOVERY_MIGRATION);
}

编号 2 来源于 ZookeeperRegistry


// => org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister
public void doRegister(URL url) {
    try {
        // 具体放到 providers 还是 consumers 目录,依赖 URL 的 category 参数,默认是 providers
        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);
    }
}

// consumers 关键节点
// 1 => org.apache.dubbo.registry.integration.RegistryProtocol#getInvoker
// 2 => org.apache.dubbo.registry.integration.RegistryProtocol#doCreateInvoker

// providers 关键节点
// => org.apache.dubbo.registry.integration.RegistryProtocol#export

编号 3 来源于 ZookeeperRegistry

// 上游触发是:org.apache.dubbo.registry.RegistryService#subscribe
// => org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe
public void doSubscribe(final URL url, final NotifyListener listener) {
    // ......
    // 入参数的时候会尝试读取 key 值为 category 的 url 参数,确定生成哪些目录
    // 例如 category=configurators
    for (String path : toCategoriesPath(url)) {
        ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.computeIfAbsent(url, k -> new ConcurrentHashMap<>());
        ChildListener zkListener = listeners.computeIfAbsent(listener, k -> new RegistryChildListenerImpl(url, path, k, latch));
        if (zkListener instanceof RegistryChildListenerImpl) {
            ((RegistryChildListenerImpl) zkListener).setLatch(latch);
        }

        // 创建节点
        zkClient.create(path, false);
        List<String> children = zkClient.addChildListener(path, zkListener);
        if (children != null) {
            urls.addAll(toUrlsWithEmpty(url, path, children));
        }
    }
    // ......
}

编号 4 来自于 ServiceConfig

// => org.apache.dubbo.config.ServiceConfig#exportUrl
MetadataUtils.publishServiceDefinition(url);


// = > org.apache.dubbo.metadata.report.support.AbstractMetadataReport#storeProviderMetadata
public void storeProviderMetadata(MetadataIdentifier providerMetadataIdentifier, ServiceDefinition serviceDefinition) {
    // 注意此方法可以是异步执行的,当初调试研究注册流程的时候,这边错过了很多次,浪费了一些时间。
    if (syncReport) {
        storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition);
    } else {
        reportCacheExecutor.execute(() -> storeProviderMetadataTask(providerMetadataIdentifier, serviceDefinition));
    }
}

编号 5 来自于 ServiceConfig

// => org.apache.dubbo.config.ServiceConfig#exported
protected void exported() {
    exported = true;
    List<URL> exportedURLs = this.getExportedUrls();
    exportedURLs.forEach(url -> {
        if (url.getParameters().containsKey(SERVICE_NAME_MAPPING_KEY)) {
            ServiceNameMapping serviceNameMapping = ServiceNameMapping.getDefaultExtension();
            // 映射服务名
            serviceNameMapping.map(url);
        }
    });
    onExported();
}

编号 6、7 来自于 DubboBootstrap

// => org.apache.dubbo.config.bootstrap.DubboBootstrap#doStart
private void doStart() {
    // ......

    // If register consumer instance or has exported services
    if (isRegisterConsumerInstance() || hasExportedServices()) {
        // 2. export MetadataService
        // 编号 6 来源
        exportMetadataService();
        // 3. Register the local ServiceInstance if required
        // 编号 7 来源,同样是异步任务
        // 后续传为 ServiceInstanceMetadataUtils.refreshMetadataAndInstance
        registerServiceInstance();
    }

    // ......
}

编号 8 来自于 ZookeeperServiceDiscovery

// => org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscovery#doRegister
// Provider 端触发,向注册中心注册服务实例信息,依赖 Zookeeper 的 ServiceDiscovery 拓展
public void doRegister(ServiceInstance serviceInstance) {
    try {
        serviceDiscovery.registerService(build(serviceInstance));
    } catch (Exception e) {
        throw new RpcException(REGISTRY_EXCEPTION, "Failed register instance " + serviceInstance.toString(), e);
    }
}


// => org.apache.dubbo.registry.zookeeper.ZookeeperServiceDiscovery#registerServiceWatcher
// Consumer 端也会尝试创建目录,但不会往目录里面写服务实例
protected void registerServiceWatcher(String serviceName, ServiceInstancesChangedListener listener) {
    String path = buildServicePath(serviceName);
    try {
        curatorFramework.create().creatingParentsIfNeeded().forPath(path);
    }

    // ......
}