总览
学习 Dubbo 的服务发现机制,可以从以下几方面入手:
- 注册中心的配置
- 服务的注册
- 客户端拉取服务列表
- 服务列表的本地缓存
- 服务提供者列表变更的监听机制
- 服务发现的接口设计
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();
}
}
}
调用流程
- 服务引用:在消费者端引用服务时,会创建 RegistryDirectory 实例,并且调用其 subscribe 方法向注册中心订阅服务提供者列表的变化。
- 注册中心通知:当注册中心的服务提供者列表发生变化时,会调用 RegistryDirectory 的 notify 方法。
- 更新 Invoker 列表:notify 方法会调用 refreshInvoker 方法,将从注册中心获取的服务提供者列表转换为 Invoker 列表,并且更新本地的 Invoker 缓存。
- 服务调用:消费者端在进行服务调用时,会从 RegistryDirectory 的 invokers 列表中选取一个 Invoker 来发起远程服务调用。
不同注册中心主动通知的实现方式
所有 Registry 必须支持主动通知,不同注册中心底层实现机制可能不同:
Zookeeper
基于 Watcher 机制:消费者订阅服务节点后,Zookeeper 通过 Watcher 主动推送节点变更事件(如节点新增、删除)。
Nacos
基于长轮询(Long Polling)或 UDP 推送:Nacos 服务端会主动推送服务列表变化事件给消费者。
Consul
基于阻塞查询(Blocking Queries)或 Watcher:Consul 通过 HTTP 长连接等待服务列表变化,超时后重新发起请求。
Etcd
基于 Watch 机制:类似 Zookeeper,通过 gRPC 长连接监听键值变化。
值得注意的是,主动通知是一次性的,如果通知失败,客户端缓存的服务提供者列表可能长期与注册中心不一致。所以,需要其他机制保证客户端缓存的服务提供者列表是最新的。
- Dubbo 消费者与服务提供者之间保持 TCP 长连接,消费者会定期检测连接的活跃性。如果发现某个提供者的连接断开(如进程崩溃、网络故障),消费者会立即将其从本地服务列表中剔除。
- 当 Dubbo 客户端与 ZooKeeper 注册中心的 TCP 连接断开后重连(如网络闪断),客户端会强制重新拉取全量服务列表,确保本地缓存与服务端状态一致。
- 通过 Dubbo Admin 等管控台工具,可手动强制刷新消费者本地缓存,使其立即从注册中心拉取最新服务列表。

2531

被折叠的 条评论
为什么被折叠?



