消费端启动初始化过程

  消费端的代码解析也是从配置文件解析开始的,服务发布对应的<dubbo:service,解析xml的时候解析了一个ServiceBean,并且调用ServiceConfig进行服务的发布。服务的消费对应的<dubbo:reference,在初始化的过程中也解析了一个 ReferenceBean类去做处理。在bean加载后会调用里面的 afterPropertiesSet()这个方法。也就是把配置文件解析到各个对应bean里面。然后调用了getObject():

public Object getObject() throws Exception {
return get();
}

  然后也有一个 ReferenceConfig这个类来一起执行,进入他的get()方法:

public synchronized T get() {
if (destroyed) {
throw new IllegalStateException("Already destroyed!");
}
if (ref == null) {
init();//初始化
}
return ref;
}

  然后进入初始化操作。init() 方法里的操作跟服务发布的时候很类似,还是检查各种配置,解析各种标签,最后封装到一个 map 里面。然后调用 ref = createProxy(map); 返回一个代理对象,对于该方法体内前面很多代码都是执行初始化。判断虚拟机情况,组装URL什么的。主要关于服务端调用的的部分代码如下:

} else { //从注册中心上获得相应的协议url地址
List<URL> us = loadRegistries(false);
if (us != null && us.size() > 0) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(Constants.MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(Constants.REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls == null || urls.size() == 0) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
} if (urls.size() == 1) {
// 获得invoker代理对象MockClusterInvoker
invoker = refprotocol.refer(interfaceClass, urls.get(0));
} else {
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(refprotocol.refer(interfaceClass, url));
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // 用了最后一个registry url }
}
if (registryURL != null) { //有 注册中心协议的URL
//对有注册中心的Cluster 只用 AvailableCluster
URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);
invoker = cluster.join(new StaticDirectory(u, invokers));
} else { //不是 注册中心的URL
invoker = cluster.join(new StaticDirectory(invokers));
}
}
}
Boolean c = check;
if (c == null && consumer != null) {
c = consumer.isCheck();
}
if (c == null) {
c = true; // default true
}
if (c && !invoker.isAvailable()) {
throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
//
return (T) proxyFactory.getProxy(invoker);

  其中 List<URL> us = loadRegistries(false); 是从配置文件解析,最后组装了这么一个地址,基于前面的案例,debug以后发现该地址如下:

registry://192.168.254.135:2181/com.alibaba.dubbo.registry.RegistryService?application=dubbo-client&dubbo=2.5.4&owner=wuzz&pid=53420&registry=zookeeper&timestamp=1543905535962

  我们可以看到以上代码有一些步骤是关于  Monitor 的操作,暂时不管。由于我们这里注册中心为1个,最后会进入以下操作:

if (urls.size() == 1) {
// 获得invoker代理对象
invoker = refprotocol.refer(interfaceClass, urls.get(0));
}

  可以从代码中发现这个 refprotocol 又是一个自适应适配器 Protocol$Adaptive ,他会调用里面的refer() 方法:

public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1)
throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null)
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if (extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url("
+ url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}

  又是熟悉的套路,这里代码中的 url.getProtocol() = registry ,所以 extension 就是 RegistryProtocol,调用他的 refer() ,跟发布服务的流程真的很像:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// url本身是一个 registry://....
// 替换成了<dubbo:registry address="zookeeper://192.168.254.135:2181" />中的zookeeper
// zookeeper://....
url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
// 跟服务注册的时候一模一样的代码,最后获得ZookeeperRegistry 获得跟zk的链接
Registry registry = registryFactory.getRegistry(url);
if (RegistryService.class.equals(type)) {// 这里的type是interface com.gupaoedu.dubbo.IGpHello
return proxyFactory.getInvoker((T) registry, type, url);
} // group="a,b" or group="*"
Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
String group = qs.get(Constants.GROUP_KEY);
if (group != null && group.length() > 0) {
if ((Constants.COMMA_SPLIT_PATTERN.split(group)).length > 1
|| "*".equals(group)) {
return doRefer(getMergeableCluster(), registry, type, url);
}
}
// 最后调用这个
return doRefer(cluster, registry, type, url);
}

  doRefer(cluster, registry, type, url):com.alibaba.dubbo.rpc.cluster.Cluster$Adpative,ZookeeperRigistry,interface com.gupaoedu.dubbo.IGpHello,zookeeper://192.168.254.135:2181/...:

  cluster : 这个是个依赖注入的拓展点:

