准备工作
在dubbo官网下载源码,3.2.6版本。启动Zookeeper和服务提供者provider。
consumer进行服务发现调试入口:org.apache.dubbo.demo.consumer.Application#runWithBootstrap
java
private static void runWithBootstrap() {
// consumer的引用配置
ReferenceConfig<DemoService> reference = new ReferenceConfig<>();
// 设置要引用的服务接口(当一系列接口被producer打包出口后,会通过网络传输,将信息存在zk中,consumer实际消费时,会将zk中的接口信息取出,并通过代理构造此处的ReferenceConfig)
reference.setInterface(DemoService.class);
reference.setGeneric("true");
// 引导启动组件,3.0.x还没有该组件
DubboBootstrap bootstrap = DubboBootstrap.getInstance();
bootstrap
.application(new ApplicationConfig("dubbo-demo-api-consumer"))
.registry(new RegistryConfig(REGISTRY_URL))
.protocol(new ProtocolConfig(CommonConstants.DUBBO, -1))
.reference(reference)
// 从这里进入调试
.start();
DemoService demoService = bootstrap.getCache().get(reference);
String message = demoService.sayHello("dubbo");
System.out.println(message);
// generic invoke
GenericService genericService = (GenericService) demoService;
Object genericInvokeResult = genericService.$invoke(
"sayHello", new String[] {String.class.getName()}, new Object[] {"dubbo generic invoke"});
System.out.println(genericInvokeResult.toString());
}
从这个方法进来,做了一些准备工作,和服务发布与注册开始时几乎相同,在此不在一一赘述。
开始调试
在startSync()
方法中,走referServices()
,再向下一直到org.apache.dubbo.config.ReferenceConfig#init(boolean)
java
protected synchronized void init(boolean check) {
.
.
.
.
// 准备工作,填充配置
// 核心流程
ref = createProxy(referenceParameters);
serviceMetadata.setTarget(ref);
serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);
consumerModel.setDestroyRunner(getDestroyRunner());
consumerModel.setProxyObject(ref);
consumerModel.initMethodModels();
if (check) {
checkInvokerAvailable(0);
}
} catch (Throwable t) {
logAndCleanup(t);
throw t;
}
initialized = true;
}
可以看到,这里要创建接口实现类的代理,赋值给ref
(接口代理的引用)
在createProxy
方法中,我们直接来看createInvoker()
方法 org.apache.dubbo.config.ReferenceConfig#createInvoker
java
/**
* \create a reference invoker
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private void createInvoker() {
if (urls.size() == 1) {
URL curUrl = urls.get(0);
invoker = protocolSPI.refer(interfaceClass, curUrl);
// registry url, mesh-enable and unloadClusterRelated is true, not need Cluster.
if (!UrlUtils.isRegistry(curUrl) && !curUrl.getParameter(UNLOAD_CLUSTER_RELATED, false)) {
List<Invoker<?>> invokers = new ArrayList<>();
invokers.add(invoker);
invoker = Cluster.getCluster(getScopeModel(), Cluster.DEFAULT)
.join(new StaticDirectory(curUrl, invokers), true);
}
} else {
List<Invoker<?>> invokers = new ArrayList<>();
URL registryUrl = null;
for (URL url : urls) {
// For multi-registry scenarios, it is not checked whether each referInvoker is available.
// Because this invoker may become available later.
invokers.add(protocolSPI.refer(interfaceClass, url));
if (UrlUtils.isRegistry(url)) {
// use last registry url
registryUrl = url;
}
}
if (registryUrl != null) {
// registry url is available
// for multi-subscription scenario, use 'zone-aware' policy by default
String cluster = registryUrl.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME);
// The invoker wrap sequence would be: ZoneAwareClusterInvoker(StaticDirectory) ->
// FailoverClusterInvoker
// (RegistryDirectory, routing happens here) -> Invoker
invoker = Cluster.getCluster(registryUrl.getScopeModel(), cluster, false)
.join(new StaticDirectory(registryUrl, invokers), false);
} else {
// not a registry url, must be direct invoke.
if (CollectionUtils.isEmpty(invokers)) {
throw new IllegalArgumentException("invokers == null");
}
URL curUrl = invokers.get(0).getUrl();
String cluster = curUrl.getParameter(CLUSTER_KEY, Cluster.DEFAULT);
invoker =
Cluster.getCluster(getScopeModel(), cluster).join(new StaticDirectory(curUrl, invokers), true);
}
}
}
这里invoker = protocolSPI.refer(interfaceClass, curUrl)
有一个SPI机制,调试不好直接跳到对应实现,直接说明此处跳到RegistryProtocol
中,可以在RegistryProtocol
的refer
方法打断点。
java
@Override
@SuppressWarnings("unchecked")
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
url = getRegistryUrl(url);
Registry registry = getRegistry(url);
if (RegistryService.class.equals(type)) {
return proxyFactory.getInvoker((T) registry, type, url);
}
// group="a,b" or group="*"
Map<String, String> qs = (Map<String, String>) url.getAttribute(REFER_KEY);
String group = qs.get(GROUP_KEY);
if (StringUtils.isNotEmpty(group)) {
if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
return doRefer(Cluster.getCluster(url.getScopeModel(), MergeableCluster.NAME), registry, type, url, qs);
}
}
Cluster cluster = Cluster.getCluster(url.getScopeModel(), qs.get(CLUSTER_KEY));
return doRefer(cluster, registry, type, url, qs);
}
此方法中,Registry registry = getRegistry(url);
获取到ZookeeperRegistry
,向下都到doRefer
java
protected <T> Invoker<T> doRefer(
Cluster cluster, Registry registry, Class<T> type, URL url, Map<String, String> parameters) {
Map<String, Object> consumerAttribute = new HashMap<>(url.getAttributes());
consumerAttribute.remove(REFER_KEY);
String p = isEmpty(parameters.get(PROTOCOL_KEY)) ? CONSUMER : parameters.get(PROTOCOL_KEY);
URL consumerUrl = new ServiceConfigURL(
p,
null,
null,
parameters.get(REGISTER_IP_KEY),
0,
getPath(parameters, type),
parameters,
consumerAttribute);
url = url.putAttribute(CONSUMER_URL_KEY, consumerUrl);
ClusterInvoker<T> migrationInvoker = getMigrationInvoker(this, cluster, registry, type, url, consumerUrl);
return interceptInvoker(migrationInvoker, url, consumerUrl);
}
创建consumerUrl
consumer://10.1.xx.219/org.apache.dubbo.demo.DemoService?application=dubbo-demo-api-consumer&background=false&dubbo=2.0.2&executor-management-mode=isolation&file-cache=true&generic=true&interface=org.apache.dubbo.demo.DemoService&pid=3700®ister.ip=10.1.68.219&release=&side=consumer&sticky=false×tamp=1742004447403&unloadClusterRelated=false
创建MigrationInvoker用于负责处理服务迁移(负责处理服务级Invoker和接口级Invoker的迁移),其中包含了以下三个参数。
private volatile ClusterInvoker invoker; // 用来记录接口级ClusterInvoker private volatile ClusterInvoker serviceDiscoveryInvoker; // 用来记录应用级的ClusterInvoker private volatile ClusterInvoker currentAvailableInvoker; // 用来记录当前使用的ClusterInvoker,要么是接口级,要么应用级
之后,执行interceptInvoker()
来利用监听器,更改Invoker的行为
java
protected <T> Invoker<T> interceptInvoker(ClusterInvoker<T> invoker, URL url, URL consumerUrl) {
List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
if (CollectionUtils.isEmpty(listeners)) {
return invoker;
}
for (RegistryProtocolListener listener : listeners) {
listener.onRefer(this, invoker, consumerUrl, url);
}
return invoker;
}
通过url获取监听器RegistryProtocolListener
,此处的listener是MigrationRuleListener
MigrationRuleListener :Listens to {@MigrationRule} from Config Center
之后,执行onRefer()
,相当于一个回调,在订阅服务时,去通知Listener
java
@Override
public void onRefer(
RegistryProtocol registryProtocol, ClusterInvoker<?> invoker, URL consumerUrl, URL registryURL) {
MigrationRuleHandler<?> migrationRuleHandler =
ConcurrentHashMapUtils.computeIfAbsent(handlers, (MigrationInvoker<?>) invoker, _key -> {
((MigrationInvoker<?>) invoker).setMigrationRuleListener(this);
return new MigrationRuleHandler<>((MigrationInvoker<?>) invoker, consumerUrl);
});
migrationRuleHandler.doMigrate(rule);
}
Invoker迁移机制
在Dubbo 3的迁移机制中,为了支持从接口级平滑过渡到应用级服务发现,需要同时维护两种服务发现的Invoker。refreshInterfaceInvoker处理旧模式,refreshServiceDiscoveryInvoker处理新模式,然后根据迁移规则动态选择优先使用的Invoker,确保兼容性和平滑迁移
此方法构建一个migrationRuleHandler
去依据迁移rule
处理迁移,此时的rule
内部还都是null,step是应用级优先(APPLICATION_FIRST)
java
public synchronized void doMigrate(MigrationRule rule) {
if (migrationInvoker instanceof ServiceDiscoveryMigrationInvoker) {
refreshInvoker(MigrationStep.FORCE_APPLICATION, 1.0f, rule);
return;
}
// initial step : APPLICATION_FIRST
MigrationStep step = MigrationStep.APPLICATION_FIRST;
float threshold = -1f;
try {
step = rule.getStep(consumerURL);
threshold = rule.getThreshold(consumerURL);
} catch (Exception e) {
logger.error(
REGISTRY_NO_PARAMETERS_URL, "", "", "Failed to get step and threshold info from rule: " + rule, e);
}
// 去根据rule刷新Invoker
if (refreshInvoker(step, threshold, rule)) {
// refresh success, update rule
setMigrationRule(rule);
}
}
在这里回去刷新Invoker,执行refreshInvoker()
在refreshInvoker()中,按照step,去执行下面这个方法
java
@Override
public void migrateToApplicationFirstInvoker(MigrationRule newRule) {
CountDownLatch latch = new CountDownLatch(0);
// 刷新接口级Invoker
refreshInterfaceInvoker(latch);
// 刷新应用级Invoker
refreshServiceDiscoveryInvoker(latch);
// directly calculate preferred invoker, will not wait until address notify
// calculation will re-occurred when address notify later
calcPreferredInvoker(newRule);
}
处理接口级Invoker
refreshInterfaceInvoker()
java
protected void refreshInterfaceInvoker(CountDownLatch latch) {
clearListener(invoker);
if (needRefresh(invoker)) {
if (logger.isDebugEnabled()) {
logger.debug("Re-subscribing interface addresses for interface " + type.getName());
}
if (invoker != null) {
invoker.destroy();
}
// 从这里进去
invoker = registryProtocol.getInvoker(cluster, registry, type, url);
}
setListener(invoker, () -> {
latch.countDown();
if (reportService.hasReporter()) {
reportService.reportConsumptionStatus(reportService.createConsumptionReport(
consumerUrl.getServiceInterface(),
consumerUrl.getVersion(),
consumerUrl.getGroup(),
"interface"));
}
if (step == APPLICATION_FIRST) {
calcPreferredInvoker(rule);
}
});
}
直接看invoker = registryProtocol.getInvoker(cluster, registry, type, url)
java
public <T> ClusterInvoker<T> getInvoker(Cluster cluster, Registry registry, Class<T> type, URL url) {
DynamicDirectory<T> directory = new RegistryDirectory<>(type, url);
return doCreateInvoker(directory, cluster, registry, type);
}
创建了一个DynamicDirectory动态注册表,之后执行doCreateInvoker
java
protected <T> ClusterInvoker<T> doCreateInvoker(
DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters =
new HashMap<>(directory.getConsumerUrl().getParameters());
URL urlToRegistry = new ServiceConfigURL(
parameters.get(PROTOCOL_KEY) == null ? CONSUMER : parameters.get(PROTOCOL_KEY),
parameters.remove(REGISTER_IP_KEY),
0,
getPath(parameters, type),
parameters);
urlToRegistry = urlToRegistry.setScopeModel(directory.getConsumerUrl().getScopeModel());
urlToRegistry = urlToRegistry.setServiceModel(directory.getConsumerUrl().getServiceModel());
if (directory.isShouldRegister()) {
directory.setRegisteredConsumerUrl(urlToRegistry);
// 注册consumer节点
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(urlToRegistry);
// 订阅zk节点
directory.subscribe(toSubscribeUrl(urlToRegistry));
return (ClusterInvoker<T>) cluster.join(directory, true);
}
向zk注册consumer节点
此时,会先去注册consumer节点,执行registry.register(directory.getRegisteredConsumerUrl())
java
@Override
public void register(URL url) {
try {
if (registry != null) {
registry.register(url);
}
} finally {
if (!UrlUtils.isConsumer(url)) {
listenerEvent(serviceListener -> serviceListener.onRegister(url, registry));
}
}
}
向下调用,跳转到org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister
java
@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);
}
}
根据url去创建zk节点, path如下
/dubbo/org.apache.dubbo.demo.DemoService/consumers/consumer%3A%2F%2F10.1.68.219%2Forg.apache.dubbo.demo.DemoService%3Fapplication%3Ddubbo-demo-api-consumer%26background%3Dfalse%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.0.2%26executor-management-mode%3Disolation%26file-cache%3Dtrue%26generic%3Dtrue%26interface%3Dorg.apache.dubbo.demo.DemoService%26pid%3D3700%26release%3D%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1742004447403%26unloadClusterRelated%3Dfalse
具体创建节点的流程可以看org.apache.dubbo.remoting.zookeeper.AbstractZookeeperClient#create
,此流程在服务发布与注册中提到过,在此不做赘述。
之后,返回去看directory.subscribe(toSubscribeUrl(urlToRegistry));
provider服务订阅与发现(接口级服务发现)
subscribe()方法中,有下面一段代码
java
MetricsEventBus.post(RegistryEvent.toSubscribeEvent(applicationModel, registryClusterName), () -> {
super.subscribe(url);
return null;
});
这部分利用MetricsEventBus去向所有注册订阅者发布RegistryEvent
事件,并返回处理结果,也就是super.subscribe(url)
的结果
继续向下看subscribe
流程到org.apache.dubbo.registry.zookeeper.ZookeeperRegistry#doSubscribe
java
@Override
public void doSubscribe(final URL url, final NotifyListener listener) {
try {
// 此处省略
} else {
CountDownLatch latch = new CountDownLatch(1);
try {
List<URL> urls = new ArrayList<>();
/*
在默认设置中,url是一个consumer时,category可以是以下三个
/dubbo/[service name]/providers,
/dubbo/[service name]/configurators
/dubbo/[service name]/routers
*/
for (String path : toCategoriesPath(url)) {
ConcurrentMap<NotifyListener, ChildListener> listeners = ConcurrentHashMapUtils.computeIfAbsent(
zkListeners, url, k -> new ConcurrentHashMap<>());
ChildListener zkListener = ConcurrentHashMapUtils.computeIfAbsent(
listeners, listener, k -> new RegistryChildListenerImpl(url, k, latch));
if (zkListener instanceof RegistryChildListenerImpl) {
((RegistryChildListenerImpl) zkListener).setLatch(latch);
}
// create "directories".
zkClient.create(path, false, true);
// Add children (i.e. service items).
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
// The invocation point that may cause 1-1.
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
notify(url, listener, urls);
} finally {
// tells the listener to run only after the sync notification of main thread finishes.
latch.countDown();
}
}
} catch (Throwable e) {
throw new RpcException(
"Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
上述过程,对不同category的path进行循环
第一个category:providers,走到create时,因zk已存在该节点,所以不会再去zk创建该节点,继续向下,addChildListener,向该节点下添加监听器,在zk是设置Watcher,可参考client.getChildren().usingWatcher(listener).forPath(path)
代码,完成向zk节点的订阅。订阅后将 toUrlsWithEmpty(url, path, children)
处理后的url(category是providers的url)放入urls
第二个category是configurators
第三个是routers,过程和上述相同,最后都放入到urls。
拿到订阅后的urls,去执行notify()
一路跳转到org.apache.dubbo.registry.support.AbstractRegistry#notify(org.apache.dubbo.common.URL, org.apache.dubbo.registry.NotifyListener, java.util.List<org.apache.dubbo.common.URL>)
java
/**
* Notify changes from the provider side.
*
* @param url consumer side url
* @param listener listener
* @param urls provider latest urls
*/
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())) {
// 1-4 Empty address.
logger.warn(REGISTRY_EMPTY_ADDRESS, "", "", "Ignore empty notify urls for subscribe url " + url);
return;
}
if (logger.isInfoEnabled()) {
logger.info("Notify urls for subscribe url " + url + ", url size: " + urls.size());
}
// keep every provider's category.
Map<String, List<URL>> result = new HashMap<>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getCategory(DEFAULT_CATEGORY);
List<URL> categoryList = result.computeIfAbsent(category, k -> new ArrayList<>());
categoryList.add(u);
}
}
if (result.size() == 0) {
return;
}
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);
// We will update our cache file after each notification.
// When our Registry has a subscribed failure due to network jitter, we can return at least the existing
// cache URL.
if (localCacheEnabled) {
saveProperties(url);
}
}
}
本方法会对三种category对应的url进行循环notify,最后再对cache file(服务器本地存文件)进行更新。
向下继续调用org.apache.dubbo.registry.integration.RegistryDirectory#notify
此方法中会处理url,加载扩展机制,但核心的还是调用refreshOverrideAndInvoker(providerURLs)
方法
java
// RefreshOverrideAndInvoker will be executed by registryCenter and configCenter, so it should be synchronized.
@Override
protected synchronized void refreshOverrideAndInvoker(List<URL> urls) {
// mock zookeeper://xxx?mock=return null
this.directoryUrl = overrideWithConfigurator(getOriginalConsumerUrl());
refreshInvoker(urls);
}
当category为configurators和routers时,入参urls都是empty的,到category为providers时,urls有具体数据,此时urls数据示例如下
dubbo://10.1.xx.219:20880/org.apache.dubbo.demo.DemoService?application=dubbo-demo-api-provider&background=false&category=providers,configurators,routers&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&executor-management-mode=isolation&file-cache=true&generic=false&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=7900&prefer.serialization=fastjson2,hessian2&service-name-mapping=true&side=provider&sticky=false&unloadClusterRelated=false
继续向下看下refreshInvoker(urls)
,此方法会将invoker的url转化成Invoker对象
java
/**
* Convert the invokerURL list to the Invoker Map. The rules of the conversion are as follows:
* <ol>
* <li> If URL has been converted to invoker, it is no longer re-referenced and obtained directly from the cache,
* and notice that any parameter changes in the URL will be re-referenced.</li>
* <li>If the incoming invoker list is not empty, it means that it is the latest invoker list.</li>
* <li>If the list of incoming invokerUrl is empty, It means that the rule is only a override rule or a route
* rule, which needs to be re-contrasted to decide whether to re-reference.</li>
* </ol>
*
* @param invokerUrls this parameter can't be null
*/
private void refreshInvoker(List<URL> invokerUrls) {
Assert.notNull(invokerUrls, "invokerUrls should not be null");
if (invokerUrls.size() == 1
&& invokerUrls.get(0) != null
&& EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
refreshRouter(
BitList.emptyList(), () -> this.forbidden = true // Forbid to access
);
destroyAllInvokers(); // Close all invokers
} else {
this.forbidden = false; // Allow to access
if (invokerUrls == Collections.<URL>emptyList()) {
invokerUrls = new ArrayList<>();
}
// use local reference to avoid NPE as this.cachedInvokerUrls will be set null by destroyAllInvokers().
Set<URL> localCachedInvokerUrls = this.cachedInvokerUrls;
if (invokerUrls.isEmpty()) {
if (CollectionUtils.isNotEmpty(localCachedInvokerUrls)) {
// 1-4 Empty address.
logger.warn(
REGISTRY_EMPTY_ADDRESS,
"configuration ",
"",
"Service" + serviceKey
+ " received empty address list with no EMPTY protocol set, trigger empty protection.");
invokerUrls.addAll(localCachedInvokerUrls);
}
} else {
// 走这里
localCachedInvokerUrls = new HashSet<>();
localCachedInvokerUrls.addAll(invokerUrls); // Cached invoker urls, convenient for comparison
// 本地缓存invokerUrls赋值
this.cachedInvokerUrls = localCachedInvokerUrls;
}
if (invokerUrls.isEmpty()) {
return;
}
// use local reference to avoid NPE as this.urlInvokerMap will be set null concurrently at
// destroyAllInvokers().
Map<URL, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
// can't use local reference as oldUrlInvokerMap's mappings might be removed directly at toInvokers().
Map<URL, Invoker<T>> oldUrlInvokerMap = null;
if (localUrlInvokerMap != null) {
// the initial capacity should be set greater than the maximum number of entries divided by the load
// factor to avoid resizing.
oldUrlInvokerMap =
new LinkedHashMap<>(Math.round(1 + localUrlInvokerMap.size() / DEFAULT_HASHMAP_LOAD_FACTOR));
localUrlInvokerMap.forEach(oldUrlInvokerMap::put);
}
// 转化逻辑入口
Map<URL, Invoker<T>> newUrlInvokerMap =
toInvokers(oldUrlInvokerMap, invokerUrls); // Translate url list to Invoker map
/*
* If the calculation is wrong, it is not processed.
*
* 1. The protocol configured by the client is inconsistent with the protocol of the server.
* eg: consumer protocol = dubbo, provider only has other protocol services(rest).
* 2. The registration center is not robust and pushes illegal specification data.
*
*/
if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
// 3-1 - Failed to convert the URL address into Invokers.
logger.error(
PROXY_FAILED_CONVERT_URL,
"inconsistency between the client protocol and the protocol of the server",
"",
"urls to invokers error",
new IllegalStateException("urls to invokers error. invokerUrls.size :" + invokerUrls.size()
+ ", invoker.size :0. urls :" + invokerUrls.toString()));
return;
}
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();
}
}
调试可以发现,只有category为providers时,才会将invokerUrl进行转化
首先,会对cachedInvokerUrls
进行赋值,此时的Map<URL, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap
还是null,也就是说,此时本地对invokerMap的缓存还是空,接下来执行关键方法toInvokers
,此方法会将invokerUrl list转化为Invoker Map。
java
/**
* Turn urls into invokers, and if url has been referred, will not re-reference.
* the items that will be put into newUrlInvokeMap will be removed from oldUrlInvokerMap.
* @param oldUrlInvokerMap it might be modified during the process.
* @param urls
* @return invokers
*/
private Map<URL, Invoker<T>> toInvokers(Map<URL, Invoker<T>> oldUrlInvokerMap, List<URL> urls) {
Map<URL, Invoker<T>> newUrlInvokerMap =
new ConcurrentHashMap<>(urls == null ? 1 : (int) (urls.size() / 0.75f + 1));
if (urls == null || urls.isEmpty()) {
return newUrlInvokerMap;
}
String queryProtocols = this.queryMap.get(PROTOCOL_KEY);
for (URL providerUrl : urls) {
if (!checkProtocolValid(queryProtocols, providerUrl)) {
continue;
}
URL url = mergeUrl(providerUrl);
// Cache key is url that does not merge with consumer side parameters,
// regardless of how the consumer combines parameters,
// if the server url changes, then refer again
Invoker<T> invoker = oldUrlInvokerMap == null ? null : oldUrlInvokerMap.remove(url);
if (invoker == null) { // Not in the cache, refer again
try {
boolean enabled = true;
if (url.hasParameter(DISABLED_KEY)) {
enabled = !url.getParameter(DISABLED_KEY, false);
} else {
enabled = url.getParameter(ENABLED_KEY, true);
}
if (enabled) {
// 这里是核心,走到DubboProtocol中的refer方法
invoker = protocol.refer(serviceType, url);
}
} catch (Throwable t) {
// Thrown by AbstractProtocol.optimizeSerialization()
if (t instanceof RpcException && t.getMessage().contains("serialization optimizer")) {
// 4-2 - serialization optimizer class initialization failed.
logger.error(
PROTOCOL_FAILED_INIT_SERIALIZATION_OPTIMIZER,
"typo in optimizer class",
"",
"Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")"
+ t.getMessage(),
t);
} else {
// 4-3 - Failed to refer invoker by other reason.
logger.error(
PROTOCOL_FAILED_REFER_INVOKER,
"",
"",
"Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")"
+ t.getMessage(),
t);
}
}
if (invoker != null) { // Put new invoker in cache
newUrlInvokerMap.put(url, invoker);
}
} else {
newUrlInvokerMap.put(url, invoker);
}
}
return newUrlInvokerMap;
}
在此方法中,核心看一下invoker = protocol.refer(serviceType, url);
调用DubboProtocol下的refer方法去获取Invoker。
java
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
checkDestroyed();
return protocolBindingRefer(type, url);
}
@Override
public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
checkDestroyed();
optimizeSerialization(url);
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}
如上,创建DubboInvoker时,会getClient(url),继续向下看
java
private ClientsProvider getClients(URL url) {
int connections = url.getParameter(CONNECTIONS_KEY, 0);
// whether to share connection
// if not configured, connection is shared, otherwise, one connection for one service
if (connections == 0) {
/*
* The xml configuration should have a higher priority than properties.
*/
String shareConnectionsStr = StringUtils.isBlank(url.getParameter(SHARE_CONNECTIONS_KEY, (String) null))
? ConfigurationUtils.getProperty(
url.getOrDefaultApplicationModel(), SHARE_CONNECTIONS_KEY, DEFAULT_SHARE_CONNECTIONS)
: url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
connections = Integer.parseInt(shareConnectionsStr);
// 走到这里
return getSharedClient(url, connections);
}
List<ExchangeClient> clients =
IntStream.range(0, connections).mapToObj((i) -> initClient(url)).collect(Collectors.toList());
return new ExclusiveClientsProvider(clients);
}
再向下走getSharedClient
java
/**
* Get shared connection
* @param url
* @param connectNum connectNum must be greater than or equal to 1
*/
@SuppressWarnings("unchecked")
private SharedClientsProvider getSharedClient(URL url, int connectNum) {
String key = url.getAddress();
// connectNum must be greater than or equal to 1
int expectedConnectNum = Math.max(connectNum, 1);
return referenceClientMap.compute(key, (originKey, originValue) -> {
if (originValue != null && originValue.increaseCount()) {
return originValue;
} else {
return new SharedClientsProvider(
this, originKey, buildReferenceCountExchangeClientList(url, expectedConnectNum));
}
});
}
这里会获取dubbo接口provider的address,并且获取连接数,默认是1
之后,执行buildReferenceCountExchangeClientList
批量构建客户端
java
// 批量构建客户端
private List<ReferenceCountExchangeClient> buildReferenceCountExchangeClientList(URL url, int connectNum) {
List<ReferenceCountExchangeClient> clients = new ArrayList<>();
for (int i = 0; i < connectNum; i++) {
clients.add(buildReferenceCountExchangeClient(url));
}
return clients;
}
/**
* Build a single client 构建一个客户端
* @param url
* @return
*/
private ReferenceCountExchangeClient buildReferenceCountExchangeClient(URL url) {
ExchangeClient exchangeClient = initClient(url);
ReferenceCountExchangeClient client = new ReferenceCountExchangeClient(exchangeClient, DubboCodec.NAME);
// read configs
int shutdownTimeout = ConfigurationUtils.getServerShutdownTimeout(url.getScopeModel());
client.setShutdownWaitTime(shutdownTimeout);
return client;
}
构建客户端之前会初始化一个客户端,执行initClient(url)
java
/**
* 创建一个新的网络连接
*/
private ExchangeClient initClient(URL url) {
/*
* Instance of url is InstanceAddressURL, so addParameter actually adds parameters into ServiceInstance,
* which means params are shared among different services. Since client is shared among services this is currently not a problem.
*/
String str = url.getParameter(CLIENT_KEY, url.getParameter(SERVER_KEY, DEFAULT_REMOTING_CLIENT));
// 检测通信模型是否是BIO,若是,则直接异常
if (StringUtils.isNotEmpty(str)
&& !url.getOrDefaultFrameworkModel()
.getExtensionLoader(Transporter.class)
.hasExtension(str)) {
throw new RpcException("Unsupported client type: " + str + "," + " supported client type is "
+ StringUtils.join(
url.getOrDefaultFrameworkModel()
.getExtensionLoader(Transporter.class)
.getSupportedExtensions(),
" "));
}
try {
ScopeModel scopeModel = url.getScopeModel();
int heartbeat = UrlUtils.getHeartbeat(url);
// Replace InstanceAddressURL with ServiceConfigURL.
url = new ServiceConfigURL(
DubboCodec.NAME,
url.getUsername(),
url.getPassword(),
url.getHost(),
url.getPort(),
url.getPath(),
url.getAllParameters());
url = url.addParameter(CODEC_KEY, DubboCodec.NAME);
// enable heartbeat by default
url = url.addParameterIfAbsent(HEARTBEAT_KEY, Integer.toString(heartbeat));
url = url.setScopeModel(scopeModel);
// connection should be lazy
return url.getParameter(LAZY_CONNECT_KEY, false)
? new LazyConnectExchangeClient(url, requestHandler)
: Exchangers.connect(url, requestHandler);
} catch (RemotingException e) {
throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
}
}
init过程,会先得到CLIENT_KEY,此处是netty,再去校验,若是BIO(阻塞式IO,有严重性能问题),则抛异常。 然后对url进行处理,得到最后要建立网络连接的url,再去判断是否以lazy模式去连接。此处并不以lazy进行加载,直接走Exchangers.connect
连接的url示例如下
dubbo://10.1.xx.219:20880/org.apache.dubbo.demo.DemoService?application=dubbo-demo-api-consumer&background=false&category=providers,configurators,routers&check=false&codec=dubbo&deprecated=false&dubbo=2.0.2&dynamic=true&executor-management-mode=isolation&file-cache=true&generic=true&heartbeat=60000&interface=org.apache.dubbo.demo.DemoService&methods=sayHello,sayHelloAsync&pid=25608&prefer.serialization=fastjson2,hessian2&service-name-mapping=true&side=consumer&sticky=false&unloadClusterRelated=false
Exchangers.connect
的具体流程上一篇文章,服务发布与注册时讲到了,就是使用Transporters去进行netty网络连接,依靠Exchanger进行数据交换。此处不再展开。
到目前,已经与服务的provider建立了网络通信,回到前面的创建DubboInvoker
DubboInvoker<T> invoker = new DubboInvoker<>(serviceType, url, getClients(url), invokers)
创建好的DubboInvoker示例如下图
回到前面的toInvokers()
,会将invoker塞进newUrlInvokerMap
接着跳回org.apache.dubbo.registry.integration.RegistryDirectory#refreshInvoker
java
// newInvokers就是toInvoker转化得到的invoker列表
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 这里会填充本地缓存invokers
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();
这里将toInvoker得到的newInvokers和newUrlInvokermap放到本地缓存this.invokers
和this.urlInvokerMap
中(后面进行服务调用时,用到的Invoker就是从缓存中取的)。最后,将invokers改变的消息进行通知,按照规则刷新invokers并发布事件,这个notify过程不再展开。
一路return,最后,将包含着得到invoker相关缓存的RegistryDirectory
封装成ClusterInvoker
进行返回得到ScopeClusterInvoker
,如下图所示
至此,接口级Invoker刷新就完成了,主要是订阅了provider节点,拿到服务url list,并根据url list构建服务的Invoker,填充本地Invoker相关缓存。
回到org.apache.dubbo.registry.client.migration.MigrationInvoker#migrateToApplicationFirstInvoker
java
@Override
public void migrateToApplicationFirstInvoker(MigrationRule newRule) {
CountDownLatch latch = new CountDownLatch(0);
refreshInterfaceInvoker(latch);
refreshServiceDiscoveryInvoker(latch);
// directly calculate preferred invoker, will not wait until address notify
// calculation will re-occurred when address notify later
calcPreferredInvoker(newRule);
}
处理应用级Invoker
下一步是执行应用级Invoker的刷新 从refreshServiceDiscoveryInvoker
走到doCreateInvoker
java
protected <T> ClusterInvoker<T> doCreateInvoker(
DynamicDirectory<T> directory, Cluster cluster, Registry registry, Class<T> type) {
directory.setRegistry(registry);
directory.setProtocol(protocol);
// all attributes of REFER_KEY
Map<String, String> parameters =
new HashMap<>(directory.getConsumerUrl().getParameters());
URL urlToRegistry = new ServiceConfigURL(
parameters.get(PROTOCOL_KEY) == null ? CONSUMER : parameters.get(PROTOCOL_KEY),
parameters.remove(REGISTER_IP_KEY),
0,
getPath(parameters, type),
parameters);
urlToRegistry = urlToRegistry.setScopeModel(directory.getConsumerUrl().getScopeModel());
urlToRegistry = urlToRegistry.setServiceModel(directory.getConsumerUrl().getServiceModel());
if (directory.isShouldRegister()) {
directory.setRegisteredConsumerUrl(urlToRegistry);
registry.register(directory.getRegisteredConsumerUrl());
}
directory.buildRouterChain(urlToRegistry);
directory.subscribe(toSubscribeUrl(urlToRegistry));
return (ClusterInvoker<T>) cluster.join(directory, true);
}
directory和registry类型如图所示
此时,ServiceDiscoveryRegistry是不去向zk注册节点的,因此,我们可以跳过register
直接看订阅的过程。 我们直接来到ServiceDiscoveryRegistry
的doSubscribe
方法,看看它在订阅时都干了什么。
java
@Override
public void doSubscribe(URL url, NotifyListener listener) {
url = addRegistryClusterKey(url);
serviceDiscovery.subscribe(url, listener);
Set<String> mappingByUrl = ServiceNameMapping.getMappingByUrl(url);
String key = ServiceNameMapping.buildMappingKey(url);
if (mappingByUrl == null) {
Lock mappingLock = serviceNameMapping.getMappingLock(key);
try {
mappingLock.lock();
mappingByUrl = serviceNameMapping.getMapping(url);
try {
MappingListener mappingListener = new DefaultMappingListener(url, mappingByUrl, listener);
mappingByUrl = serviceNameMapping.getAndListen(this.getUrl(), url, mappingListener);
synchronized (mappingListeners) {
mappingListeners
.computeIfAbsent(url.getProtocolServiceKey(), (k) -> new ConcurrentHashSet<>())
.add(mappingListener);
}
} catch (Exception e) {
logger.warn(
INTERNAL_ERROR,
"",
"",
"Cannot find app mapping for service " + url.getServiceInterface() + ", will not migrate.",
e);
}
if (CollectionUtils.isEmpty(mappingByUrl)) {
logger.info(
"No interface-apps mapping found in local cache, stop subscribing, will automatically wait for mapping listener callback: "
+ url);
return;
}
} finally {
mappingLock.unlock();
}
}
subscribeURLs(url, listener, mappingByUrl);
}
首先进入到serviceDiscovery.subscribe(url, listener);
java
@Override
public void subscribe(URL url, NotifyListener listener) {
metadataInfo.addSubscribedURL(url);
}
向元数据添加了已订阅的url,填充subscribedServiceURLs
缓存 具体操作如下图
接着,中间处理Listener
和mappingByUrl
最后,执行subscribeURLs
(url, listener, mappingByUrl)`
java
protected void subscribeURLs(URL url, NotifyListener listener, Set<String> serviceNames) {
serviceNames = toTreeSet(serviceNames);
String serviceNamesKey = toStringKeys(serviceNames);
String serviceKey = url.getServiceKey();
logger.info(
String.format("Trying to subscribe from apps %s for service key %s, ", serviceNamesKey, serviceKey));
// register ServiceInstancesChangedListener
Lock appSubscriptionLock = getAppSubscription(serviceNamesKey);
try {
appSubscriptionLock.lock();
ServiceInstancesChangedListener serviceInstancesChangedListener = serviceListeners.get(serviceNamesKey);
if (serviceInstancesChangedListener == null) {
serviceInstancesChangedListener = serviceDiscovery.createListener(serviceNames);
// 循环检查每个应用下是否有服务实例
for (String serviceName : serviceNames) {
List<ServiceInstance> serviceInstances = serviceDiscovery.getInstances(serviceName);
if (CollectionUtils.isNotEmpty(serviceInstances)) {
serviceInstancesChangedListener.onEvent(
new ServiceInstancesChangedEvent(serviceName, serviceInstances));
}
}
serviceListeners.put(serviceNamesKey, serviceInstancesChangedListener);
}
if (!serviceInstancesChangedListener.isDestroyed()) {
listener.addServiceListener(serviceInstancesChangedListener);
serviceInstancesChangedListener.addListenerAndNotify(url, listener);
ServiceInstancesChangedListener finalServiceInstancesChangedListener = serviceInstancesChangedListener;
String serviceDiscoveryName =
url.getParameter(RegistryConstants.REGISTRY_CLUSTER_KEY, url.getProtocol());
MetricsEventBus.post(
RegistryEvent.toSsEvent(
url.getApplicationModel(), serviceKey, Collections.singletonList(serviceDiscoveryName)),
() -> {
serviceDiscovery.addServiceInstancesChangedListener(finalServiceInstancesChangedListener);
return null;
});
} else {
logger.info(String.format("Listener of %s has been destroyed by another thread.", serviceNamesKey));
serviceListeners.remove(serviceNamesKey);
}
} finally {
appSubscriptionLock.unlock();
}
}
这里会根据serviceNames(应用名称列表),检查每个应用下是否存在接口对应的服务实例
然后走到serviceInstancesChangedListener.addListenerAndNotify(url, listener);
java
public synchronized void addListenerAndNotify(URL url, NotifyListener listener) {
if (destroyed.get()) {
return;
}
Set<NotifyListenerWithKey> notifyListeners =
this.listeners.computeIfAbsent(url.getServiceKey(), _k -> new ConcurrentHashSet<>());
String protocol = listener.getConsumerUrl().getParameter(PROTOCOL_KEY, url.getProtocol());
ProtocolServiceKey protocolServiceKey = new ProtocolServiceKey(
url.getServiceInterface(),
url.getVersion(),
url.getGroup(),
!CommonConstants.CONSUMER.equals(protocol) ? protocol : null);
NotifyListenerWithKey listenerWithKey = new NotifyListenerWithKey(protocolServiceKey, listener);
notifyListeners.add(listenerWithKey);
// Aggregate address and notify on subscription.
List<URL> urls = getAddresses(protocolServiceKey, listener.getConsumerUrl());
if (CollectionUtils.isNotEmpty(urls)) {
logger.info(String.format(
"Notify serviceKey: %s, listener: %s with %s urls on subscription",
protocolServiceKey, listener, urls.size()));
listener.notify(urls);
}
}
List<URL> urls = getAddresses(protocolServiceKey, listener.getConsumerUrl())
通过此方法聚合得到的url为InstanceAddressUrl,示例如下
DefaultServiceInstance{serviceName='dubbo-demo-api-provider', host='10.1.xx.219', port=20880, enabled=true, healthy=true, metadata={dubbo.endpoints=[{"port":20880,"protocol":"dubbo"}], dubbo.metadata-service.url-params={"prefer.serialization":"fastjson2,hessian2","version":"1.0.0","dubbo":"2.0.2","side":"provider","port":"20880","protocol":"dubbo"}, dubbo.metadata.revision=341cd1d1e10ad64119c07e88ad034887, dubbo.metadata.storage-type=local, timestamp=1742004299061}}
之后,会对urls进行notify,根据notify一路跳到下面这个方法org.apache.dubbo.registry.client.ServiceDiscoveryRegistryDirectory#refreshInvoker
java
private void refreshInvoker(List<URL> invokerUrls) {
Assert.notNull(invokerUrls, "invokerUrls should not be null, use EMPTY url to clear current addresses.");
this.originalUrls = invokerUrls;
if (invokerUrls.size() == 1 && EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
refreshRouter(
BitList.emptyList(), () -> this.forbidden = true // Forbid to access
);
destroyAllInvokers(); // Close all invokers
} else {
this.forbidden = false; // Allow accessing
if (CollectionUtils.isEmpty(invokerUrls)) {
return;
}
// use local reference to avoid NPE as this.urlInvokerMap will be set null concurrently at
// destroyAllInvokers().
Map<ProtocolServiceKeyWithAddress, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap;
// can't use local reference as oldUrlInvokerMap's mappings might be removed directly at toInvokers().
Map<ProtocolServiceKeyWithAddress, Invoker<T>> oldUrlInvokerMap = null;
if (localUrlInvokerMap != null) {
// the initial capacity should be set greater than the maximum number of entries divided by the load
// factor to avoid resizing.
oldUrlInvokerMap =
new LinkedHashMap<>(Math.round(1 + localUrlInvokerMap.size() / DEFAULT_HASHMAP_LOAD_FACTOR));
localUrlInvokerMap.forEach(oldUrlInvokerMap::put);
}
// 核心在这,将url转化成Invoker
Map<ProtocolServiceKeyWithAddress, Invoker<T>> newUrlInvokerMap =
toInvokers(oldUrlInvokerMap, invokerUrls); // Translate url list to Invoker map
logger.info(String.format("Refreshed invoker size %s from registry %s", newUrlInvokerMap.size(), this));
if (CollectionUtils.isEmptyMap(newUrlInvokerMap)) {
logger.error(
PROTOCOL_UNSUPPORTED,
"",
"",
"Unsupported protocol.",
new IllegalStateException(String.format(
"Cannot create invokers from url address list (total %s)", invokerUrls.size())));
return;
}
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;
if (oldUrlInvokerMap != null) {
try {
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
} catch (Exception e) {
logger.warn(PROTOCOL_FAILED_DESTROY_INVOKER, "", "", "destroyUnusedInvokers error. ", e);
}
}
}
// notify invokers refreshed
this.invokersChanged();
}
这段代码相信已经比较熟悉了,和前面接口级服务发现干的事基本一致,只不过不在一个类中。具体流程在此就不再重复叙述,最后也是创建了netty客户端,然后封装在了一个DubboInvoker中,再将得到的invoker进行缓存填充,最后层层封装,得到ScopeClusterInvoker
再回到下面的方法
java
@Override
public void migrateToApplicationFirstInvoker(MigrationRule newRule) {
CountDownLatch latch = new CountDownLatch(0);
refreshInterfaceInvoker(latch);
refreshServiceDiscoveryInvoker(latch);
// directly calculate preferred invoker, will not wait until address notify
// calculation will re-occurred when address notify later
calcPreferredInvoker(newRule);
}
接着执行calcPreferredInvoker
去从invoker(接口级Invoker)和serviceDiscoveryInvoker(应用级Invoker)中选出currentAvailableInvoker
java
this.currentAvailableInvoker = serviceDiscoveryInvoker;
这里直接说结果,Invoker平滑迁移机制选出的是serviceDiscoveryInvoker。
下面放下接口级Invoker和应用级Invoker中Directory
中urlInvokerMap
缓存的区别
接口级Invoker下Directory为RegistryDirectory,其中的urlInvokerMap如下
arduino
/**java
* Map<url, Invoker> cache service url to invoker mapping.
* The initial value is null and the midway may be assigned to null, please use the local variable reference
*/
protected volatile Map<URL, Invoker<T>> urlInvokerMap;
应用级Invoker下Directory为ServiceDiscoveryRegistryDirectory,其中的urlInvokerMap如下
swift
/**java
* instance address to invoker mapping.
* The initial value is null and the midway may be assigned to null, please use the local variable reference
*/
private volatile Map<ProtocolServiceKeyWithAddress, Invoker<T>> urlInvokerMap;
别的不再多讲,回到前面选出currentAvailableInvoker
之后一路跳回到org.apache.dubbo.config.ReferenceConfig#createInvoker
最终拿到的invoker
是MigrationInvoker
,并将其放到ReferenceConfig
的invoker
缓存中,其中包含了三种invoker,如下图
服务发现流程到这里也就结束了,后续会根据invoker来进行服务调用。
总结
Dubbo3.2.6的服务发现机制是基于Invoker平滑迁移机制来实现的,即在接口级服务发现模型向应用级服务发现模型升级过程中,保证两种类型都兼容的机制。
首先是一系列配置的初始化
之后开始invoker迁移流程
Migration机制中,先对接口级Invoker进行refresh处理,其中先在zk注册consumer节点,然后对providers进行订阅,在订阅时,会根据url和服务提供者建立netty网络Client,封装成为DubboInvoker
,并将这些Invoker放入到RegistryDirectory
的本地缓存中,封装得到最后的ScopeClusterInvoker
。
接着对应用级的Invoker进行refresh处理,然后对包含目标接口的应用进行订阅,会根据instanceAddressURL
去和服务提供者建立netty网络Client,也封装成DubboInvoker,并放入到ServiceDiscoveryRegistryDirectory
的本地缓存中,封装得到最后的ScopeClusterInvoker
。
之后,对二者进行选举,选出当前可用的invoker,即应用级Invoker(serviceDiscoveryInvoker
)
最后统一封装进ReferenceConfig
的MigrationInvoker
本地缓存中。