Dubbo 双注册原理解析
Dubbo3.0 相对于 Dubbo 2.0 非常显著的一个优势就是提供了应用级服务发现机制
- 应用级服务发现 从服务/接口粒度到应用粒度的升级,使得 Dubbo 在集群可伸缩性、连接异构微服务体系上更具优势。应用粒度能以更低的资源消耗支持超百万实例规模集群程; 实现与 Spring Cloud、Kubernetes Service 等异构微服务体系的互联互通。
应用级服务发现优势
看了上面的介绍,可能大家还是对应用级发现没有一个概念。简单来说,应用级服务发现相对于接口级服务发现,所消耗的资源会更小。在应用大面积启动或重启时,能大幅度的减轻对于注册中心的压力。同时对于消费端而言,在选择服务地址、接收服务的上下线时的计算消耗更小
下面简单讲一下 应用级服务发现 和 接口级服务发现 的区别
以一个中等规模的集群实例来说: 2000 实例、50个应用(500 个 Dubbo 接口,平均每个应用 10 个接口)。
假设每个接口级 URL 地址平均大小为 5kb,每个应用级 URL 平均大小为 0.5kb
- 接口级应用发现
接口级应用发现会针对于每个接口分别在注册中心注册一个provider节点,例如 /dubbo/org.apache.dubbo.DubboSampleService/providers/xxxx%192.168.10.0%xxxxx
以上面的集群规模来计算的话,
-
注册节点数量:2000 * 500 = 100000
-
消耗的容量:2000 * 500 * 5kb ≈ 4.8G
-
应用级接口发现
应用级接口发现不再单独注册 providers节点,改为仅注册 应用服务地址,依靠服务的元数据信息和服务地址来拼接出对应的服务地址
即仅注册服务的元数据信息以及服务的实例地址
元数据信息存储和dubbo 2.0相同,存储在/dubbo/metadata中
服务的实例存储在 /services/{appname}中,例如 /services/demo/192.168.10.0:50051
以上面的集群规则来计算的话
-
注册节点数量:2000 * 50 = 10000
-
消耗的容量:2000 * 50 * 0.5kb ≈ ≈ 48M
可以看到应用级服务发现注册消耗的资源和注册的节点数量都大幅度的下降
双注册简介
在不改变任何 Dubbo 配置的情况下,可以将一个应用或实例升级到 3.x 版本,升级后的 Dubbo 实例会默保保证与 2.x 版本的兼容性,即会正常注册 2.x 格式的地址到注册中心,因此升级后的实例仍会对整个集群仍保持可见状态。
同时新的地址发现模型(注册应用级别的地址)也将会自动注册。
关于官网提供双注册的图我这里贴一下,方便了解:
双注册控制参数:
-
dubbo.application.registry-mode
- interface - 接口级注册
- instance - 应用级注册
- all - 双注册(默认)
双注册配置入口
双注册配置入口 位于 ServiceConfig中的doExportUrls()方法中,具体逻辑详情可以看 服务暴露源码解析
我们这里就简单来看下入口位置
private void doExportUrls() {
// ...省去代码
List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
// ...省去代码
}
然后就是具体注册中心地址的获取过程我们看下: ConfigValidationUtils的加载注册中心地址方法loadRegistries
public static List<URL> loadRegistries(AbstractInterfaceConfig interfaceConfig, boolean provider) {
// ...省去代码
List<RegistryConfig> registries = interfaceConfig.getRegistries();
return genCompatibleRegistries(interfaceConfig.getScopeModel(), registryList, provider);
}
genCompatibleRegistries方法就是兼容应用级和接口级服务发布的地方,来看下
private static List<URL> genCompatibleRegistries(ScopeModel scopeModel, List<URL> registryList, boolean provider) {
List<URL> result = new ArrayList<>(registryList.size());
registryList.forEach(registryURL -> {
if (provider) {
// for registries enabled service discovery, automatically register interface compatible addresses.
String registerMode;
// 默认RegistryURL是Registry,这段逻辑是不会走的
if (SERVICE_REGISTRY_PROTOCOL.equals(registryURL.getProtocol())) {
// 这部分代码取掉,直接看下面部分的逻辑
} else {
// 从URL中获取registry-model参数,没有的话从域模型中获取,都没有取到则使用默认值 all
registerMode = registryURL.getParameter(REGISTER_MODE_KEY, ConfigurationUtils.getCachedDynamicProperty(scopeModel, DUBBO_REGISTER_MODE_DEFAULT_KEY, DEFAULT_REGISTER_MODE_ALL));
if (!isValidRegisterMode(registerMode)) {
registerMode = DEFAULT_REGISTER_MODE_INTERFACE;
}
// 如果registry-model为instance或all的话,配置service-registry-discovery URL,即应用级发布URL
if ((DEFAULT_REGISTER_MODE_INSTANCE.equalsIgnoreCase(registerMode) || DEFAULT_REGISTER_MODE_ALL.equalsIgnoreCase(registerMode))
&& registryNotExists(registryURL, registryList, SERVICE_REGISTRY_PROTOCOL)) {
URL serviceDiscoveryRegistryURL = URLBuilder.from(registryURL)
.setProtocol(SERVICE_REGISTRY_PROTOCOL)
.removeParameter(REGISTRY_TYPE_KEY)
.build();
result.add(serviceDiscoveryRegistryURL);
}
// 如果registry-model为instance或all的话,代表为接口级服务发布,直接将registryURL添加到新数组中
if (DEFAULT_REGISTER_MODE_INTERFACE.equalsIgnoreCase(registerMode) || DEFAULT_REGISTER_MODE_ALL.equalsIgnoreCase(registerMode)) {
result.add(registryURL);
}
}
// ...省去代码
});
return result;
}
可以看到这里简化的配置比较容易理解了
- 双注册模式配置查询 对应参数为dubbo.application.register-mode ,默认值为all
- 如果用户配置了一个错误的注册模式配置则只走接口级配置 这里默认值为interface
- 满足应用级注册就添加一个应用级注册的地址
- 满足接口级注册配置就添加一个接口级注册地址
接口级与应用级服务注册
服务注册调用逻辑
先来看下双注册的接口级和应用级的url具体的样子:
- 接口级服务发现URL
registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-sample&client=curator&dubbo=2.0.2&metadata-service-protocol=tri&pid=2508&qos.enable=false®istry=zookeeper&release=3.1.1×tamp=1686562781647
- 应用级服务发现URL
service-discovery-registry://localhost:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-sample&client=curator&dubbo=2.0.2&metadata-service-protocol=tri&pid=2508&qos.enable=false®istry=zookeeper&release=3.1.1×tamp=1686562781647
服务注册入口位于RegistryProtocol中的export方法中,其中会调用registry方法,registry方法完成服务的注册
RegistryProtocol#export方法如下:
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 获取服务发现URL
URL registryUrl = getRegistryUrl(originInvoker);
// 获取服务暴露URL
URL providerUrl = getProviderUrl(originInvoker);
// ...省去代码
// 根据服务发现URL获取注册中心实例
final Registry registry = getRegistry(registryUrl);
// 根据服务暴露URL向服务发现URL转换
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// decide if we need to delay publish (provider itself and registry should both need to register)
boolean register = providerUrl.getParameter(REGISTER_KEY, true) && registryUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 注册服务
register(registry, registeredProviderUrl);
}
// ...省去代码
return new DestroyableExporter<>(exporter);
}
获取注册中心实例
protected Registry getRegistry(final URL registryUrl) {
//这里分为两步先获取注册中心工厂对象,这里是dubbo spi自适应获取,不做介绍
RegistryFactory registryFactory = ScopeModelUtil.getExtensionLoader(RegistryFactory.class, registryUrl.getScopeModel()).getAdaptiveExtension();
// 注册中心工厂对象通过服务发现URL获取注册中心
return registryFactory.getRegistry(registryUrl);
}
获取注册中心实例部分涉及到 Dubbo SPI 动态代码,不好DEBUG进去,直接来看下工厂实例的整个结构图,这里以zookeeper为例子
这里来解析下每个类的作用
-
RegistryFactory - 注册中心实例工厂接口,不管是zookeeper、nacos还是其他的注册中心,都需要实现此接口
-
ScopeModelAware - 域模型自动注入接口,根据 Dubbo SPI 相关机制来获取扩展对象时,SPI 会将相关域模型通过此接口注入到对应的扩展对象中
-
AbstractRegistryFactory - 注册中心实例抽象工厂,像zookeeper、nacos等注册中心都是继承自此抽象类,这个抽象类直接实现了RegistryFactory的getResgitry方法,所有子类不用重写,直接使用即可。同时里面维护了注册中心实例的缓存,避免重复创建。
-
ZookeeperRegistryFactory - zookeeper注册中心实例工厂类,内部实现了AbstractRegistryFactory的createRegistry方法,用于创建zookeeper注册中心实例
-
ServiceDiscoveryRegistryFactory - 用于创建服务发现注册中心工厂对象 用于创建ServiceDiscoveryRegistry对象
-
RegistryFactoryWrapper - 用于创建注册中心实例的包装类,ListenerRegistryWrapper对象,里面封装了相关的监听器,用于注册中心相关行为完成后的通知,所有的注册中心实例都会被此包装类休息
接下来看下注册中心实例Registry的结构图,依旧以zookeeper为例
- Node - 注册中心实例节点信息获取接口,里面有三个方法,分别是获取服务注册URL、获取注册中心实例可用状态、注册中心销毁
- RegistryService - 用于服务的注册、订阅、取消订阅
- Registry - 注册中心接口,是否服务发现查询,注册,取消注册方法
- AbstractRegistry - 注册中心逻辑抽象模板类型,封装了注册,订阅,通知的基本逻辑,和本地缓存注册中心信息的基本逻辑
- RegistryFactoryWrapper - 封装了注册中心完成注册、订阅等动作后的监听器通知
- FailBackRegistry - 封装了失败重试的逻辑
- ServiceDiscoveryRegistry - 实现了应用级服务发现注册中心逻辑,这个是一个比较特殊的注册中心,兼容了旧的服务发现模型和3.0的服务发现模型,即服务的元数据信息。ServiceDiscoveryRegistry并不会直接与zookeper或nacos等注册中心交互,而是与ServiceMetadata组件交互。作用如下: 1. 通过将接口级数据聚合为metadatainfo,用于后续的的整个服务订阅过程的处理 2. 启动新的服务发现侦听器(InstanceListener),并使NotifierListener成为InstanceListener的一部分,用于后续的实例上下线的监听以及兼容
- CacheableFailbackRegistry 提供了一些本地内存缓存的逻辑 对注册中心有用,注册中心的sdk将原始字符串作为提供程序实例返回,例如zookeeper和etcd
- ZookeeperRegistry Zookeeper作为注册中心的基本操作逻辑封装
回到获取注册中心实例的代码
protected Registry getRegistry(final URL registryUrl) {
//这里分为两步先获取注册中心工厂对象,这里是dubbo spi自适应获取,不做介绍
RegistryFactory registryFactory = ScopeModelUtil.getExtensionLoader(RegistryFactory.class, registryUrl.getScopeModel()).getAdaptiveExtension();
// 注册中心工厂对象通过服务发现URL获取注册中心
return registryFactory.getRegistry(registryUrl);
}
如果RegistryUrl是应用级服务发现URL,将会通过ServiceDiscoveryRegistryFactory来获取注册中心
但是并不是在直接获取ServiceDiscoveryRegistryFactory,而是获取到其装饰类RegistryFactoryWrapper
先来看下RegistryFactoryWrapper 整个类
public class RegistryFactoryWrapper implements RegistryFactory {
private RegistryFactory registryFactory;
// @Adaptive逻辑会根据具体的协议获取对应的注册中心工厂并注入进来,应用级服务注册地址注入的是ServiceDiscoveryRegistryFactory
public RegistryFactoryWrapper(RegistryFactory registryFactory) {
this.registryFactory = registryFactory;
}
@Override
public Registry getRegistry(URL url) {
// 先执行真正的注册中心工厂的获取注册中心逻辑,返回注册中心实例后使用ListenerRegistryWrapper包装
return new ListenerRegistryWrapper(registryFactory.getRegistry(url),
Collections.unmodifiableList(url.getOrDefaultApplicationModel().getExtensionLoader(RegistryServiceListener.class)
.getActivateExtension(url, "registry.listeners")));
}
}
再来看下ListenerRegistryWrapper的构造
public ListenerRegistryWrapper(Registry registry, List<RegistryServiceListener> listeners) {
// 这是注册中心实例,也就是装饰
this.registry = registry;
// 设置监听器
this.listeners = listeners;
}
这里简单讲一下ListenerRegistryWrapper,这也是个装饰类,主要作用是在注册中心完成各自的注册、订阅等逻辑后进行监听器的回调通知
接下来来看下真正的应用级注册中心获取的逻辑,代码位于AbstractRegistryFactory中
public Registry getRegistry(URL url) {
// 检查注册中心管理器,即缓存
if (registryManager == null) {
throw new IllegalStateException("Unable to fetch RegistryManager from ApplicationModel BeanFactory. " +
"Please check if `setApplicationModel` has been override.");
}
// 销毁状态获取默认的注册中心并直接返回,默认的注册中心即未真正实现任何注册中心应有方法的一个实例,类似与一个空值
Registry defaultNopRegistry = registryManager.getDefaultNopRegistryIfDestroyed();
if (null != defaultNopRegistry) {
return defaultNopRegistry;
}
url = URLBuilder.from(url)
.setPath(RegistryService.class.getName())
.addParameter(INTERFACE_KEY, RegistryService.class.getName())
.removeParameter(TIMESTAMP_KEY)
.removeAttribute(EXPORT_KEY)
.removeAttribute(REFER_KEY)
.build();
String key = createRegistryCacheKey(url);
Registry registry = null;
boolean check = url.getParameter(CHECK_KEY, true) && url.getPort() != 0;
// Lock the registry access process to ensure a single instance of the registry
registryManager.getRegistryLock().lock();
try {
// 双重检索,防止注册中心管理器多线程下,管理器已被关闭,还能继续获取到注册中心
// fix https://github.com/apache/dubbo/issues/7265.
defaultNopRegistry = registryManager.getDefaultNopRegistryIfDestroyed();
if (null != defaultNopRegistry) {
return defaultNopRegistry;
}
// 检查管理器有是否已经包含对应url的注册中心,有则直接返回
registry = registryManager.getRegistry(key);
if (registry != null) {
return registry;
}
// 创建注册中心
registry = createRegistry(url);
if (check && registry == null) {
throw new IllegalStateException("Can not create registry " + url);
}
// 缓存
if (registry != null) {
registryManager.putRegistry(key, registry);
}
} catch (Exception e) {
if (check) {
throw new RuntimeException("Can not create registry " + url, e);
} else {
// 1-11 Failed to obtain or create registry (service) object.
LOGGER.warn(REGISTRY_FAILED_CREATE_INSTANCE, "", "",
"Failed to obtain or create registry ", e);
}
} finally {
// Release the lock
registryManager.getRegistryLock().unlock();
}
return registry;
}
来看下注册中心是如何创建的,createRegistry是AbstractRegistryFactory中的抽象方法
这里的示例是应用级服务发现,直接看ServiceDiscoveryRegistryFactory的实现
@Override
protected Registry createRegistry(URL url) {
// 检查服务发现协议是否为service-discovery-registry
if (UrlUtils.hasServiceDiscoveryRegistryProtocol(url)) {
// 获取服务注册协议
String protocol = url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY);
// 生成服务注册url
url = url.setProtocol(protocol).removeParameter(REGISTRY_KEY);
}
// 返回
return new ServiceDiscoveryRegistry(url, applicationModel);
}
通过以上代码可以看到其实最终创建的是一个ServiceDiscoveryRegistry注册中心对象,这个url协议被转换为了对应注册中心的协议,也就是说双注册会有两个协议一个是原先的接口级注册注册中心对象(这个还未说到)和这里对应注册中心协议的服务发现注册中心对象ServiceDiscoveryRegistry
ServiceDiscoveryRegistry
先来看下 ServiceDiscoveryRegistry 的构造方法
public ServiceDiscoveryRegistry(URL registryURL, ApplicationModel applicationModel) {
super(registryURL);
// 创建服务发现实例
this.serviceDiscovery = createServiceDiscovery(registryURL);
this.serviceNameMapping = (AbstractServiceNameMapping) ServiceNameMapping.getDefaultExtension(registryURL.getScopeModel());
super.applicationModel = applicationModel;
}
ServiceDiscoveryRegistry中创建服务发现对象createServiceDiscovery方法
protected ServiceDiscovery createServiceDiscovery(URL registryURL) {
return getServiceDiscovery(registryURL.addParameter(INTERFACE_KEY, ServiceDiscovery.class.getName())
.removeParameter(REGISTRY_TYPE_KEY));
}
private ServiceDiscovery getServiceDiscovery(URL registryURL) {
// 获取服务发现创建工厂,使用dubbo spi机制
ServiceDiscoveryFactory factory = getExtension(registryURL);
// 获取服务发现实例
return factory.getServiceDiscovery(registryURL);
}
再来看下ServiceDiscoveryRegistry的父类FailBackRegistry
FailBackRegistry是失败重试的的一个基础类,封装了相关重试的逻辑
public FailbackRegistry(URL url) {
super(url);
//重试间隔配置retry.period ,默认为5秒
this.retryPeriod = url.getParameter(REGISTRY_RETRY_PERIOD_KEY, DEFAULT_REGISTRY_RETRY_PERIOD);
// since the retry task will not be very much. 128 ticks is enough.
// 生成重试器,重试次数为128
retryTimer = new HashedWheelTimer(new NamedThreadFactory("DubboRegistryRetryTimer", true), retryPeriod, TimeUnit.MILLISECONDS, 128);
}
再往上看FailBackRegistry的父类AbstractRegistry
public AbstractRegistry(URL url) {
setUrl(url);
registryManager = url.getOrDefaultApplicationModel().getBeanFactory().getBean(RegistryManager.class);
//是否本地缓存默认为true
localCacheEnabled = url.getParameter(REGISTRY_LOCAL_FILE_CACHE_ENABLED, true);
registryCacheExecutor = url.getOrDefaultFrameworkModel().getBeanFactory()
.getBean(FrameworkExecutorRepository.class).getSharedExecutor();
if (localCacheEnabled) {
// Start file save timer 是否同步缓存默认为false
syncSaveFile = url.getParameter(REGISTRY_FILESAVE_SYNC_KEY, false);
//默认缓存的文件路径与文件名字为:/Users/song/.dubbo/dubbo-registry-dubbo-demo-api-provider-127.0.0.1-2181.cache
String defaultFilename = System.getProperty(USER_HOME) + DUBBO_REGISTRY +
url.getApplication() + "-" + url.getAddress().replaceAll(":", "-") + CACHE;
//未指定缓存的文件名字则用默认的文件名字
String filename = url.getParameter(FILE_KEY, defaultFilename);
File file = null;
//父目录创建,保证目录存在
if (ConfigUtils.isNotEmpty(filename)) {
file = new File(filename);
if (!file.exists() && file.getParentFile() != null && !file.getParentFile().exists()) {
if (!file.getParentFile().mkdirs()) {
throw new IllegalArgumentException("Invalid registry cache file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");
}
}
}
this.file = file;
// When starting the subscription center,
// we need to read the local cache file for future Registry fault tolerance processing.
//加载本地磁盘文件
loadProperties();
//变更推送
notify(url.getBackupUrls());
}
}
将服务提供者相关数据缓存到本地
前面已经分析了registry的整个结构和获取的基本逻辑,应用级服务发现的模式下,可以知道在RegistryProtocol中获取的注册中心为ServiceDiscoveryRegistry
现在来看看获取注册中心实例后干了什么
继续来看ResgitryProtocol的export方法
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
URL providerUrl = getProviderUrl(originInvoker);
// ...省去代码
// url to registry
final Registry registry = getRegistry(registryUrl);
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// decide if we need to delay publish (provider itself and registry should both need to register)
boolean register = providerUrl.getParameter(REGISTER_KEY, true) && registryUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 将服务暴露URL注册到注册中心中
register(registry, registeredProviderUrl);
}
// ...省去代码
}
来看下register方法
private void register(Registry registry, URL registeredProviderUrl) {
// 调用了注册中心的register方法
registry.register(registeredProviderUrl);
}
接着来看 ServiceDiscoveryRegistry 的register方法
public final void register(URL url) {
// 检查是否需要诸恶
if (!shouldRegister(url)) { // Should Not Register
return;
}
doRegister(url);
}
public void doRegister(URL url) {
// fixme, add registry-cluster is not necessary anymore
url = addRegistryClusterKey(url);
serviceDiscovery.register(url);
}
本文实例是zookeeper,serviceDiscovery的实例是ZookeeperServiceDiscovery,注册方法实现在抽象类AbstractServiceDiscovery中,来看下
public void register(URL url) {
// metadaInfo类型为MetadataInfo类型,用来操作元数据的
metadataInfo.addService(url);
}
接着来看下 MetadataInfo addService方法
public synchronized void addService(URL url) {
// fixme, pass in application mode context during initialization of MetadataInfo.
//元数据参数过滤器扩展获取:MetadataParamsFilter
if (this.loader == null) {
this.loader = url.getOrDefaultApplicationModel().getExtensionLoader(MetadataParamsFilter.class);
}
//元数据参数过滤器获取
List<MetadataParamsFilter> filters = loader.getActivateExtension(url, "params-filter");
// generate service level metadata
//生成服务级别的元数据
ServiceInfo serviceInfo = new ServiceInfo(url, filters);
this.services.put(serviceInfo.getMatchKey(), serviceInfo);
// extract common instance level params
extractInstanceParams(url, filters);
if (exportedServiceURLs == null) {
exportedServiceURLs = new ConcurrentSkipListMap<>();
}
addURL(exportedServiceURLs, url);
updated = true;
}
到这里 应用即服务发现的注册 逻辑已大致走完
整体来讲就是做
- 根据服务发现URL获取注册中心
- 使用注册中心将服务提供者相关数据缓存到本地
这里并没有真正往注册中心注册服务相关信息,只是往本地缓存了服务的相关信息,真正的服务信息注册可以在下面会讲到
ZookeeperRegistry
接口级服务发现和应用级服务发现都是需要走export方法
区别在于获取的示例不同
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
URL registryUrl = getRegistryUrl(originInvoker);
// url to export locally
URL providerUrl = getProviderUrl(originInvoker);
// ...省去代码
// url to registry
// 接口级服务发现这里获取的注册中心为具体的注册中心地址了
final Registry registry = getRegistry(registryUrl);
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// decide if we need to delay publish (provider itself and registry should both need to register)
boolean register = providerUrl.getParameter(REGISTER_KEY, true) && registryUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 将服务暴露URL注册到注册中心中
register(registry, registeredProviderUrl);
}
// ...省去代码
}
本文用的是zookeeper,所以注册中心实例返回为 ZookeeperRegistry
先来看下ZookeeperRegistry的构造器
public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
super(url);
// 校验服务注册地址
if (url.isAnyHost()) {
throw new IllegalStateException("registry address == null");
}
// 获取注册的节点根目录,默认为dubbo
String group = url.getGroup(DEFAULT_ROOT);
if (!group.startsWith(PATH_SEPARATOR)) {
group = PATH_SEPARATOR + group;
}
this.root = group;
// 初始化zookeeper客户端
this.zkClient = zookeeperTransporter.connect(url);
// 添加zookeeper监听
this.zkClient.addStateListener((state) -> {
if (state == StateListener.RECONNECTED) {
logger.warn("Trying to fetch the latest urls, in case there are provider changes during connection loss.\n" +
" Since ephemeral ZNode will not get deleted for a connection lose, " +
"there's no need to re-register url of this instance.");
ZookeeperRegistry.this.fetchLatestAddresses();
} else if (state == StateListener.NEW_SESSION_CREATED) {
logger.warn("Trying to re-register urls and re-subscribe listeners of this instance to registry...");
try {
ZookeeperRegistry.this.recover();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
} else if (state == StateListener.SESSION_LOST) {
logger.warn("Url of this instance will be deleted from registry soon. " +
"Dubbo client will try to re-register once a new session is created.");
} else if (state == StateListener.SUSPENDED) {
} else if (state == StateListener.CONNECTED) {
}
});
}
registry方法在FailBackRegistry中实现,调用了doRegistry方法,也是ZookeeperRegistry实现的
接下来直接看下doRegistry方法
public void doRegister(URL url) {
try {
// 检查实例是否被销毁
checkDestroyed();
// 注册 provider数据 到zookeeper中
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);
}
}
toUrlPath方法最终生成path示例,里面包含了服务暴露的url
/dubbo/org.apache.dubbo.sample.provider.DubboService/providers/tri%3A%2F%2F192.168.11.119%3A50051%2Forg.apache.dubbo.sample.provider.DubboService%3Fanyhost%3Dtrue%26application%3Ddubbo-sample%26background%3Dfalse%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26group%3Ddemo%26interface%3Dorg.apache.dubbo.sample.provider.DubboService%26ispuserver%3Dtrue%26metadata-service-protocol%3Dtri%26methods%3Dsend%26pid%3D27191%26register-mode%3Dinterface%26release%3D3.1.1%26side%3Dprovider%26timestamp%3D1686730095051
来看下最终注册到zookeeper的数据
Zookeeper断开连接或者Session超时的时候这个信息会被移除,具体在zookeeper监听器里实现
可以看到这里就于应用级服务发现存在区别了,接口级服务发现是直接往注册中心创建对应的providers的节点了
应用级服务发现模型ServiceDiscovery
先来看整个ZookeeperServiceDiscovery整个类的结构关系
整个结构层次与Registry类似,来看下每个类的大致的作用
- RegistryService: 注册中心接口,定义了注册中心需要实现的基本接口
- ServiceDiscovery: 服务发现接口,定义了服务发现实例需要实现的基本的接口
- AbstractServiceDiscovery: 服务发现抽象类,里面实现了注册中心相关接口的基本逻辑
- ZookeeperServiceDiscovery: 用于实现zookeeper服务发现相关逻辑
刚刚在 ServiceDiscoveryRegistry中创建服务发现对象getServiceDiscovery方法看到了两个类型一个是服务发现工厂类型ServiceDiscoveryFactory,一个是服务发现类型ServiceDiscovery
private ServiceDiscovery getServiceDiscovery(URL registryURL) {
//服务发现工厂对象的获取这里是ServiceDiscoveryFactory类型,这里对应ZookeeperServiceDiscoveryFactory
ServiceDiscoveryFactory factory = getExtension(registryURL);
//服务发现工厂对象获取服务发现对象
return factory.getServiceDiscovery(registryURL);
}
AbstractServiceDiscoveryFactory类型的getServiceDiscovery方法
@Override
public ServiceDiscovery getServiceDiscovery(URL registryURL) {
//这个key是 zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.client.ServiceDiscovery
//一个地址需要创建一个服务发现对象
String key = registryURL.toServiceStringWithoutResolving();
return discoveries.computeIfAbsent(key, k -> createDiscovery(registryURL));
}
createDiscovery方法对应ZookeeperServiceDiscoveryFactory类型中的createDiscovery方法
如下代码所示:
@Override
protected ServiceDiscovery createDiscovery(URL registryURL) {
return new ZookeeperServiceDiscovery(applicationModel, registryURL);
}
ZookeeperServiceDiscovery
先来看下ZookeeperServiceDiscovery的构造器
public ZookeeperServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
super(applicationModel, registryURL);
try {
// 创建 创建CuratorFramework 类型对象用于操作Zookeeper
this.curatorFramework = buildCuratorFramework(registryURL, this);
// 构建注册的节点的根路径,为/services,用于存储应用实例信息
this.rootPath = getRootPath(registryURL);
// 用于构建服务发现对象,serviceDiscovery实例为ServiceDiscoveryImpl,Curator框架中的discovery模块
this.serviceDiscovery = buildServiceDiscovery(curatorFramework, rootPath);
// 启动服务发现
this.serviceDiscovery.start();
} catch (Exception e) {
throw new IllegalStateException("Create zookeeper service discovery failed.", e);
}
}
这个方法比较重要是应用级服务发现的实现,这里主要关注下serviceDiscovery类型的创建与启动,这个应用级服务发现的实现其实是Dubbo使用了Curator来做的,Dubbo只是在这里封装了一些方法来进行调用Curator的实现
应用级节点注册由AbstractServiceDiscovery.register完成
先看下调用链路
可以看到是由DefaultModuleDeployer类触发的
DefaultModuleDeployer启动时负责了服务的导出与引入,完成之后会进行服务实例的注册
不熟悉服的可以看下前面的文章
来看下DefaultModuleDeployer startSync方法
private synchronized Future startSync() throws IllegalStateException {
// ... 省去代码
// 导出服务
exportServices();
// prepare application instance
// exclude internal module to avoid wait itself
if (moduleModel != moduleModel.getApplicationModel().getInternalModule()) {
applicationDeployer.prepareInternalModule();
}
// 引入服务
referServices();
// 非异步直接标记服务启动,这里也是服务实例注册的入口
if (asyncExportingFutures.isEmpty() && asyncReferringFutures.isEmpty()) {
onModuleStarted();
} else {
frameworkExecutorRepository.getSharedExecutor().submit(() -> {
try {
// 等待服务的引入和导出结束
// wait for export finish
waitExportFinish();
// wait for refer finish
waitReferFinish();
} catch (Throwable e) {
logger.warn("wait for export/refer services occurred an exception", e);
} finally {
onModuleStarted();
}
});
}
// ... 省去代码
}
AbstractServiceDiscovery register方法
public synchronized void register() throws RuntimeException {
// 检查服务发现实例是否已销毁
if (isDestroy) {
return;
}
// 生成服务实例信息
this.serviceInstance = createServiceInstance(this.metadataInfo);
// 校验实例信息是否有效
if (!isValidInstance(this.serviceInstance)) {
logger.warn("No valid instance found, stop registering instance address to registry.");
return;
}
// 检查是否需要更新实例信息
boolean revisionUpdated = calOrUpdateInstanceRevision(this.serviceInstance);
if (revisionUpdated) {
// 发布元数据信息
reportMetadata(this.metadataInfo);
// 注册节点实例信息
doRegister(this.serviceInstance);
}
}
ZookeeperServiceDiscovery register
public void doRegister(ServiceInstance serviceInstance) {
try {
serviceDiscovery.registerService(build(serviceInstance));
} catch (Exception e) {
throw new RpcException(REGISTRY_EXCEPTION, "Failed register instance " + serviceInstance.toString(), e);
}
}
来看下应用级服务注册的节点信息:
{
"name": "dubbo-sample",
"id": "192.168.11.119:50051",
"address": "192.168.11.119",
"port": 50051,
"sslPort": null,
"payload": {
"@class": "org.apache.dubbo.registry.zookeeper.ZookeeperInstance",
"id": "192.168.11.119:50051",
"name": "dubbo-sample",
"metadata": {
"dubbo.endpoints": "[{\"port\":50051,\"protocol\":\"tri\"}]",
"dubbo.metadata-service.url-params": "{\"connections\":\"1\",\"version\":\"1.0.0\",\"dubbo\":\"2.0.2\",\"release\":\"3.1.1\",\"side\":\"provider\",\"port\":\"50051\",\"protocol\":\"tri\"}",
"dubbo.metadata.revision": "ae7c8497b10e0bfef94f074121aa4781",
"dubbo.metadata.storage-type": "local",
"timestamp": "1686793869229"
}
},
"registrationTimeUTC": 1686793869613,
"serviceType": "DYNAMIC",
"uriSpec": null
}
AbstractServiceDiscovery的构造器
public AbstractServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
//调用重载的构造器
this(applicationModel.getApplicationName(), registryURL);
this.applicationModel = applicationModel;
MetadataReportInstance metadataReportInstance = applicationModel.getBeanFactory().getBean(MetadataReportInstance.class);
metadataType = metadataReportInstance.getMetadataType();
this.metadataReport = metadataReportInstance.getMetadataReport(registryURL.getParameter(REGISTRY_CLUSTER_KEY));
// if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataReportInstance.getMetadataType())) {
// this.metadataReport = metadataReportInstance.getMetadataReport(registryURL.getParameter(REGISTRY_CLUSTER_KEY));
// } else {
// this.metadataReport = metadataReportInstance.getNopMetadataReport();
// }
}
重载的构造器
public AbstractServiceDiscovery(String serviceName, URL registryURL) {
this.applicationModel = ApplicationModel.defaultModel();
//这个url参考:zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-api-provider&dubbo=2.0.2&interface=org.apache.dubbo.registry.client.ServiceDiscovery&pid=4570&release=3.0.8
this.registryURL = registryURL;
//这个serviceName参考dubbo-demo-api-provider
this.serviceName = serviceName;
//MetadataInfo 用来封装元数据信息
this.metadataInfo = new MetadataInfo(serviceName);
//这个是元数据缓存信息管理的类型 缓存文件使用LRU策略 感兴趣的可以详细看看
//对应缓存路径为:/Users/song/.dubbo/.metadata.zookeeper127.0.0.1%003a2181.dubbo.cache
this.metaCacheManager = new MetaCacheManager(getCacheNameSuffix(),
applicationModel.getFrameworkModel().getBeanFactory()
.getBean(FrameworkExecutorRepository.class).getCacheRefreshingScheduledExecutor());
}
双注册发布元数据至注册中心
前面讲了 接口级服务注册和应用级服务注册在注册中心上的区别,接下来来看看 两种服务发现方式的共同数据,元数据是如何发布到注册中心的
回到ServiceConfig的exportUrl方法
private void exportUrl(URL url, List<URL> registryURLs) {
String scope = url.getParameter(SCOPE_KEY);
// ... 省去代码
// 服务不是本地暴露
if (!SCOPE_LOCAL.equalsIgnoreCase(scope)) {
// export to extra protocol is used in remote export
String extProtocol = url.getParameter("ext.protocol", "");
List<String> protocols = new ArrayList<>();
// export original url
url = URLBuilder.from(url).
addParameter(IS_PU_SERVER_KEY, Boolean.TRUE.toString()).
removeParameter("ext.protocol").
build();
url = exportRemote(url, registryURLs);
if (!isGeneric(generic) && !getScopeModel().isInternal()) {
// 元数据信息发布
MetadataUtils.publishServiceDefinition(url, providerModel.getServiceModel(), getApplicationModel());
}
// ...省去代码
}
元数据信息发布位于exportUrl中MetadataUtils.publishServiceDefinition方法,这里是不区分具体是哪种服务发布的方式,也就是说,不管是应用级服务发布还是接口级服务发布,都会走到这一段逻辑
接下来来看下 MetadataUtils.publishServiceDefinition
public static void publishServiceDefinition(URL url, ServiceDescriptor serviceDescriptor, ApplicationModel applicationModel) {
// 检查是否存在元数据对象,对应接口MetadataReport 这里对应实现类 ZookeeperMetadataReport
if (getMetadataReports(applicationModel).size() == 0) {
String msg = "Remote Metadata Report Server is not provided or unavailable, will stop registering service definition to remote center!";
logger.warn(msg);
return;
}
try {
String side = url.getSide();
// 服务提供者逻辑
if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
String serviceKey = url.getServiceKey();
// 获取服务提供者元数据信息
FullServiceDefinition serviceDefinition = serviceDescriptor.getFullServiceDefinition(serviceKey);
if (StringUtils.isNotEmpty(serviceKey) && serviceDefinition != null) {
serviceDefinition.setParameters(url.getParameters());
for (Map.Entry<String, MetadataReport> entry : getMetadataReports(applicationModel).entrySet()) {
MetadataReport metadataReport = entry.getValue();
if (!metadataReport.shouldReportDefinition()) {
logger.info("Report of service definition is disabled for " + entry.getKey());
continue;
}
//存储服务提供者的元数据 metadataReport类型为ZookeeperMetadataReport 方法来源于父类模板方法: AbstractMetadataReport类型的storeProviderMetadata模板方法
metadataReport.storeProviderMetadata(
new MetadataIdentifier(
url.getServiceInterface(),
url.getVersion() == null ? "" : url.getVersion(),
url.getGroup() == null ? "" : url.getGroup(),
PROVIDER_SIDE,
applicationModel.getApplicationName())
, serviceDefinition);
}
}
} else {
// 消费者逻辑
for (Map.Entry<String, MetadataReport> entry : getMetadataReports(applicationModel).entrySet()) {
MetadataReport metadataReport = entry.getValue();
if (!metadataReport.shouldReportDefinition()) {
logger.info("Report of service definition is disabled for " + entry.getKey());
continue;
}
metadataReport.storeConsumerMetadata(
new MetadataIdentifier(
url.getServiceInterface(),
url.getVersion() == null ? "" : url.getVersion(),
url.getGroup() == null ? "" : url.getGroup(),
CONSUMER_SIDE,
applicationModel.getApplicationName()),
url.getParameters());
}
}
} catch (Exception e) {
//ignore error
logger.error("publish service definition metadata error.", e);
}
}
元数据对象(MetadatarReport)来源于dubbo的元数据中心的创建,由DefaultApplicationDeployer启动元数据中心时初始化
接下来先看下元数据信息的组装,元数据信息的组装由ServiceDescriptor来完成
来看下getFullServiceDefinition方法
public FullServiceDefinition getFullServiceDefinition(String serviceKey) {
// 根据服务接口class信息获取方法信息、参数等组装成FullServiceDefinition
return serviceDefinitions.computeIfAbsent(serviceKey,
(k) -> ServiceDefinitionBuilder.buildFullDefinition(serviceInterfaceClass, Collections.emptyMap()));
}
来看下 AbstractMetadataReport
public void storeConsumerMetadata(MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap) {
// 同步发布
if (syncReport) {
storeConsumerMetadataTask(consumerMetadataIdentifier, serviceParameterMap);
} else {
// 异步发布
reportCacheExecutor.execute(() -> storeConsumerMetadataTask(consumerMetadataIdentifier, serviceParameterMap));
}
}
storeConsumerMetadataTask方法
protected void storeConsumerMetadataTask(MetadataIdentifier consumerMetadataIdentifier, Map<String, String> serviceParameterMap) {
try {
if (logger.isInfoEnabled()) {
logger.info("store consumer metadata. Identifier : " + consumerMetadataIdentifier + "; definition: " + serviceParameterMap);
}
allMetadataReports.put(consumerMetadataIdentifier, serviceParameterMap);
failedReports.remove(consumerMetadataIdentifier);
String data = JsonUtils.getJson().toJson(serviceParameterMap);
// 注册元数据信息至注册中心
doStoreConsumerMetadata(consumerMetadataIdentifier, data);
saveProperties(consumerMetadataIdentifier, data, true, !syncReport);
} catch (Exception e) {
// retry again. If failed again, throw exception.
failedReports.put(consumerMetadataIdentifier, serviceParameterMap);
metadataReportRetry.startRetryTask();
logger.error("Failed to put consumer metadata " + consumerMetadataIdentifier + "; " + serviceParameterMap + ", cause: " + e.getMessage(), e);
}
}
元数据信息如下:可以分为两类 应用元数据,服务元数据
{
"annotations":[
],
"canonicalName":"org.apache.dubbo.sample.provider.DubboService",
"codeSource":"file:/Users/amberdata/IdeaProjects/dubbo_sample/target/classes/",
"methods":[
{
"annotations":[
],
"name":"send",
"parameterTypes":[
"java.lang.String"
],
"parameters":[
],
"returnType":"org.apache.dubbo.sample.provider.SampleDTO"
}
],
"parameters":{
"application":"dubbo-sample",
"release":"3.1.1",
"interface":"org.apache.dubbo.sample.provider.DubboService",
"pid":"48998",
"anyhost":"true",
"side":"provider",
"dubbo":"2.0.2",
"methods":"send",
"deprecated":"false",
"service-name-mapping":"true",
"register-mode":"instance",
"qos.enable":"false",
"generic":"false",
"bind.port":"50052",
"ispuserver":"true",
"bind.ip":"192.168.11.119",
"background":"false",
"metadata-service-protocol":"tri",
"dynamic":"true",
"timestamp":"1687230034197"
},
"types":[
{
"enums":[
],
"items":[
],
"properties":{
"tag":"java.lang.String"
},
"type":"org.apache.dubbo.sample.provider.SampleDTO"
},
{
"enums":[
],
"items":[
],
"properties":{
},
"type":"java.lang.String"
}
],
"uniqueId":"org.apache.dubbo.sample.provider.DubboService@file:/Users/amberdata/IdeaProjects/dubbo_sample/target/classes/"
}
Zookeeper扩展类型ZookeeperMetadataReport实现的存储方法如下所示doStoreProviderMetadata:
如果我们自己实现一套元数据就可以重写这个方法来进行元数据的额存储
ZookeeperMetadataReport的doStoreProviderMetadata
@Override
protected void doStoreProviderMetadata(MetadataIdentifier providerMetadataIdentifier, String serviceDefinitions) {
storeMetadata(providerMetadataIdentifier, serviceDefinitions);
}
ZookeeperMetadataReport的storeMetadata
private void storeMetadata(MetadataIdentifier metadataIdentifier, String v) {
//参数false为非临时节点,这个元数据为持久节点,这个细节就暂时不看了就是将刚刚的json元数据存储到对应路径上面:路径为:/dubbo/metadata/link.elastic.dubbo.entity.DemoService/provider/dubbo-demo-api-provider
zkClient.create(getNodePath(metadataIdentifier), v, false);
}
最后
总结下来,双注册就是保留了接口级服务注册于应用级服务注册的双实现,通过不同的服务暴露协议,分别是 registry、service-discovery-registry来暴露不同的服务,由参数dubbo.application.registry-mode来控制
等服务注册完成后,统一进行元数据的注册