Dubbo服务订阅的接口定义在dubbo-registry包中,接口为RegistryService。先来看RegistryService的源码:
public interface RegistryService {
/**
* 1.当URL设置check=false参数时。当注册失败时,不会抛出异常并在后台重试。
* 2. 当URL设置category=routers时,只通知指定的分类数据。多个分类用逗号分隔,允许用星号进行匹配,表示订阅了所有分类数据。
* 3.允许接口、组、版本和分类器作为条件查询,例如:interface=org.apache.dubbo.foo.BarService&version=1.0.0
* 4。并且查询条件允许星号匹配,订阅所有接口的所有包的所有版本,例如:interface=*&group=*&version=*&classifier=*
* 5。当注册表重新启动和网络抖动时,需要自动恢复订阅请求。
* 6. 允许具有相同URL但不同参数的URL共存,它们不能相互覆盖。
* 7. 当第一个通知完成并返回时,必须阻止订阅过程。
* @param url 订阅条件,不能为null, 例如: consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
* @param listener 事件更改的监听器,不允许为空
*/
void subscribe(URL url, NotifyListener listener);
/**
* 取消订阅
* <p>
* 为了支持契约,需要取消订阅
* 1. 如果不订阅,直接忽略它
* 2. 取消订阅完全URL匹配
*
* @param url 订阅条件,不能为null. 例如: consumer://10.20.153.10/org.apache.dubbo.foo.BarService?version=1.0.0&application=kylin
* @param listener 更改事件监听器,不允许为null
*/
void unsubscribe(URL url, NotifyListener listener);
}
订阅
subscribe的入口有两个,一个是在服务导出时,通知订阅的服务进行注册表的更新,一个是在服务引用时,订阅该服务的所有provider。在服务导出中,其入口时RegistryProtocol#export方法,在服务引用时,其入口在RegistryProtocol#doRefer方法中,通过注册目录(RegistryDirectory)的subscribe方法进行订阅。接下下我们先分析下这两个入口
服务导出入口
上面有提过,subscribe在服务导出的入口是RegistryProtocol#export方法,下面我们来分析下
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 获取注册中心 URL,以 zookeeper 注册中心为例,得到的示例 URL 如下:
// zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.2
// &export=dubbo%3A%2F%2F172.17.48.52%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider
URL registryUrl = getRegistryUrl(originInvoker);
// 获取provider的url
// dubbo://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider
// &bind.ip=192.168.1.101&bind.port=20880&default=true&deprecated=false&dubbo=2.0.2&dynamic=true
// &generic=false&interface=org.apache.dubbo.demo.DemoService&metadata-type=remote&methods=sayHello,sayHelloAsync
// &pid=15770&release=&side=provider×tamp=1635326008251
URL providerUrl = getProviderUrl(originInvoker);
// 订阅URL,比如:
// provider://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider
// &bind.ip=192.168.1.101&bind.port=20880&category=configurators&check=false&default=true&deprecated=false
// &dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&metadata-type=remote
// &methods=sayHello,sayHelloAsync&pid=15770&release=&side=provider×tamp=1635326008251
final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);
final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
// TODO
providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);
// 导出Export
final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);
// 根据 URL 加载 Registry 实现类,比如 ZookeeperRegistry
final Registry registry = getRegistry(originInvoker);
final URL registeredProviderUrl = getUrlToRegistry(providerUrl, registryUrl);
// 是否需要延迟加载
boolean register = providerUrl.getParameter(REGISTER_KEY, true);
if (register) {
// 向注册中心注册服务
register(registryUrl, registeredProviderUrl);
}
// 在提供程序模型上注册声明的url
registerStatedUrl(registryUrl, registeredProviderUrl, register);
exporter.setRegisterUrl(registeredProviderUrl);
exporter.setSubscribeUrl(overrideSubscribeUrl);
// 弃用!订阅以覆盖2.6中的规则。x或之前。
registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
notifyExport(exporter);
// 确保每次导出时都返回一个新的导出器实例
return new DestroyableExporter<>(exporter);
}
由上面的方法可以看出,在服务导出的过程中,会将解析获取到的providerUrl转换成overrideSubscribeUrl,在这个过程中将providerUrl的协议如dubbo、redis、http、hassian等转换成provider,并添加key为category,value为configurators、key为check,value为false的参数;此外通过overrideSubscribeUrl和RegistryProtocol#export方法的参数Invoker构建OverrideListener,最后调用registry.subscribe(overrideSubscribeUrl,overrideSubscribeListener)方法进行订阅。这里需要注意overrideSubscribeUrl的几处改变:
- 将protocol设置为provider
- 添加category参数,值为configurators
- 添加check参数,值为false
转换之后的overrideSubscribeUrl的格式为:provider://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?anyhost=true&application=dubbo-demo-api-provider&bind.ip=192.168.1.101&bind.port=20880&category=configurators&check=false&default=true&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=org.apache.dubbo.demo.DemoService&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=15770&release=&side=provider×tamp=1635326008251。在服务服务导出中,最终传递给RegistryService#subscribe方法的URL为上面的url,加粗部分为转换的协议和添加的参数。最后,来分析下RegistryProtocol#export方法中registry是如何获取到的:
private RegistryFactory registryFactory;
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
// 省略无关代码
final Registry registry = getRegistry(originInvoker);
// 省略无关代码
}
protected Registry getRegistry(final Invoker<?> originInvoker) {
URL registryUrl = getRegistryUrl(originInvoker);
return registryFactory.getRegistry(registryUrl);
}
这里的代码很简单,但是中间转换了好几次,但都是围绕Dubbo SPI来做的,下面简单介绍下执行流程,自己可根据需要自行调试:
- 在执行RegistryProtocol#export之前,通过Dubbo SPI获取到了RegistryProtocol,并在注入时将registryFacotry赋值为RegistryFacotry的自适应扩展类RegistryFactory$Adaptive
- 调用RegistryFactory$Adaptive#getRegistry方法,通过解析URL获取到Registry,如DubboRegistry、NacosRegistry,又因在Dubbo SPI配置中配置了ListenerRegistryWrapper包装类,所以,此时获取到的ListenerRegistryWrapper实例。
服务引用入口
前文有提过,subscribe在服务导出的入口是RegistryProtocol#doRefer方法:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
// 设置Registry
directory.setRegistry(registry);
// 设置Protocol
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
// 构建消费者订阅的URL
URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
// 是否需要注册
if (directory.isShouldRegister()) {
// 设置消费者URL
directory.setRegisteredConsumerUrl(subscribeUrl);
// 注册
registry.register(directory.getRegisteredConsumerUrl());
}
// 设置路由
directory.buildRouterChain(subscribeUrl);
// 订阅
directory.subscribe(toSubscribeUrl(subscribeUrl));
// 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个
Invoker<T> invoker = cluster.join(directory);
List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
if (CollectionUtils.isEmpty(listeners)) {
return invoker;
}
RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker);
for (RegistryProtocolListener listener : listeners) {
// 监听
listener.onRefer(this, registryInvokerWrapper);
}
return registryInvokerWrapper;
}
从上面的代码可看出subsribeUrl经过了两次转换
- 通过构造URL构造方法,subscribeUrl的格式为:consumer://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?XXX=xxx
- 通过toSubscribeUrl方法,subscribeUrl变为:consumer://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=providers,configurators,routers&XXX=xxx
接下里继续看RegistryDirectory#subscribe方法:
public void subscribe(URL url) {
// 设置消费者URL
setConsumerUrl(url);
// 添加通知监听器
CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this);
serviceConfigurationListener = new ReferenceConfigurationListener(this, url);
// 订阅
registry.subscribe(url, this);
}
此方法非常简单,在这里发起了具体的Registry#subsribe调用,并在这里指定了服务更改的监听器--RegistryDirectory,这一点很重要,不管用的nacos,zk、eureka或其他注册中心,当服务发生变化时,都会通过此监听器通知消费者。
到这里,我们分析出了在服务导出与服务引用时,传递给RegistryService#subscribe方法的URL格式:
- 服务导出:provider://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=configurators&check=false
- 服务引用:consumer://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=providers,configurators,routers&XXX=xxx
订阅
接下来分析具体订阅的源码,因Dubbo框架中的Dubbo SPI配置文件中已配置了Registry的扩展ListenerRegistryWrapper包装类,so,在不配置其他包装类的情况下,subscribe的入口为ListenerRegistryWrapper#subscribe,接下来我们对该方法进行具体的分析:
public void subscribe(URL url, NotifyListener listener) {
try {
// 订阅 NacosRegistry
registry.subscribe(url, listener);
} finally {
if (CollectionUtils.isNotEmpty(listeners)) {
RuntimeException exception = null;
for (RegistryServiceListener registryListener : listeners) {
if (registryListener != null) {
try {
// 监听订阅
registryListener.onSubscribe(url);
} catch (RuntimeException t) {
logger.error(t.getMessage(), t);
exception = t;
}
}
}
if (exception != null) {
throw exception;
}
}
}
}
这里做了两件事,一是继续深入执行subcribe方法,二是执行subsribe方法的事件监听。我们分两块来讲解,另外,接下来的subscribe方法将以NacosRegistry#subscribe来分析,其他的如DubboRegistry、ZookeeperRegistry自行分析。由上面的代码可知,在ListenerRegistryWraper#subscribe方法中执行执行了NacosRegistry#subscribe方法,此方法在NacosRegistry的父类FailbackRegistry中:
public void subscribe(URL url, NotifyListener listener) {
// 在缓存中添加监听器
super.subscribe(url, listener);
// 删除订阅失败的数据
removeFailedSubscribed(url, listener);
try {
// 向服务器端发送订阅请求
doSubscribe(url, listener);
} catch (Exception e) {
Throwable t = e;
List<URL> urls = getCacheUrls(url);
if (CollectionUtils.isNotEmpty(urls)) {
notify(url, listener, urls);
logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
} else {
// 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);
boolean skipFailback = t instanceof SkipFailbackWrapperException;
if (check || skipFailback) {
if (skipFailback) {
t = t.getCause();
}
throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
} else {
logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
}
// 将失败的注册请求记录到失败列表中,定期重试
addFailedSubscribed(url, listener);
}
}
在FailbackRegistry#subscribe方法中,做了四件事:
- 添加缓存,将providerUrl对应的监听器添加到缓存中
- 删除订阅失败的数据,如失败重试的任务、监听器
- 向服务端发送订阅请求
- 异常处理,将失败的订阅请求记录到失败列表中,定期重试
接下来,深入分析doSubscribe方法,需要下沉到具体的实现类中,继续看NacosRegistry#doSubscribe方法
public void doSubscribe(final URL url, final NotifyListener listener) {
// 获取服务名
Set<String> serviceNames = getServiceNames(url, listener);
// 设置相应的servicename,方便以后查找
if (isServiceNamesWithCompatibleMode(url)) {
for (String serviceName : serviceNames) {
NacosInstanceManageUtil.setCorrespondingServiceNames(serviceName, serviceNames);
}
}
doSubscribe(url, listener, serviceNames);
}
此方法逻辑不多,首先是获取服务名,其次将获取到名称添加到NacosInstanceManageUtil#CORRESPONDING_SERVICE_NAMES_MAP缓存中,最后进一步执行doSubscribe方法
private void doSubscribe(final URL url, final NotifyListener listener, final Set<String> serviceNames) {
execute(namingService -> {
// url的protocol非admin,且是否是具体的名称
if (isServiceNamesWithCompatibleMode(url)) {
List<Instance> allCorrespondingInstanceList = Lists.newArrayList();
for (String serviceName : serviceNames) {
// 从nacos注册中心获取所有实例
List<Instance> instances = namingService.getAllInstances(serviceName,
getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP));
// 添加到缓存
NacosInstanceManageUtil.initOrRefreshServiceInstanceList(serviceName, instances);
allCorrespondingInstanceList.addAll(instances);
}
// 通知
notifySubscriber(url, listener, allCorrespondingInstanceList);
for (String serviceName : serviceNames) {
// 发布订阅事件
subscribeEventListener(serviceName, url, listener);
}
} else {
List<Instance> instances = new LinkedList<>();
for (String serviceName : serviceNames) {
instances.addAll(namingService.getAllInstances(serviceName
, getUrl().getParameter(GROUP_KEY, Constants.DEFAULT_GROUP)));
notifySubscriber(url, listener, instances);
subscribeEventListener(serviceName, url, listener);
}
}
});
}
此方法做了以下几件事:
- 从注册中心获取所有实例
- 将实例添加到缓存
- 订阅通知
- 订阅事件监听
接着往下看订阅通知
private void notifySubscriber(URL url, NotifyListener listener, Collection<Instance> instances) {
List<Instance> healthyInstances = new LinkedList<>(instances);
if (healthyInstances.size() > 0) {
// 过滤出健康的实例
filterHealthyInstances(healthyInstances);
}
List<URL> urls = toUrlWithEmpty(url, healthyInstances);
NacosRegistry.this.notify(url, listener, urls);
}
以上的代码很简单,需要注意的toUrlWithEmpth这个方法,在这个方法中可能会有protocol协议的转换,我们深入其逻辑:
private List<URL> toUrlWithEmpty(URL consumerURL, Collection<Instance> instances) {
List<URL> urls = buildURLs(consumerURL, instances);
// 构建新的URL,protocol设置为empty
if (urls.size() == 0) {
URL empty = URLBuilder.from(consumerURL)
.setProtocol(EMPTY_PROTOCOL)
.addParameter(CATEGORY_KEY, DEFAULT_CATEGORY)
.build();
urls.add(empty);
}
return urls;
}
private List<URL> buildURLs(URL consumerURL, Collection<Instance> instances) {
List<URL> urls = new LinkedList<>();
if (instances != null && !instances.isEmpty()) {
for (Instance instance : instances) {
URL url = buildURL(instance);
if (UrlUtils.isMatch(consumerURL, url)) {
urls.add(url);
}
}
}
return urls;
}
private URL buildURL(Instance instance) {
Map<String, String> metadata = instance.getMetadata();
String protocol = metadata.get(PROTOCOL_KEY);
String path = metadata.get(PATH_KEY);
return new URL(protocol,
instance.getIp(),
instance.getPort(),
path,
instance.getMetadata());
}
根据上述三个方法可看出,遍历Instance列表,获取到providerUrl,其格式为:dubbo://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=providers,然后通过UrlUtils#isMatch方法确定是否添加到需要返回的URL的列表中,如果返回的URL列表的size为0,则构建一个URL并添加到list中返回,其格式为:empty://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=providers,下面我们深入去了解下UrlUtils#isMatch方法:>
public static boolean isMatch(URL consumerUrl, URL providerUrl) {
// 获取consumerInterface
String consumerInterface = consumerUrl.getServiceInterface();
// 获取providerInterface
String providerInterface = providerUrl.getServiceInterface();
//FIXME accept providerUrl with '*' as interface name, after carefully thought about all possible scenarios I think it's ok to add this condition.
// 当consumerInterface不为*、providerInterface不为*、consumerInterface和providerInterface不同(不相等的含义看StringUtils#isEquals方法)时,返回false
if (!(ANY_VALUE.equals(consumerInterface)
|| ANY_VALUE.equals(providerInterface)
|| StringUtils.isEquals(consumerInterface, providerInterface))) {
return false;
}
// 当providerUrl中的category参数的值在consumerUrl的category参数的值中不存在时,返回false
if (!isMatchCategory(providerUrl.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY),
consumerUrl.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY))) {
return false;
}
// 当provider的enabled参数为false且consumerUrl的enabled参数不为*时,返回false
if (!providerUrl.getParameter(ENABLED_KEY, true)
&& !ANY_VALUE.equals(consumerUrl.getParameter(ENABLED_KEY))) {
return false;
}
String consumerGroup = consumerUrl.getParameter(GROUP_KEY);
String consumerVersion = consumerUrl.getParameter(VERSION_KEY);
String consumerClassifier = consumerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);
String providerGroup = providerUrl.getParameter(GROUP_KEY);
String providerVersion = providerUrl.getParameter(VERSION_KEY);
String providerClassifier = providerUrl.getParameter(CLASSIFIER_KEY, ANY_VALUE);
// 满足以下三个条件时返回true,否则返回false
// 1、consumerGroup为*,或consumerGroup等于providerGroup,或consumerGroup包含providerGroup
// 2、consumerVersion为*,或consumerVersion和providerVersion相同
// 3、consumer的classifier为null,或为*,或consumer和provider的classifier相同
return (ANY_VALUE.equals(consumerGroup) || StringUtils.isEquals(consumerGroup, providerGroup) || StringUtils.isContains(consumerGroup, providerGroup))
&& (ANY_VALUE.equals(consumerVersion) || StringUtils.isEquals(consumerVersion, providerVersion))
&& (consumerClassifier == null || ANY_VALUE.equals(consumerClassifier) || StringUtils.isEquals(consumerClassifier, providerClassifier));
}
看上面的代码可知,无非就是比较consumerUrl和providerUrl的interface参数、category参数、enabled参数、group参数、version参数、classifier参数,该方法返回结果总结如下:
-
当consumerInterface不为 、providerInterface不为、consumerInterface和providerInterface不同(不相等的含义看StringUtils#isEquals方法)时,返回false
-
当providerUrl中的category参数的值在consumerUrl的category参数的值中不存在时,返回false
-
当provider的enabled参数为false且consumerUrl的enabled参数不为*时,返回false
-
满足以下三个条件时返回true,否则返回false
- consumerGroup为 ,或consumerGroup等于providerGroup,或consumerGroup包含providerGroup
- consumerVersion为*,或consumerVersion和providerVersion相同
- consumer的classifier为null,或为*,或consumer和provider的classifier相同
前面我们分析出了服务导出、服务引用时的consumerUrl以及获取到的proderUrl的格式,由此可知,当在服务导出时执行subscribe,通过NacosRegistry#toUrlWithEmpty方法获取到的URL格式为empty://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=providers,接下来回到这里继续分析,看NacosRegistry.this.notify(url, listener, urls)这行代码,将执行FailbackRegistry#notify(URL, NotifyListener, List)方法:
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
try {
doNotify(url, listener, urls);
} catch (Exception t) {
// 将失败的订阅请求记录到失败列表中,定期重试
addFailedNotified(url, listener, urls);
logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
}
protected void doNotify(URL url, NotifyListener listener, List<URL> urls) {
super.notify(url, listener, urls);
}
这里除了两个判空校验外,就是执行FailbackRegistry#doNotify方法后调用AbstractRegistry#notify方法,其次就是对FailbackRegistry#doNotify方法执行异常时的错误处理,就是将失败的订阅请求记录大失败列表中,定期重试,这里先不深入分析起重试机制,后面会在Dubbo源码---重试机制中进行详解。接下里继续深入AbstractRegistry#notify方法:
protected void notify(URL url, NotifyListener listener, List<URL> urls) {
if (url == null) {
throw new IllegalArgumentException("notify url == null");
}
if (listener == null) {
throw new IllegalArgumentException("notify listener == null");
}
if ((CollectionUtils.isEmpty(urls))
&& !ANY_VALUE.equals(url.getServiceInterface())) {
logger.warn("Ignore empty notify urls for subscribe url " + url);
return;
}
if (logger.isInfoEnabled()) {
logger.info("Notify urls for subscribe url " + url + ", urls: " + urls);
}
/*-------------------------便签1---------------------------------*/
// 保留每个provider的category
Map<String, List<URL>> result = new HashMap<>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getParameter(CATEGORY_KEY, DEFAULT_CATEGORY);
List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
}
/*-------------------------便签2---------------------------------*/
// 添加缓存
Map<String, List<URL>> categoryNotified = notified.computeIfAbsent(url, u -> new ConcurrentHashMap<>());
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();
List<URL> categoryList = entry.getValue();
categoryNotified.put(category, categoryList);
listener.notify(categoryList);
// 我们将在每次通知后更新缓存文件。
// 当我们的注册表由于网络抖动而订阅失败时,我们至少可以返回现有的缓存URL。
saveProperties(url);
}
}
我们结合前面分析的内容,先对AbstractRegistry#notify方法的三个参数做下简单的分析:
-
URL(消费者URL):
- 服务导出:provider://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=configurators&check=false
- 服务引用:consumer://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=providers,configurators,routers&XXX=xxx
-
NotifyListener(监听器):
- 服务导出:RegistryProtoco.OverrideListener
- 服务引用:RegistryDirectory
-
List(提供者URL):
-
服务导出:empty://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=providers
-
服务引用:dubbo://192.168.1.101:20880/org.apache.dubbo.demo.DemoService?category=providers
-
也就是说,不管subscribe的入口是服务引用还是服务导出,对AbstractRegistry#notify方法的第三个参数影响不大,无非就是改了protocol,最主要的还是第一个参数的改变。当入口为服务导出时,AbstractRegistry#notify方法标签1到标签2之间的逻辑时,subsribe的订阅逻辑在这里就结束了,而后会直接执行ListenerRegistryWraper#subscribe方法的监听逻辑。接下来我们继续标签2下面的逻辑,只不过这之后的逻辑已经跟服务导出时的订阅没关系了,因为下面的逻辑只会发生在服务引用的过程中。 遍历便签1获取的providerUrl,然后发起通知及更新订阅信息到缓存,其中发起通知我们会在Dubbo源码---集群容错之服务目录进行详解。
发起通知
上文有提到过,AbstractRegsitry#notify方法的NotifyListener在服务引用时为RegistryDirectory,接下来看RegistryDirectory#notify方法:
public synchronized void notify(List<URL> urls) {
// 过滤并排序
Map<String, List<URL>> categoryUrls = urls.stream()
.filter(Objects::nonNull)
.filter(this::isValidCategory)
.filter(this::isNotCompatibleFor26x)
.collect(Collectors.groupingBy(this::judgeCategory));
// 获取category为configurators的URL,并赋值到configuration
List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);
// 获取category为routers的URL,并添加到routerChain
List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
// 过滤掉routerURLs中protocol为empty到URL,并做了以下四件事
// 1、过滤掉routerURLs中protocol为empty到URL
// 2、如果URL中包含router参数,且其值不为null或空字符串时,将该URL的protocol设置为该值
// 3、通过URL构建Router
// 4、若返回的Router列表不为空,则将其添加到routerChain中
toRouters(routerURLs).ifPresent(this::addRouters);
// 获取category为providers的URL,
List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
// 3.x 版本添加的逻辑
// 获取AddressListener
ExtensionLoader<AddressListener> addressListenerExtensionLoader = ExtensionLoader.getExtensionLoader(AddressListener.class);
List<AddressListener> supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
if (supportedListeners != null && !supportedListeners.isEmpty()) {
for (AddressListener addressListener : supportedListeners) {
// 处理
providerURLs = addressListener.notify(providerURLs, getConsumerUrl(),this);
}
}
// 刷新
refreshOverrideAndInvoker(providerURLs);
}
此方法的逻辑很清晰,分为了五个步骤来做:
-
过滤掉不符合条件的providerUrl并排序
-
获取category为configurators的URL,并赋值到configuration
-
获取category为routers的URL,并并做了以下操作:
- 过滤掉routerURLs中protocol为empty到URL
- 如果URL中包含router参数,且其值不为null或空字符串时,将该URL的protocol设置为该值
- 通过URL构建Router
- 若返回的Router列表不为空,则将其添加到routerChain中
-
获取category为providers的URL,并做了以下操作
- 获取AddressListener扩展
- 遍历扩展执行AddressListener#notify方法
-
刷新override和Invoker(refreshOverrideAndInvoker)
下面继续深入RegistryDirectory#refreshOverrideAndInvoker方法:
更新缓存
事件监听
回到ListenerRegistryWraper#subscribe方法:
public class ListenerRegistryWrapper implements Registry {
private final Registry registry;
private final List<RegistryServiceListener> listeners;
public ListenerRegistryWrapper(Registry registry, List<RegistryServiceListener> listeners) {
this.registry = registry;
this.listeners = listeners;
}
public void register(URL url) {
try {
// 注册
registry.register(url);
} finally {
if (CollectionUtils.isNotEmpty(listeners)) {
RuntimeException exception = null;
for (RegistryServiceListener listener : listeners) {
if (listener != null) {
try {
// 注册监听
listener.onRegister(url);
} catch (RuntimeException t) {
logger.error(t.getMessage(), t);
exception = t;
}
}
}
if (exception != null) {
throw exception;
}
}
}
}
}
listeners是一个成员变量,是通过构造方法初始化的。而ListenerRegistryWrapper是通过RegistryFactoryWrapper#getRegistry方法创建的:
public Registry getRegistry(URL url) {
return new ListenerRegistryWrapper(registryFactory.getRegistry(url),
Collections.unmodifiableList(ExtensionLoader.getExtensionLoader(RegistryServiceListener.class)
.getActivateExtension(url, "registry.listeners")));
}
由上面的代码可看出,ListenerRegistryWrapper#listeners实际上通过Dubbo SPI实例化的,如果使用可参考Dubbo源码---SPI自适应扩展。