Dubbo 服务发现

总览

学习 Dubbo 的服务发现机制,可以从以下几方面入手:

  1. 注册中心的配置
  2. 服务的注册
  3. 客户端拉取服务列表
  4. 服务列表的本地缓存
  5. 服务提供者列表变更的监听机制
  6. 服务发现的接口设计

1. 注册中心的配置

Dubbo 通过解析用户配置确定使用的注册中心。比如,用户配置了 zookeeper://127.0.0.1:2181,Dubbo 在导出服务时解析出 name = zookeeper,便拿着这个名字到 ExtensionLoader 加载得到 ZookeeperServiceDiscoveryFactory,该工厂类生产出 ZookeeperServiceDiscovery,后续 ServiceDiscoveryRegistry 便可以通过调用 ZookeeperServiceDiscovery 的 doRegister 方法注册服务。

2. 服务的注册

Spring 执行 refresh 方法后会触发 Dubbo 将服务注册到注册中心。

Spring 的 AbstractApplicationContext 执行 refresh 方法返回前,会发布 ContextRefreshedEvent, DubboBootstrapApplicationListener 接收到 ContextRefreshedEvent 后,会初始化并导出服务,最后将服务注册到注册中心。

/**
 * An ApplicationListener to control Dubbo application.
 */
public class DubboDeployApplicationListener
        implements ApplicationListener<ApplicationContextEvent>, ApplicationContextAware, Ordered {

    @Override
    public void onApplicationEvent(ApplicationContextEvent event) {
        if (nullSafeEquals(applicationContext, event.getSource())) {
            if (event instanceof ContextRefreshedEvent) {
                onContextRefreshedEvent((ContextRefreshedEvent) event);
            }
            ...
        }
    }

    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        ModuleDeployer deployer = moduleModel.getDeployer();
        Assert.notNull(deployer, "Module deployer is null");
        Object singletonMutex = LockUtils.getSingletonMutex(applicationContext);
        // start module
        Future future = null;
        synchronized (singletonMutex) {
            future = deployer.start();
        }

        ...
    }
}

ModuleDeployer 在 start 方法中调用了 registerServices 方法,完成了服务的注册。其调用链如下:

ModuleDeployer.registerServices -> ModuleDeployer.registerServiceInternal
-> ServiceConfigBase.register -> Exporter.register

其中,ModuleDeployer.registerServiceInternal 遍历 configManager 中的每一个服务,逐个对它们进行注册。

Exporter.register 实际调用的类视注册中心的不同而不同,以 ZooKeeper 为例,通过模版方法实现服务注册:

public abstract class FailbackRegistry extends AbstractRegistry {
    @Override
    public void register(URL url) {
        if (!shouldRegister(url)) {
            return;
        }
        super.register(url);
        removeFailedRegistered(url);
        removeFailedUnregistered(url);
        try {
            // Sending a registration request to the server side
            doRegister(url);
        } catch (Exception e) {
            Throwable t = e;

            // If the startup detection is opened, the Exception is thrown directly.
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true)
                    && (url.getPort() != 0);
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if (skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException(
                        "Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: "
                                + t.getMessage(),
                        t);
            } else {
                logger.error(
                        INTERNAL_ERROR,
                        "unknown error in registry module",
                        "",
                        "Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(),
                        t);
            }

            // Record a failed registration request to a failed list, retry regularly
            addFailedRegistered(url);
        }
    }
}

