上一章节分析服务提供方的注册和暴露,这章通过源码分析服务是如何被订阅和引用的?消费者服务的配置文件一般如下:
一开始和服务注册是一个道理,都是要系通过Dubbo自定义的XSD的扩展解析成一个Javabean,上面的配置配件文件有个叫
reference标签,是配置要引用的服务接口,最终会通过parser解析成ReferenceBean,而它实现了这里面有个afterPropertiesSet它会最终调用一个getObject方法,,这个方法内部调用的是get()方法, 这里很关键了,如果ref是空的话,就会通过init方法来进行初始化了,这个方法代码很多其中最后两行代码可以看出它调用一个代理来创建初始化对象,这个方法的逻辑也很多的,但是主要的一个方法就是这个,然后根据SPI适配机制,其最终实现是RegistryProtocol类来完成的,继续看这个类
这这里面会牵涉到RegistryDirectory,它其实是本质是封装了invoker的列表,只不过这个列表是可以变化的,OK 继续分析这个方法是调用了这个方法,
最终调用的是ZookeeperRegistry的父类的subscribe方法,
这里使用了模板设计模式,最终实现是由子类zookeeperregistry的dosubscribe方法来做真正的订阅实现,继续往下看
protected void doSubscribe(final URL url, final NotifyListener listener) { try { if (Constants.ANY_VALUE.equals(url.getServiceInterface())) { String root = toRootPath(); ConcurrentMap, ChildListener> listeners = zkListeners.get(url); if (listeners == null) { zkListeners.putIfAbsent(url, new ConcurrentHashMap , ChildListener>()); listeners = zkListeners.get(url); } ChildListener zkListener = listeners.get(listener); if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { public void childChanged(String parentPath, List 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); Listservices = 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 { Listurls = new ArrayList() ; for (String path : toCategoriesPath(url)) { ConcurrentMap, ChildListener> listeners = zkListeners.get(url); if (listeners == null) { zkListeners.putIfAbsent(url, new ConcurrentHashMap , ChildListener>()); listeners = zkListeners.get(url); } ChildListener zkListener = listeners.get(listener); if (zkListener == null) { listeners.putIfAbsent(listener, new ChildListener() { public void childChanged(String parentPath, List currentChilds) { ZookeeperRegistry. this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)); } }); zkListener = listeners.get(listener); } zkClient.create(path, false); Listchildren = 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); } }
上面还缺少一个知识点没有讲,就是列表的获取,其实RegistryDirectory里面有个doList方法,该方法被父类AbstractRegistryDirectory在list方法里面调用,拿到该list,首先经过router机制,获取到一组可用的invoker列表,然后再经过集群的loadblance负载均衡机制,选择一个真正可用的invoker,返回给客户端.
到这里服务的订阅就完成了,接下里就需要筛选一个真正的invoker流程了,这里就牵涉到了cluster机制,
这里的cluster实现是根据spi机制获取到的,默认实现是
而它又是有对应的invoker进行了包装, 由于 ExtensionLoader 在实例化对象时,会在实例化完成之后自动套上 Wrapper 类,而 MockerClusterWrapper 就是这样一个 Wrapper,所以需要继续看
而它实例化的是,该类才会执行最终的join,在这个类里面其实是通过invoke方法实现的:
这里又涉及到了一个Mock机制,主要是dubbo用来进行降级使用的, 从 MockClusterWrapper.join() 方法可知,实际创建的 ClusterInvoker 是封装了 FailoverClusterInvoker 的 MockerClusterInvoker。
在 MockerClusterInvoker 中,调用之前 Dubbo 会先检查 URL 中是否有 mock 参数(通过服务治理后台 Consumer 端的屏蔽和容错进行设置,或者直接动态设置 mock 参数值),如果存在且以 force 开头,则不发起远程调用直接执行降级逻辑;如果存在且以 fail 开头,则在远程调用异常时才会执行降级逻辑。
因此,通过 MockerClusterWrapper 成功地在 Invoker 中植入了 Mock 机制。至此整个服务消费流程结束.