Dubbo之Cluster目录服务
dubbo版本
- dubbo版本2.6.7
目录服务
-
目录服务(Directory service):是一个储存、组织和提供信息访问服务的软件系统。一个目录是指一组名字和值的映射。它允许根据一个给出的名字来查找对应的值,与词典相似。像词典中每一个词也许会有多个词义,在一个目录中,一个名字也许会与多个不同的信息相关联。
-
目录一般只提供范围非常小的节点类型和数值类型,也可能对任意的或可扩展的一组类型提供支持。比如在一个电话目录中,节点就是姓名而数值项就是电话号码
-
在Dubbo中目录服务存储了一些和服务提供者有关的信息,通过服务目录,服务消费者可获取到服务提供者的信息。Dubbo中目录服务其实就是Invoker对象的集合,这个集合中的元素会随注册中心的变化而进行动态调整。
-
Directory
既提供静态的Invoker列表(用户设置),也提供了动态的Invoker列表(注册中心动态变化)
AbstractDirectory
-
AbstractDirectory
是模板类,封装了通用的流程- 检查Directory是否已经销毁
- 子类实现获取Invoker列表
- 获取路由Router列表,过滤Invoker列表
@Override public List<Invoker<T>> list(Invocation invocation) throws RpcException { if (destroyed) { throw new RpcException("Directory already destroyed .url: " + getUrl()); } //子类实现获取Invoker列表 List<Invoker<T>> invokers = doList(invocation); // 获取路由 Router 列表 List<Router> localRouters = this.routers; // local reference if (localRouters != null && !localRouters.isEmpty()) { for (Router router : localRouters) { try { //runtime参数决定了是否在每次调用服务时都执行路由规则。 //如果runtime为true,那么每次调用服务前,都需要进行服务路由。 if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, false)) { invokers = router.route(invokers, getConsumerUrl(), invocation); } } catch (Throwable t) { logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t); } } } return invokers; }
StaticDirectory
-
StaticDirectory#doList
逻辑非常简单,不做任何处理,直接返回Invoker列表@Override protected List<Invoker<T>> doList(Invocation invocation) throws RpcException { return invokers; }
RegistryDirectory
-
RegistryDirectory 可根据配置变更信息刷新 Invoker 列表。RegistryDirectory的重要逻辑
- 列举Invoker列表
- 接收服务配置变更的逻辑
- 刷新 Invoker 列表
-
RegistryDirectory是一种动态目录服务,实现了NotifyListener,当注册中心服务配置发生变化后,RegistryDirectory 可收到与当前服务相关的变化。收到变更通知后,RegistryDirectory 可根据配置变更信息刷新 Invoker 列表
-
列举Invoker列表:doList本质就是对methodInvokerMap的读取操作
@Override public List<Invoker<T>> doList(Invocation invocation) { // 服务提供者关闭或禁用了服务,此时抛出 No provider 异常 if (forbidden) { // 1. No service provider 2. Service providers are disabled throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " + getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please check status of providers(disabled, not registered or in blacklist)."); } List<Invoker<T>> invokers = null; // 获取 Invoker 本地缓存,这里其实赋值本地变量是没有必要的 Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference if (localMethodInvokerMap != null && localMethodInvokerMap.size() > 0) { //获取方法名和第一个参数 String methodName = RpcUtils.getMethodName(invocation); Object[] args = RpcUtils.getArguments(invocation); // 检测参数列表的第一个参数是否为 String 或 enum 类型 if (args != null && args.length > 0 && args[0] != null && (args[0] instanceof String || args[0].getClass().isEnum())) { // 通过 方法名 + 第一个参数名称 查询 Invoker 列表,具体的使用场景不太清楚 invokers = localMethodInvokerMap.get(methodName + "." + args[0]); // The routing can be enumerated according to the first parameter } if (invokers == null) { // 通过方法名获取 Invoker 列表 invokers = localMethodInvokerMap.get(methodName); } if (invokers == null) { // 通过星号 * 获取 Invoker 列表(泛化调用) invokers = localMethodInvokerMap.get(Constants.ANY_VALUE); } // 冗余逻辑,pull request #2861 移除了下面的 if 分支代码 // https://github.com/apache/dubbo/pull/2861 //provider没有方法,但是consumer依然能获取到invoker if (invokers == null) { Iterator<List<Invoker<T>>> iterator = localMethodInvokerMap.values().iterator(); if (iterator.hasNext()) { invokers = iterator.next(); } } } return invokers == null ? new ArrayList<Invoker<T>>(0) : invokers; }
订阅与动态更新
-
订阅与动态更新主要涉及subscribe、notify、refreshInvoker三个方法
-
RegistryDirectory#subscribe
就是调用注册中心的subscribe方法,RegistryDirectory实现了NotifyListener
,所以传入的就是自己public void subscribe(URL url) { //设置consumer url setConsumerUrl(url); //调用注册中心订阅消息消息消费者URL,listener就是自己 registry.subscribe(url, this); }
-
NotifyListener
是一个通知接口,当收到服务变更通知时触发。public interface NotifyListener { /** * 当收到服务变更通知时触发。 * 通知需处理契约: * 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。<br> * 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。<br> * 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。<br> * 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。<br> * 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。<br> */ void notify(List<URL> urls); }
-
RegistryDirectory#notify
是在注册中心providers、configurators、routers目录下的节点发生变化后,通知RegistryDirectory,从而实现动态发现机制@Override 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的前缀,分别添加到: // invokerUrls(提供者url)、routerUrls(路由信息)、configuratorUrls (配置url)。 for (URL url : urls) { //获取URL协议字段,例如condition://、route://、script://、override://等 String protocol = url.getProtocol(); //获取url的category,providers、configurators、routers String category = url.getParameter(Constants.CATEGORY_KEY, Constants.DEFAULT_CATEGORY); //routers或者route if (Constants.ROUTERS_CATEGORY.equals(category) || Constants.ROUTE_PROTOCOL.equals(protocol)) { routerUrls.add(url); //configurators和override } else if (Constants.CONFIGURATORS_CATEGORY.equals(category) || Constants.OVERRIDE_PROTOCOL.equals(protocol)) { configuratorUrls.add(url); //providers } 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.isEmpty()) { this.configurators = toConfigurators(configuratorUrls); } // routers if (routerUrls != null && !routerUrls.isEmpty()) { List<Router> routers = toRouters(routerUrls); if (routers != null) { // null - do nothing setRouters(routers); } } List<Configurator> localConfigurators = this.configurators; // local reference // merge override parameters this.overrideDirectoryUrl = directoryUrl; if (localConfigurators != null && !localConfigurators.isEmpty()) { for (Configurator configurator : localConfigurators) { this.overrideDirectoryUrl = configurator.configure(overrideDirectoryUrl); } } // providers refreshInvoker(invokerUrls); }
-
refreshInvoker 方法首先会根据入参 invokerUrls 的数量和协议头判断是否禁用所有的服务,如果禁用,则将 forbidden 设为 true,并销毁所有的 Invoker。若不禁用,则将 url 转成 Invoker,得到 的映射关系。然后进一步进行转换,得到 映射关系。之后进行多组 Invoker 合并操作,并将合并结果赋值给 methodInvokerMap。methodInvokerMap 变量在 doList 方法中会被用到,doList 会对该变量进行读操作,在这里是写操作。当新的 Invoker 列表生成后,还要一个重要的工作要做,就是销毁无用的 Invoker,避免服务消费者调用已下线的服务的服务。
rivate void refreshInvoker(List<URL> invokerUrls) { // invokerUrls 仅有一个元素,且 url 协议头为 empty,此时表示禁用所有服务 if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) { this.forbidden = true; // Forbid to access this.methodInvokerMap = null; // Set the method invoker map to null // 销毁所有 Invoker destroyAllInvokers(); // Close all invokers } else { this.forbidden = false; // Allow to access Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) { // 添加缓存 url 到 invokerUrls 中 invokerUrls.addAll(this.cachedInvokerUrls); } else { this.cachedInvokerUrls = new HashSet<URL>(); // 缓存 invokerUrls this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison } if (invokerUrls.isEmpty()) { return; } // 将 url 转成 Invoker Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map // 将 newUrlInvokerMap 转成方法名到 Invoker 列表的映射 Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map // state change // If the calculation is wrong, it is not processed. // 转换出错,直接打印异常,并返回 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; } // 合并多个组的 Invoker this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap; this.urlInvokerMap = newUrlInvokerMap; try { // 销毁无用 Invoker destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker } catch (Exception e) { logger.warn("destroyUnusedInvokers error. ", e); } } }