文章目录
1 集群容错模式配置优先级配置
- 在服务端和消费端都配置集群容错模式时,以消费端配置优先级最高
- 若消费端没有配置集群容错模式而服务端配置了集群容错模式,则使用服务端的集群容错模式
以如下配置为例,主要探究一下上面优先级配置,如服务端和消费端都配置了相同的参数dubbo怎么做相应的处理。
<dubbo:service interface="service.DemoService" ref="demoService" protocol="dubbo" group="dubbo_test" cluster="mergeable">
<dubbo:reference id="permissionService" interface="service.DemoService" group="dubbo_test">
<dubbo:parameter key="invoker.listener" value="invokerListenerTest"/>
</dubbo:reference>
2.配置原理
消费端启动过程在refer服务过程中会创建RegistryDirectory实例directory,同时directory向注册中心发起订阅,即向服务器端发送订阅请求,此时Zookeeper会创建订阅服务节点,然后进行通知并刷新重建invoker实例。
2.1 原理初探:Directory实例的overrideDirectoryUrl属性
此时服务端启动成功,消费端debug启动,直接看RegistryProtocol的doRefer方法:
private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);
directory.setProtocol(protocol);
URL subscribeUrl = new URL(Constants.CONSUMER_PROTOCOL, NetUtils.getLocalHost(), 0, type.getName(), directory.getUrl().getParameters());
if (! Constants.ANY_VALUE.equals(url.getServiceInterface())
&& url.getParameter(Constants.REGISTER_KEY, true)) {
registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY,
Constants.CHECK_KEY, String.valueOf(false)));
}
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
// 这里会调用cluster的适配器code
return cluster.join(directory);
}
其中Cluster的适配器code如下:
package com.alibaba.dubbo.rpc.cluster;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Cluster$Adpative implements com.alibaba.dubbo.rpc.cluster.Cluster {
public com.alibaba.dubbo.rpc.Invoker join(com.alibaba.dubbo.rpc.cluster.Directory arg0) throws com.alibaba.dubbo.rpc.cluster.Directory {
if (arg0 == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument == null");
if (arg0.getUrl() == null)
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.cluster.Directory argument getUrl() == null");
com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("cluster", "failover");
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.cluster.Cluster) name from url(" + url.toString() + ") use keys([cluster])");
com.alibaba.dubbo.rpc.cluster.Cluster extension = (com.alibaba.dubbo.rpc.cluster.Cluster)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
return extension.join(arg0);
}
}
doRefer方法中会调用Cluster的适配器code,即会最终会调用code中的下面一行代码:
directory.getUrl().getParameter("cluster")
再看 directory.getUrl() 返回的是Directory实例的overrideDirectoryUrl属性:
public URL getUrl() {
return this.overrideDirectoryUrl;
}
2.2 ClusterUtils进行消费端和远程参数合并,同时更新providerUrl,并对Directory实例的overrideDirectoryUrl属性进行更新
重点分析一下RegistryProtocol的doRefer方法中的一行,
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));
即directory进行订阅subscribeUrl,此时subscribeUrl的值是:
consumer://192.168.1.102/service.DemoService?application=dubbo-consumer&category=providers,configurators,routers&dubbo=2.5.0&group=dubbo_test&interface=service.DemoService&invoker.listener=invokerListenerTest&methods=getPermission&organization=dubbox&owner=programmer&pid=34308&side=consumer×tamp=1558369013207
接下来的调用链:
RegistryDirectory的subscribe方法 --> FailbackRegistry的subscribe方法 --> ZookeeperRegistry的doSubscribe方法 --> FailbackRegistry的notify方法 --> RegistryDirectory的notify方法
这里直接看RegistryDirectory的notify方法:
public synchronized void notify(List<URL> urls) {
// 省略大部分代码......
// providers
refreshInvoker(invokerUrls);
}
/**
**invokerUrls size是1,值:dubbo://10.88.97.9:20880/service.DemoService?anyhost=true&application=debbo-provider&cluster=mergeable&dubbo=2.8.4&generic=false&group=dubbo_test&interface=service.DemoService&methods=getPermission&organization=dubbox&owner=programmer&pid=34787&side=provider×tamp=1558403615130
**/
private void refreshInvoker(List<URL> invokerUrls){
//......
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls) ;// 将URL列表转成Invoker列表
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 换方法名映射Invoker列表
//......
}
// 将urls转成invokers,如果url已经被refer过,不再重新引用。
private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<String, Invoker<T>>();
if(urls == null || urls.size() == 0){
return newUrlInvokerMap;
}
Set<String> keys = new HashSet<String>();
// 获取消费端的protocol key,其中queryMap存储的是消费端配置的参数,此时无"protocol" key,即queryProtocols=null
String queryProtocols = this.queryMap.get(Constants.PROTOCOL_KEY); // "protocol" key
for (URL providerUrl : urls) {
//如果reference端配置了protocol,则只选择匹配的protocol
if (queryProtocols != null && queryProtocols.length() >0) {
boolean accept = false;
String[] acceptProtocols = queryProtocols.split(",");
for (String acceptProtocol : acceptProtocols) {
if (providerUrl.getProtocol().equals(acceptProtocol)) {
accept = true;
break;
}
} // for
if (!accept) {
continue;
}
}
// 若协议头是"empty"开头,则进入到下一次for循环
if (Constants.EMPTY_PROTOCOL.equals(providerUrl.getProtocol())) {
continue;
}
if (!ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(providerUrl.getProtocol())) {
logger.error(......);
continue;
}
// 合并URL,这里是重点
URL url = mergeUrl(providerUrl);
String key = url.toFullString(); // URL参数是排序的
if (keys.contains(key)) { // 重复URL
continue;
}
keys.add(key);
// 缓存key为没有合并消费端参数的URL,不管消费端如何合并参数,如果服务端URL发生变化,则重新refer
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
if (invoker == null) { // 缓存中没有,重新refer
boolean enabled = true;
if (url.hasParameter(Constants.DISABLED_KEY)) {
enabled = ! url.getParameter(Constants.DISABLED_KEY, false);
} else {
enabled = url.getParameter(Constants.ENABLED_KEY, true);
}
if (enabled) {
// 重新refer
invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url,providerUrl);
}
if (invoker != null) { // 将新的引用放入缓存
newUrlInvokerMap.put(key, invoker);
}
}else {
newUrlInvokerMap.put(key, invoker);
}
} // for
keys.clear();
return newUrlInvokerMap;
}
// 合并url参数 顺序为override > -D >Consumer > Provider
private URL mergeUrl(URL providerUrl){
// 合并消费端参数
providerUrl = ClusterUtils.mergeUrl(providerUrl, queryMap);
List<Configurator> localConfigurators = this.configurators; // local reference
if (localConfigurators != null && localConfigurators.size() > 0) {
for (Configurator configurator : localConfigurators) {
providerUrl = configurator.configure(providerUrl);
}
}
// 不检查连接是否成功,总是创建Invoker!设置check key为false,该值表示启动时检查提供者是否存在,true报错,false忽略
providerUrl = providerUrl.addParameter(Constants.CHECK_KEY, String.valueOf(false));
//合并提供者参数。这里更新了overrideDirectoryUrl,另外directoryUrl 与 override 合并是在notify的最后,这里不能够处理
this.overrideDirectoryUrl = this.overrideDirectoryUrl.addParametersIfAbsent(providerUrl.getParameters());
// 设置path
if ((providerUrl.getPath() == null || providerUrl.getPath().length() == 0) && "dubbo".equals(providerUrl.getProtocol())) { // 兼容1.0
String path = directoryUrl.getParameter(Constants.INTERFACE_KEY); // interface key
if (path != null) {
int i = path.indexOf('/');
if (i >= 0) {
path = path.substring(i + 1);
}
i = path.lastIndexOf(':');
if (i >= 0) {
path = path.substring(0, i);
}
providerUrl = providerUrl.setPath(path);
}
}
return providerUrl;
}
上面进行完消费端参数合并之后,providerUrl的值是:
dubbo://10.88.97.9:20880/service.DemoService?anyhost=true&application=dubbo-consumer&cluster=mergeable&dubbo=2.8.4&generic=false&group=dubbo_test&interface=service.DemoService&invoker.listener=invokerListenerTest&methods=getPermission&organization=dubbox&owner=programmer&pid=35779&side=consumer×tamp=1558414726755
这里providerUrl已经包含了服务端配置的"cluster"的key,其value是"mergeable",下面再看一下ClusterUtils.mergeUrl方法,这里做了合并消费端参数:
public class ClusterUtils {
public static URL mergeUrl(URL remoteUrl, Map<String, String> localMap) {
Map<String, String> map = new HashMap<String, String>();
Map<String, String> remoteMap = remoteUrl.getParameters();
if (remoteMap != null && remoteMap.size() > 0) {
// map中首先放置远程参数
map.putAll(remoteMap);
//线程池配置不使用提供者的
map.remove(Constants.THREAD_NAME_KEY);
map.remove(Constants.CORE_THREADS_KEY);
map.remove(Constants.THREADS_KEY);
map.remove(Constants.QUEUES_KEY);
map.remove(Constants.ALIVE_KEY);
}
if (localMap != null && localMap.size() > 0) {
// map中再放置本地参数,若存在和远程冲突的key值,则覆盖
map.putAll(localMap);
}
if (remoteMap != null && remoteMap.size() > 0) {
// 版本号使用提供者的
String dubbo = remoteMap.get(Constants.DUBBO_VERSION_KEY);
if (dubbo != null && dubbo.length() > 0) {
map.put(Constants.DUBBO_VERSION_KEY, dubbo);
}
String version = remoteMap.get(Constants.VERSION_KEY);
if (version != null && version.length() > 0) {
map.put(Constants.VERSION_KEY, version);
}
String group = remoteMap.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
map.put(Constants.GROUP_KEY, group);
}
String methods = remoteMap.get(Constants.METHODS_KEY);
if (methods != null && methods.length() > 0) {
map.put(Constants.METHODS_KEY, methods);
}
// 合并filter和listener
String remoteFilter = remoteMap.get(Constants.REFERENCE_FILTER_KEY);
String localFilter = localMap.get(Constants.REFERENCE_FILTER_KEY);
if (remoteFilter != null && remoteFilter.length() > 0
&& localFilter != null && localFilter.length() > 0) {
localMap.put(Constants.REFERENCE_FILTER_KEY, remoteFilter + "," + localFilter);
}
String remoteListener = remoteMap.get(Constants.INVOKER_LISTENER_KEY);
String localListener = localMap.get(Constants.INVOKER_LISTENER_KEY);
if (remoteListener != null && remoteListener.length() > 0
&& localListener != null && localListener.length() > 0) {
localMap.put(Constants.INVOKER_LISTENER_KEY, remoteListener + "," + localListener);
}
}
return remoteUrl.addParameters(map);
}
private ClusterUtils() {}
}