Dubbo/Dubbox的服务消费(二)- 服务发现
来源:互联网 发布:mac 抹除系统 重装 编辑:程序博客网 时间:2024/06/05 18:00
上文书整理了dubbo是如何生成服务代理的,并且留了个尾巴,这一文主要介绍dubbo是如何实现服务发现的,继续前文的脚步,看一下dubbo如何完成传说中的服务自动发现
打开com.alibaba.dubbo.config.ReferenceConfig
类,只关注
@SuppressWarnings({ "unchecked", "rawtypes", "deprecation" }) private T createProxy(Map<String, String> map) { //...省略不相关代码 invoker = refprotocol.refer(interfaceClass, urls.get(0)); //...省略不相关代码 }
其中refprotocol是一个全局变量
private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
可见其是一个“自适应扩展点”那么我们根据去看Protocol的refer方法对应的extName是什么,其方法签名如下
/** * 引用远程服务:<br> * 1. 当用户调用refer()所返回的Invoker对象的invoke()方法时,协议需相应执行同URL远端export()传入的Invoker对象的invoke()方法。<br> * 2. refer()返回的Invoker由协议实现,协议通常需要在此Invoker中发送远程请求。<br> * 3. 当url中有设置check=false时,连接失败不能抛出异常,并内部自动恢复。<br> * * @param <T> 服务的类型 * @param type 服务的类型 * @param url 远程服务的URL地址 * @return invoker 服务的本地代理 * @throws RpcException 当连接服务提供方失败时抛出 */ @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
这里URL参数为
registry://注册中心地址:2181/com.alibaba.dubbo.registry.RegistryService?application=demo&dubbo=2.8.4&file=缓存文件路径&logger=log4j&organization=demo&owner=weiythi&pid=16072&
refer=application%3Dprojectname%26default.check%3Dfalse%26default.timeout%3D10000%26dubbo%3D2.8.4%26interface%3Dcom.companyName.projectName.moduleName.protocol.dubbo.ServiceName%26logger%3Dlog4j%26
methods%3DadSync%26organization%3DconpanyName%26owner%3Dweiythi%26pid%3D16072%26protocol%3Ddubbo%26revision%3D1.0.0-SNAPSHOT%26side%3Dconsumer%26timestamp%3D1511402541402&
registry=zookeeper×tamp=1511402541632
已知针对针对该连接的protocol为registry ,所以会使用如下的扩展点
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class) .getExtension("registry");
返回 META-INF\dubbo\internal\com.alibaba.dubbo.rpc.Protocol 文件 其内容如下
registry=com.alibaba.dubbo.registry.integration.RegistryProtocoldubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocolfilter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapperlistener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrappermock=com.alibaba.dubbo.rpc.support.MockProtocolinjvm=com.alibaba.dubbo.rpc.protocol.injvm.InjvmProtocolrmi=com.alibaba.dubbo.rpc.protocol.rmi.RmiProtocolhessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocolcom.alibaba.dubbo.rpc.protocol.http.HttpProtocolcom.alibaba.dubbo.rpc.protocol.webservice.WebServiceProtocolthrift=com.alibaba.dubbo.rpc.protocol.thrift.ThriftProtocolmemcached=com.alibaba.dubbo.rpc.protocol.memcached.MemcachedProtocolredis=com.alibaba.dubbo.rpc.protocol.redis.RedisProtocolrest=com.alibaba.dubbo.rpc.protocol.rest.RestProtocol
发现有两个包装类
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
根据扩展点的规则
两个包装类的套用是无序的,即有可能是以下两种情况
ProtocolFilterWrapper->ProtocolListenerWrapper->RegistryProtocol
ProtocolListenerWrapper->ProtocolFilterWrapper->RegistryProtocol
最终调用对象,都是RegistryProtocol,那么看一下该类的refer方法实现。
@SuppressWarnings("unchecked") public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { //该操作会将url上的registry参数设置成协议,然后移除URL上的registry参数 url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY); //根据对应的协议,取得对应的注册中心实现,以zookeeper为例,这里取到的registry为com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry Registry registry = registryFactory.getRegistry(url); //根据上文我们传入的参数是服务接口对象,所以该条件不成立 if (RegistryService.class.equals(type)) { 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 ); } } //查看doRefer方法,这里传入了一个cluster对象 return doRefer(cluster, registry, type, url); }
cluster是一个全局变量,其定义如下
private Cluster cluster;
其使用扩展点的com.alibaba.dubbo.common.extension.ExtensionLoader.injectExtension(T instance) 方法,对其完成赋值
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); //初始化一个订阅链接 consumer://本机地址(无端口号)/服务接口名?xxxx=yyyy...... 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)) { //会在zk中创建节点,节点parent path为 registry.register(subscribeUrl.addParameters(Constants.CATEGORY_KEY, Constants.CONSUMERS_CATEGORY, Constants.CHECK_KEY, String.valueOf(false))); } //订阅服务 //1.向url总线中添加订阅的主题,一共三个分别为 providers,configurators,routers //2.调用 registry.subscribe(url, this); directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY + "," + Constants.CONFIGURATORS_CATEGORY + "," + Constants.ROUTERS_CATEGORY)); return cluster.join(directory); }
directory.subscribe(url)
调用registry.subscribe(url, this);
方法,第二个参数为其本身,他的UML图如下,可见其本身就是一个com.alibaba.dubbo.registry.NotifyListener(事件监听器)
subscribe 方法最终会调用com.alibaba.dubbo.registry.zookeeper.doSubscribe,其内部代码如下
protected void doSubscribe(final URL url, final NotifyListener listener) { try { if (Constants.ANY_VALUE.equals(url.getServiceInterface())) { //不管,省略. } else { List<URL> urls = new ArrayList<URL>(); //toCategoriesPath 会生成一个String 数组,数组元素如下 // /dubbo/服务接口名/providers // /dubbo/服务接口名/configurators // /dubbo/服务接口名/routers for (String path : toCategoriesPath(url)) { // 缓存相关处理,即如果为当前consumer创建对应监听器,则取缓存中的,如果没有则新建 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) { ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)); } }); zkListener = listeners.get(listener); } //创建节点,如果第二个参数值为true则为临时节点,此处创建了永久节点 zkClient.create(path, false); //添加监视点事件监听器,方法内部会调用 org.I0Itec.zkclient.ZkClient.subscribeChildChanges(path, listener); 方法, //并返回该方法的返回值(子节点列表) List<String> children = zkClient.addChildListener(path, zkListener); if (children != null) { urls.addAll(toUrlsWithEmpty(url, path, children)); } } // 该方法继承自 com.alibaba.dubbo.registry.support.FailbackRegistry notify(url, listener, urls); } } catch (Throwable e) { throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e); } }
这里注意 toUrlsWithEmpty(url, parentPath, currentChilds)
方法,该方法会根据consumerUrl过滤zk中的providerUrl,拿到相匹配的服务列表,否则会根据consumerUrl生成带有category参数的empty协议url(这个empty url的作用我没理解上去)
已知com.alibaba.dubbo.registry.support.FailbackRegistry类中的notify方法会做以下几个操作
1.判断url和listener是否为null 如果为null则抛出IllegalArgumentException 异常
2.参数检验没有问题,则调用doNotify(URL url, NotifyListener listener, List urls) 方法
doNotify(URL url, NotifyListener listener, List urls) 方法调用 com.alibaba.dubbo.registry.support.AbstractRegistry 类中的
protected void notify(@NotNull URL url,@NotNull NotifyListener listener,List urls) 方法
如果AbstractRegistry类的notify方法中抛出异常则将失败的通知请求记录到失败列表,定时重试
目前为止的调用顺序图:
以下是com.alibaba.dubbo.registry.support.AbstractRegistry 类的protected void notify(@NotNull URL url,@NotNull NotifyListener listener,List urls) 方法实现
protected void notify(URL url, NotifyListener listener, List<URL> urls) { //......省略参数校验和日志输出......// Map<String, List<URL>> result = new HashMap<String, List<URL>>(); for (URL u : urls) { //该方法会判断服务接口是否一致,group version 等配置是否一致,通过这个方法可以过滤当前consumer不匹配的服务。 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; } 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(); List<URL> categoryList = entry.getValue(); categoryNotified.put(category, categoryList); //在缓存文件中保存信息 (dubbo.cache.file 配置的文件) saveProperties(url); //调用实际的监听器 ,针对服务的发现来说,此处调用的为 com.alibaba.dubbo.registry.integration.RegistryDirectory 类的相关实现 listener.notify(categoryList); } }
com.alibaba.dubbo.registry.integration.RegistryDirectory的notify实现如下
public synchronized void notify(List<URL> urls) { List<URL> invokerUrls = new ArrayList<URL>(); List<URL> routerUrls = new ArrayList<URL>(); List<URL> configuratorUrls = new ArrayList<URL>(); //将传入url按照category分类处理 for (URL url : urls) { String protocol = url.getProtocol(); String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) { routerUrls.add(url); } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) { configuratorUrls.add(url); } else if (Constants.PROVIDERS_CATEGORY.equals(category)) { invokerUrls.add(url); } else { logger.warn("Unsupported category " + category + " in notified url: " + url + " from registry " + getUrl().getAddress() + " to consumer " + NetUtils.getLocalHost()); } } // 刷新 configurators if (configuratorUrls != null && configuratorUrls.size() >0 ){ this.configurators = toConfigurators(configuratorUrls); } // 刷新 routers if (routerUrls != null && routerUrls.size() >0 ){ List<Router> routers = toRouters(routerUrls); if(routers != null){ // null - do nothing setRouters(routers); } } List<Configurator> localConfigurators = this.configurators; // local reference // 合并override参数 this.overrideDirectoryUrl = directoryUrl; if (localConfigurators != null && localConfigurators.size() > 0) { for (Configurator configurator : localConfigurators) { this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl); } } // 刷新 invokers,如果是空协议(empty://)会关闭所有的invoker //根据invokerURL列表转换为invoker列表。转换规则如下: //1.如果url已经被转换为invoker,则不在重新引用,直接从缓存中获取,注意如果url中任何一个参数变更也会重新引用 //2.如果传入的invoker列表不为空,则表示最新的invoker列表 //3.如果传入的invokerUrl列表是空,则表示只是下发的override规则或route规则,需要重新交叉对比,决定是否需要重新引用。 refreshInvoker(invokerUrls); }
至此服务的发现流程已全部了解,即com.alibaba.dubbo.registry.integration.RegistryProtocol的 doRefer方法中的代码行
directory.subscribe(subscribeUrl.addParameter(Constants.CATEGORY_KEY, Constants.PROVIDERS_CATEGORY + "," + Constants.CONFIGURATORS_CATEGORY + "," + Constants.ROUTERS_CATEGORY));
调用流程已全部完成,该方法还剩最后一行
cluster.join(directory); 但该方法与本文无关,整理dubbo服务实现的时候再说。
- Dubbo/Dubbox的服务消费(二)- 服务发现
- Dubbo/Dubbox的服务消费(一)- 服务代理的创建
- Dubbo/Dubbox的服务暴露(二)-扩展点机制
- Dubbo/Dubbox的服务暴露(一)
- Dubbo/Dubbox的服务暴露(三)- 服务的注册
- 分布式服务框架 dubbo/dubbox 的搭建
- dubbox为dubbo提供REST服务(dubbox + springmvc)
- dubbo2.5.3消费dubbox提供的服务报错
- dubbo-服务消费端类图
- 分布式服务框架 dubbo/dubbox 入门示例
- 分布式服务框架 dubbo/dubbox 入门示例
- 分布式服务框架 dubbo/dubbox 入门示例
- 分布式服务框架 dubbo/dubbox 入门示例
- 分布式服务框架Dubbo/Dubbox入门示例
- 分布式服务框架 dubbo/dubbox 入门示例
- 分布式服务框架 dubbo/dubbox 入门示例
- 分布式服务框架 dubbo/dubbox 基础
- 分布式服务框架 dubbo/dubbox 入门示例
- 建设部,住建部,住房和城乡建设部,区别?
- 使用Apache ab进行压测
- React | 高效前端之浅谈
- Android开发从一个activity设置跳转到另一个activity中的一个fragment中的一个viewpager中的某一个页面
- asp.net中HTML控件和web控件的简单理解
- Dubbo/Dubbox的服务消费(二)- 服务发现
- 超强、超详细Redis数据库入门教程
- 接口
- AIDL详解之Messenger
- Java泛型
- Picasso的简单实用
- SurfaceView介绍和通用模板
- 静态资源路径是指系统可以直接访问的路径,且路径下的所有文件均可被用户直接读取
- 产品经理,原型设计之前你要做些什么?