doRegister 仅仅是在 Zookeeper 创建一个 ZNode(Zookeeper 文件系统中的节点):

    @Override
    public void doRegister(URL url) {
        try {
            checkDestroyed();
            zkClient.create(toUrlPath(url), url.getParameter(DYNAMIC_KEY, true), true);
        } catch (Throwable e) {
            throw new RpcException(
                    "Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

3. 客户端拉取服务列表

在 Dubbo 里,拉取服务端列表这一操作主要由 RegistryDirectory 类来完成。

RegistryDirectory 类是 Dubbo 服务发现机制里的关键类,它的主要功能是从注册中心拉取服务提供者列表,并且把这些提供者信息转换为 Invoker 对象列表。Invoker 代表着可执行的服务调用,消费者端借助 Invoker 来发起远程服务调用。

RegistryDirectory 的关键方法如下:

subscribe 方法
此方法的作用是向注册中心订阅服务提供者列表的变化。一旦注册中心的服务提供者列表有变动,注册中心就会通知 RegistryDirectory。

RegistryDirectory 最核心的代码是调用 RegistryService.subscribe 方法。

public abstract class DynamicDirectory<T> extends AbstractDirectory<T> implements NotifyListener {

    public void subscribe(URL url) {
        setSubscribeUrl(url);
        registry.subscribe(url, this);
    }
}

notify 方法
当注册中心的服务提供者列表发生变化时,会调用 notify 方法。该方法会接收注册中心传来的服务提供者列表,并更新本地的 Invoker 列表。

public class RegistryDirectory<T> extends DynamicDirectory<T> {

    @Override
    public synchronized void notify(List<URL> urls) {
        ...
        refreshOverrideAndInvoker(providerURLs);
    }
}

refreshInvoker 方法
refreshInvoker 方法会把从注册中心获取的服务提供者列表转换为 Invoker 列表,并且更新本地的 Invoker 缓存。

public class RegistryDirectory<T> extends DynamicDirectory<T> {
	...
    private void refreshInvoker(List<URL> invokerUrls) {
            List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
            BitList<Invoker<T>> finalInvokers =
                    multiGroup ? new BitList<>(toMergeInvokerList(newInvokers)) : new BitList<>(newInvokers);
            // pre-route and build cache
            refreshRouter(finalInvokers.clone(), () -> this.setInvokers(finalInvokers));
            this.urlInvokerMap = newUrlInvokerMap;

            try {
                destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
            } catch (Exception e) {
                logger.warn(REGISTRY_FAILED_DESTROY_SERVICE, "", "", "destroyUnusedInvokers error. ", e);
            }

            // notify invokers refreshed
            this.invokersChanged();
        }
    }
}

调用流程

  1. 服务引用:在消费者端引用服务时,会创建 RegistryDirectory 实例,并且调用其 subscribe 方法向注册中心订阅服务提供者列表的变化。
  2. 注册中心通知:当注册中心的服务提供者列表发生变化时,会调用 RegistryDirectory 的 notify 方法。
  3. 更新 Invoker 列表:notify 方法会调用 refreshInvoker 方法,将从注册中心获取的服务提供者列表转换为 Invoker 列表,并且更新本地的 Invoker 缓存。
  4. 服务调用:消费者端在进行服务调用时,会从 RegistryDirectory 的 invokers 列表中选取一个 Invoker 来发起远程服务调用。

不同注册中心主动通知的实现方式

所有 Registry 必须支持主动通知,不同注册中心底层实现机制可能不同:

Zookeeper
基于 Watcher 机制:消费者订阅服务节点后,Zookeeper 通过 Watcher 主动推送节点变更事件(如节点新增、删除)。

Nacos
基于长轮询(Long Polling)或 UDP 推送:Nacos 服务端会主动推送服务列表变化事件给消费者。

Consul
基于阻塞查询(Blocking Queries)或 Watcher:Consul 通过 HTTP 长连接等待服务列表变化,超时后重新发起请求。

Etcd
基于 Watch 机制:类似 Zookeeper,通过 gRPC 长连接监听键值变化。

值得注意的是,主动通知是一次性的,如果通知失败,客户端缓存的服务提供者列表可能长期与注册中心不一致。所以,需要其他机制保证客户端缓存的服务提供者列表是最新的。

  1. Dubbo 消费者与服务提供者之间保持 TCP 长连接,消费者会定期检测连接的活跃性。如果发现某个提供者的连接断开(如进程崩溃、网络故障),消费者会立即将其从本地服务列表中剔除。
  2. 当 Dubbo 客户端与 ZooKeeper 注册中心的 TCP 连接断开后重连(如网络闪断),客户端会强制重新拉取全量服务列表,确保本地缓存与服务端状态一致。
  3. 通过 Dubbo Admin 等管控台工具,可手动强制刷新消费者本地缓存,使其立即从注册中心拉取最新服务列表。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值