@SPI(FailoverCluster.NAME)
public interface Cluster { /**
* Merge the directory invokers to a virtual invoker.
*
* @param <T>
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException; }

  而且可以从源码中发现这cluster默认是 FailoverCluster ,而且@Adaptive是标注在方法上,所以会生成一个自适应的适配器 Cluster$Adaptive:

public class Cluster$Adaptive 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.RpcException {
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);
}
}

  很明显,这个时候我们可以知道这个适配器里面会生成一个拓展点,而这个 extension 默认他就是 FailoverCluster 而且在文件 com.alibaba.dubbo.rpc.cluster.Cluster中:

mock=com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster
failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster
failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster
failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster
forking=com.alibaba.dubbo.rpc.cluster.support.ForkingCluster
available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster
mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster
broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster

  我们发现了一个装饰器 Wrapper :mock=com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper,经过查看其源码,发现内有一个携带了拓展点Cluster的构造方法,所以上面说到的 extension其实是MockClusterWrapper(FailoverCluster)。这里就是之前提到的服务降级里面的 Mock机制,在你 FailoverCluster失败之后调用Mock。

  了解了 Cluster 我们继续进入 doRefer(cluster, registry, type, url)::

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
// 对多个invoker 进行组装,也就是做到服务的动态上下线的功能
RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
directory.setRegistry(registry);//ZookeeperRegistry
directory.setProtocol(protocol);// Protocol$Adaptive
// 组装一个 url=comsumer://192.168.254.135/.......
// 就是ZK上面会注册一个消费的协议地址
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)) {
// 注册一个消费协议地址 最后是zkClient.create()
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));
return cluster.join(directory);
}

  最后的两行代码我们从官网上能找到一张图:http://dubbo.apache.org/zh-cn/docs/user/demos/fault-tolerent-strategy.html

  通过 Directory 传入cluster,返回一个 List<invoker>,传入路由,经过路由机制,继续传入负载均衡做一个负载,得到最终调用的 invoker。

  先来看一下 cluster.join(directory):实际会调用到 MockClusterWrapper 的 join方法:

public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new MockClusterInvoker<T>(directory,
this.cluster.join(directory));
}

  到这里new 出来一个 MockClusterInvoker 。到此为止 invoker = refprotocol.refer(interfaceClass, urls.get(0));所得到的对象就是  MockClusterInvoker 。

  接着流程来到了 proxyFactory.getProxy(invoker) :

  proxyFactory 这个东西是否是曾相识呢?? 在服务的发布之前也调用了proxyFactory去获得一个服务的代理对象去发布,而且这个proxyFactory对应的是 :先是生成一个ProxyFactory$Adpative,再调用里面的getProxy,通过默认实现extName=Javassist来获得一个Extension,即JavassistProxyFactory,然后去调用他的getProxy方法,但是在拓展点文件 com.alibaba.dubbo.rpc.ProxyFactory文件中:

stub=com.alibaba.dubbo.rpc.proxy.wrapper.StubProxyFactoryWrapper
jdk=com.alibaba.dubbo.rpc.proxy.jdk.JdkProxyFactory
javassist=com.alibaba.dubbo.rpc.proxy.javassist.JavassistProxyFactory

  说明有个装饰的Wapper,所以这里需要包装 ,所以proxyFactory其实就是StubProxyFactoryWrapper(JavassistProxyFactory):做了一系列装饰以后生成本地stub存根,还是会进入 JavassistProxyFactory 的getProxy方法:

public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
}

  该方法内基于javassist 机制生成一个动态的代理类,跟之前手写RPC时候获取代理类有点像了,跟之前服务端发布服务获取invoker类似,这个里面最后组装了一个 Class<?> pc = ccm.toClass(); debug看到里面有个 mMethods是一个数组:

public java.lang.String sayHello(java.lang.String arg0){
Object[] args = new Object[1];
args[0] = ($w)$1;
Object ret = handler.invoke(this, methods[0], args);
return (java.lang.String)ret;
} public java.lang.Object $echo(java.lang.Object arg0) {
Object[] args = new Object[1];
args[0] = ($w) $1;
Object ret = handler.invoke(this, methods[1], args);
return (java.lang.Object) ret;
}

  最后调用方法的时候通过 InvokerInvocationHandler 的invoke去进行远程调用。最后生成的代理类当中会有一个 InvokerInvocationHandler 属性,并且要通过构造进行赋值,也就是上面的Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));这段代码,然后再通过哪个生成的pc这个代理类的 sayHello去调用方法 。至此,消费端去消费获得引用的流程就结束了。

什么时候建立和服务端的连接:

  消费端的初始化过程,但是似乎没有看到客户端和服务端建立NIO连接。实际上,建立连接的过程在消费端初始化的时候就建立好的,只是前面我们没有分析,代码在RegistryProtocol.doRefer方法内directory.subscribe方法中:做订阅

directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY,
Constants.PROVIDERS_CATEGORY
+ "," + Constants.CONFIGURATORS_CATEGORY
+ "," + Constants.ROUTERS_CATEGORY));

  调用RegistryDirectory<T>里面的subscribe(URL url):

public void subscribe(URL url) {
// url=consumer://192.168.254.1/com.gupaoedu.dubbo.IGpHello?....
setConsumerUrl(url);//设置消费端URL
registry.subscribe(url, this);
}

  这里的registry就是之前所设置的ZookeeperRegistry ,url 就是comsumer://....,this就是 RegistryDirectory,由于ZookeeperRegistry没有实现 subscribe方法,所以去他的父类中去找,父类是 FailbackRegistry:

public void subscribe(URL url, NotifyListener listener) {
if (destroyed.get()){
return;
}
super.subscribe(url, listener);
//移除失败的订阅
removeFailedSubscribed(url, listener);
try {
// 向服务器端发送订阅请求
doSubscribe(url, listener);
} catch (Exception e) {
Throwable t = e; List<URL> urls = getCacheUrls(url);
if (urls != null && urls.size() > 0) {
notify(url, listener, urls);
logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
} else {
// 如果开启了启动检测,则直接抛出异常
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);
}
}

  doSubscribe(url, listener);是个模板方法,会在子类中实现,即 ZookeeperRegistry:

protected void doSubscribe(final URL url, final NotifyListener listener) {
try {//这里的Interface是com.gupaoedu.dubbo.IGpHello,明显不相等,走else
if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
String root = toRootPath();
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) {
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
listeners = zkListeners.get(url);
}
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List<String> currentChilds) {
for (String child : currentChilds) {
child = URL.decode(child);
if (!anyServices.contains(child)) {
anyServices.add(child);
subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
}
});
zkListener = listeners.get(listener);
}
zkClient.create(root, false);
List<String> services = zkClient.addChildListener(root, zkListener);
if (services != null && services.size() > 0) {
for (String service : services) {
service = URL.decode(service);
anyServices.add(service);
subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service,
Constants.CHECK_KEY, String.valueOf(false)), listener);
}
}
} else {// 进入这里
List<URL> urls = new ArrayList<URL>();
//toCategoriesPath(url) 会得到一个数组:[/dubbo/com.gupaoedu.dubbo.IGpHello/providers,
// /dubbo/com.gupaoedu.dubbo.IGpHello/configurators, /dubbo/com.gupaoedu.dubbo.IGpHello/routers]
for (String path : toCategoriesPath(url)) {
//第一次进来是空的,存放监听器
ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
if (listeners == null) {
// 将url做key 新建一个map做value,放入监听器
zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
// listeners ={}
listeners = zkListeners.get(url);
}
// null
ChildListener zkListener = listeners.get(listener);
if (zkListener == null) {
// 这里新建了一个监听器该监听器为子节点变化监听并且变化后调用notify做事件处理
listeners.putIfAbsent(listener, new ChildListener() {
public void childChanged(String parentPath, List<String> currentChilds) {
ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
}
});
// 得到该监听器
zkListener = listeners.get(listener);
}
// 创建节点,并且将上面创建的监听器放入
zkClient.create(path, false);
List<String> children = zkClient.addChildListener(path, zkListener);
if (children != null) {
urls.addAll(toUrlsWithEmpty(url, path, children));
}
}
notify(url, listener, urls);
}
} catch (Throwable e) {
throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
}
}

  notify(url, listener, urls):将刚刚的消费者的地址,监听器,以及自己组装的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");
}
try {
doNotify(url, listener, urls);
} catch (Exception t) {
//
Map<NotifyListener, List<URL>> listeners = failedNotified.get(url);
if (listeners == null) {
failedNotified.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, List<URL>>());
listeners = failedNotified.get(url);
}
listeners.put(listener, urls);
logger.error("Failed to notify for subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
}
}

  doNotify(url, listener, urls);这个方法最终通过调用链进入 AbstactRegistry :

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 ((urls == null || urls.size() == 0)
&& !Constants.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);
}
// 通过判断 新建一个map
Map<String, List<URL>> result = new HashMap<String, List<URL>>();
for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
List<URL> categoryList = result.get(category);
if (categoryList == null) {
categoryList = new ArrayList<URL>();
result.put(category, categoryList);
}
categoryList.add(u);
}
}// 判断
if (result.size() == 0) {
return;
}//对 notified做个赋值 以comsumer的url为key value为{}
Map<String, List<URL>> categoryNotified = notified.get(url);
if (categoryNotified == null) {
notified.putIfAbsent(url, new ConcurrentHashMap<String, List<URL>>());
categoryNotified = notified.get(url);
}//循环
for (Map.Entry<String, List<URL>> entry : result.entrySet()) {
String category = entry.getKey();//获得key
List<URL> categoryList = entry.getValue();//获得value
categoryNotified.put(category, categoryList);//存值
saveProperties(url);//保存到本地配置
listener.notify(categoryList);
}
}

  上述代码中:

for (URL u : urls) {
if (UrlUtils.isMatch(url, u)) {
String category = u.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY);
List<URL> categoryList = result.get(category);
if (categoryList == null) {
categoryList = new ArrayList<URL>();
result.put(category, categoryList);
}
categoryList.add(u);
}
}

  这段代码片段循环过后会将map做个初始化,结果为:其实就是我们在ZK上面看到的节点

{
configurators=[empty://192.168.254.1/com.gupaoedu.dubbo.IGpHello?application=dubbo-client&category=configurators&dubbo=2.5.4&interface=com.gupaoedu.dubbo.IGpHello&methods=sayHello
&mock=com.gupaoedu.dubbo.TestMock&owner=wuzz&pid=&protocol=dubbo&side=consumer&timeout=&timestamp=],
routers=[empty://192.168.254.1/com.gupaoedu.dubbo.IGpHello?application=dubbo-client&category=routers&dubbo=2.5.4&interface=com.gupaoedu.dubbo.IGpHello&methods=sayHello
&mock=com.gupaoedu.dubbo.TestMock&owner=wuzz&pid=&protocol=dubbo&side=consumer&timeout=&timestamp=],
providers=[dubbo://192.168.254.1:20880/com.gupaoedu.dubbo.IGpHello?anyhost=true&application=dubbo-server&dubbo=2.5.4&generic=false&interface=com.gupaoedu.dubbo.IGpHello&methods=sayHello
&owner=wuzz&pid=&side=provider&timestamp=]}

  在最后循环里面的  listener.notify(categoryList); 进行一个ZK目录节点下的变化做一个更新:其实就是做到服务地址上下线的通知更新(缓存和变更缓存):该方法内前半部分主要是新建了3个关于providers,routers,configurations 3个目录下的地址集合,并进行赋值,然后判断处理,到最后调用refreshInvoker :

    public synchronized void notify(List<URL> urls) {
.......
// providers
refreshInvoker(invokerUrls);
}

  其实RegistryDirectory<T>这个类的作用就是整合 多个invoker ,以及对于zk的指定的服务节点的变化进行监听并进行刷新。refreshInvoker(invokerUrls);这个方法主要的作用就是将传入的 invokerUrls转化成invoker列表,如果已经转换则不再重新引用,直接从缓存中获取,如果传入的invokerUrls不为空,则表示最新的invokerUrls,如果为空,则表示只是下发更新地址活路由router规则:

private void refreshInvoker(List<URL> invokerUrls) {
if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
&& Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
this.forbidden = true; // 禁止访问
this.methodInvokerMap = null; // 置空列表
destroyAllInvokers(); // 关闭所有的nvoker
} else {
this.forbidden = false; // 允许访问
Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
if (invokerUrls.size() == 0 && this.cachedInvokerUrls != null) {
invokerUrls.addAll(this.cachedInvokerUrls);
} else {
this.cachedInvokerUrls = new HashSet<URL>();
this.cachedInvokerUrls.addAll(invokerUrls);//缓存invokerUrl列表
}
if (invokerUrls.size() == 0) {
return;
}
// 将URL列表转换成invoker列表
Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);
Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // 将方法名映射到invoker列表
// state change
//计算失误不处理
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
return;
}
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
this.urlInvokerMap = newUrlInvokerMap;
try {
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap);
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}
}
}

  最核心的方法则是  Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);这个方法,就是转化的方法:比如一个地址是dubbo://192.168.254.135...这样的地址 转化成一个DubboInvoker,下面来看一下里面的部分代码:

private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
...... if (invoker == null) { //
try {
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) {
invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl);
}
} catch (Throwable t) {
logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
}
if (invoker != null) { //
newUrlInvokerMap.put(key, invoker);
}
} else {
newUrlInvokerMap.put(key, invoker);
}
.......
}

   这个里面有调了一个拓展点 invoker = new InvokerDelegete<T>(protocol.refer(serviceType, url), url, providerUrl); 这个protocol是 Protocol$Adaptive ,而Url里面拿到的protocol是dubbo,最后进入了 DubboProtocol的refer:

public <T> Invoker<T> refer(Class<T> serviceType, URL url) throws RpcException {
// create rpc invoker.
DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
invokers.add(invoker);
return invoker;
}

  这里新建了一个 DubboInvoker,里面getClient(url) 方法就是与服务端产生连接的代码,顺着这条主线下去,我们会在initClient(url) 这个方法里面找到如下代码:

client = Exchangers.connect(url, requestHandler);

  期间判断我们的连接状态,接着进入getExchanger(url).connect(url, handler);这里跟服务端的原理是一样的,可以翻过去看看,然后也是通过NettyTransporter去创建一个连接。

  然后我们回到RegistryDirectory<T>里面的toInvokers(List<URL>) 方法里:

// 缓存key为没有合并消费端参数的URL,不管消费端如何合并,如果服务端URL发生变法,则重新refer
Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
if (newUrlInvokerMap == null || newUrlInvokerMap.size() == ) {
logger.error(new IllegalStateException("urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
return;
}//把 invoker 放入成员变量中,后续需要去获取
this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
this.urlInvokerMap = newUrlInvokerMap;
try {
destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); //
} catch (Exception e) {
logger.warn("destroyUnusedInvokers error. ", e);
}

  这里最后获得的 invoker是:

  而且这里是经过包装的 DubboInvoker .到这里,消费端的建立会话跟获取invoker的过程都结束了,下面应该进入数据交互,之前服务端发布的时候获取到的 invoker对象是 JavassistProxyFactory.在代理调用方法的时候会调用 代理的sayHello方法,在动态生成的代理类字节码上我们可以看到他调用的是 handler.invoke,也就是 InvocationHandler 里面的invoke 方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
if (method.getDeclaringClass() == Object.class) {
return method.invoke(invoker, args);
}
if ("toString".equals(methodName) && parameterTypes.length == 0) {
return invoker.toString();
}
if ("hashCode".equals(methodName) && parameterTypes.length == 0) {
return invoker.hashCode();
}
if ("equals".equals(methodName) && parameterTypes.length == 1) {
return invoker.equals(args[0]);
}
return invoker.invoke(new RpcInvocation(method, args)).recreate();
}

  最后调用  invoker.invoke(new RpcInvocation(method, args)).recreate(); 这里需要知道 invoke是什么,其实就是之前在ReferenceConfig类里面的createProxy方法内:

invoker = refprotocol.refer(interfaceClass, urls.get(0));

  其实就是这个 InvokerInvocationHandler 对象,接着我们进入 MockClusterInvoker的invoke方法:

    public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
//*******关键,这个地方会从url里面回去 mock的key,也就是之前配置文件配置的moke
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
//no mock 没有
result = this.invoker.invoke(invocation);
// force 表示强制调用,不仅仅是请求失败的情况采取调用
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);
} else {//所以配置了mock一般会进来这里
//fail-mock
try {
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
} else {
if (logger.isWarnEnabled()) {
logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
}//原来mock机制在这里
result = doMockInvoke(invocation, e);
}
}
}
return result;
}

  由于我是配置了mock的,所以debug可以看到:这里拿到的value就是我们对应的类名

  所以到了 result = this.invoker.invoke(invocation);这个result 就是我们需要的结果,而这个invoker就是他的默认的服务降级的配置了:是他 FailoverClusterInvoker

  为什么是 Failover呢,因为 Cluster的默认拓展点就是这个类,而MockCluster仅仅是对其做了一个封装:进入他的invoke方法会发现 FailoverClusterInvoker对该方法并没有做实现,所以还是跟往常一样,进入父类的方法 AbstractClusterInvoker<T> 这个类的invoke:我们会发现里面有一个 LoadBalance的局部变量,而这个就是光网所提供的图里面的关于负载那块相关的,通过负载便会返回一个具体的invoker去调用

    public Result invoke(final Invocation invocation) throws RpcException {

        checkWhetherDestroyed();
// 做负载
LoadBalance loadbalance; List<Invoker<T>> invokers = list(invocation);
if (invokers != null && invokers.size() > 0) {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl()
.getMethodParameter(invocation.getMethodName(), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE));
} else {
loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
}
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
return doInvoke(invocation, invokers, loadbalance);
}

  通过 RegistryDiectory 去获取一个invoker的list。在 RegistryDiectory 里面的doList 就是通过之前的成员变量 methodInvokerMap 去拿的

   ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension,又发现拓展点,进 LoadBalance.class 去看可以看到默认实现是 @SPI(RandomLoadBalance.NAME) 随机负载算法。nice

  最后调用  doInvoke(invocation, invokers, loadbalance); 这里是继续进入 FailoverClusterInvoker 的doInvoke方法:该方法拿到了一个委托的Invoker(DelegateInvoker):

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyinvokers = invokers;
checkInvokers(copyinvokers, invocation);
int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// retry loop.
RpcException le = null; // last exception.
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
//
//
if (i > 0) {
checkWhetherDestroyed();
copyinvokers = list(invocation);
//
checkInvokers(copyinvokers, invocation);
}//做负载 获得一个具体的 invoker
Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers((List) invoked);
try {//调用该方法获取结果
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + invocation.getMethodName()
+ " in the service " + getInterface().getName()
+ " was successful by the provider " + invoker.getUrl().getAddress()
+ ", but there have been failed providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost()
+ " using the dubbo version " + Version.getVersion() + ". Last error is: "
+ le.getMessage(), le);
}
return result;
} catch (RpcException e) {
if (e.isBiz()) { // biz exception.
throw e;
}
le = e;
} catch (Throwable e) {
le = new RpcException(e.getMessage(), e);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method "
+ invocation.getMethodName() + " in the service " + getInterface().getName()
+ ". Tried " + len + " times of the providers " + providers
+ " (" + providers.size() + "/" + copyinvokers.size()
+ ") from the registry " + directory.getUrl().getAddress()
+ " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version "
+ Version.getVersion() + ". Last error is: "
+ (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le);
}

  因为之前在toInvokers方法中进行转换的时候 是一个DubboInvoker 进行了层层包装,我们又发现 里面 并没有实现这个方法,所以继续找父类 AbstractInvoker 这个里面可以看到invoke方法,该方法内最终是调用了一个 doInvoke 方法,而这个方法是具体调到 Dubboinvoker里面的方法:

protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version); ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {// 判断当前的通信方式,是单向啊异步啊什么的
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) {
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) {
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else {
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}

  最后调用 currentClient.request(inv, timeout).get(),获取结果,这个currentClient 就是创建invoker里面的NettyClient,

    public ResponseFuture request(Object request, int timeout) throws RemotingException {
if (closed) {
throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");
}
// create request.组装请求,跟手写RPC框架一样的
Request req = new Request();
req.setVersion("2.0.0");
req.setTwoWay(true);
req.setData(request);
DefaultFuture future = new DefaultFuture(channel, req, timeout);
try {//通过这个去发送请求
channel.send(req);
} catch (RemotingException e) {
future.cancel();
throw e;
}
return future;
}

  channel.send(req);这个就是最后的发送方法了,通过 HeaderExchangeChannel 类的 send方法 最后进入 NettyChannel,利用netty做一个发送。这就是整个通信过程

  需要异步调用的话在 reference里面配置 <dubbo:method name="goodbye" async="true"/>

上一张官网的时序图:

消费端调用过程流程图

dubbo源码之服务消费的更多相关文章

  1. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

  2. Dubbo源码(五) - 服务目录

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 今天,来聊聊Dubbo的服务目录(Directory).下面是官方文档对服务目录的定义: 服务目 ...

  3. Dubbo源码(四) - 服务引用(消费者)

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 上一篇文章,讲了Dubbo的服务导出: Dubbo源码(三) - 服务导出(生产者) 本文,咱们 ...

  4. dubbo源码之四——服务发布二

    dubbo版本:2.5.4 2. 服务提供者暴露一个服务的详细过程 上图是服务提供者暴露服务的主过程: 首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl ...

  5. Dubbo 源码分析 - 服务引用

    1. 简介 在上一篇文章中,我详细的分析了服务导出的原理.本篇文章我们趁热打铁,继续分析服务引用的原理.在 Dubbo 中,我们可以通过两种方式引用远程服务.第一种是使用服务直联的方式引用服务,第二种 ...

  6. Dubbo 源码分析 - 服务导出

    1.服务导出过程 本篇文章,我们来研究一下 Dubbo 导出服务的过程.Dubbo 服务导出过程始于 Spring 容器发布刷新事件,Dubbo 在接收到事件后,会立即执行服务导出逻辑.整个逻辑大致可 ...

  7. Dubbo源码(九) - 服务调用过程

    1. 前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 源码分析均基于官方Demo,路径:dubbo/dubbo-demo 如果没有看过之前Dub ...

  8. Dubbo源码学习--服务是如何引用的

    ReferenceBean 跟服务引用一样,Dubbo的reference配置会被转成ReferenceBean类,ReferenceBean实现了InitializingBean接口,直接看afte ...

  9. Dubbo源码学习--服务是如何发布的

    相关文章: Dubbo源码学习--服务是如何发布的 Dubbo源码学习--服务是如何引用的 ServiceBean ServiceBean 实现ApplicationListener接口监听Conte ...

随机推荐

  1. Kotlin数据模型

    Java中的常用的Bean类,每一个变量我们都需要写getter,setter方法,却都要手动实现很多方法: 尽管可以用工具帮我们完成,但是代码显得很臃肿,不灵活. 特别是当我们需改其中某一个成员的时 ...

  2. python - 条件语句/循环语句/迭代器

    条件测试:if 条件表达式python 的比较操作        所有的python对象都支持比较操作            可用于测试相等性.相对大小等            如果是复合对象,pyt ...

  3. GB GBRT XgBoost

    https://blog.csdn.net/github_38414650/article/details/76061893 https://www.cnblogs.com/wxquare/p/554 ...

  4. RabbitMQ简单应用の公平分发(fair dipatch)

    公平分发(fair dipatch)和轮询分发其实基本一致,只是每次分发的机制变了,由原来的平均分配到现在每次只处理一条消息 1.MQ连接工厂类Connection package com.mmr.r ...

  5. 连接字符串配置在App.config中

    <?xml version="1.0" encoding="utf-8"?> <configuration> <connectio ...

  6. Linux系统安全学习笔记(1)-- 文件系统类型

    今天看了一个关于Linux系统安全的视频教程,这个教程有很多的知识点,我会分几篇博文将我的笔记分享出来. 首先是关于Linux文件系统类型的一些知识,Linux有四种常见的文件系统类型(网上大多数是3 ...

  7. BZOJ 3620: 似乎在梦中见过的样子

    似乎在梦中见过的样子.... 一道水题调了这么久,还半天想不出来怎么 T 的...佩服自己(果然蒟蒻) 这题想想 KMP 但是半天没思路瞟了一眼题解发现暴力枚举起始点,然后 KMP 如图: O( n2 ...

  8. Codeforces 914D - Bash and a Tough Math Puzzle 线段树,区间GCD

    题意: 两个操作, 单点修改 询问一段区间是否能在至多一次修改后,使得区间$GCD$等于$X$ 题解: 正确思路; 线段树维护区间$GCD$,查询$GCD$的时候记录一共访问了多少个$GCD$不被X整 ...

  9. Linux inode与文件系统关系

    inode只有在linux文件系统的概念(ext3,ext4) .inode节点数量与文件存储的关系. 二.在文件系统初始化时设置合适的节点数量. linux服务器在存储文件小而数量多的情况下,需要考 ...

  10. linux TLB表

    TLB - translation lookaside buffer 快表,直译为旁路快表缓冲,也可以理解为页表缓冲,地址变换高速缓存. 由于页表存放在主存中,因此程序每次访存至少需要两次:一次访存获 ...